use async_graphql::{Context, InputObject, Object, Result};
use chrono::{Datelike, NaiveDate};
use uuid::Uuid;

use crate::auth::UserContext;
use crate::db::Database;
use crate::graphql::types::{ServiceTeamMemberType, ServiceType, WorkStatusType};
use crate::models::{EventType, Schedule, Service, ServiceTeamMember, WorkStatus};
use crate::services::{EventPublisher, JobQueue};

/// Input for creating a service
#[derive(InputObject)]
pub struct CreateServiceInput {
    pub account_id: Uuid,
    pub account_address_id: Uuid,
    pub date: NaiveDate,
    pub status: Option<WorkStatusType>,
    pub notes: Option<String>,
}

/// Input for updating a service
#[derive(InputObject)]
pub struct UpdateServiceInput {
    pub date: Option<NaiveDate>,
    pub status: Option<WorkStatusType>,
    pub notes: Option<String>,
    pub calendar_event_id: Option<String>,
}

/// Input for generating services from a schedule
#[derive(InputObject)]
pub struct GenerateServicesInput {
    pub account_address_id: Uuid,
    pub schedule_id: Uuid,
    pub month: i32,
    pub year: i32,
}

#[derive(Default)]
pub struct ServiceMutation;

#[Object]
impl ServiceMutation {
    /// Create a new service
    async fn create_service(
        &self,
        ctx: &Context<'_>,
        input: CreateServiceInput,
    ) -> Result<ServiceType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let status = input.status.map(WorkStatus::from).unwrap_or(WorkStatus::Scheduled);

        let service: Service = sqlx::query_as::<_, Service>(
            r#"
            INSERT INTO services (
                id, created_at, updated_at, account_id, account_address_id, date, status, notes
            )
            VALUES (
                gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5
            )
            RETURNING id, created_at, updated_at, account_id, account_address_id, date, status, notes, calendar_event_id
            "#,
        )
        .bind(input.account_id)
        .bind(input.account_address_id)
        .bind(input.date)
        .bind(status)
        .bind(&input.notes)
        .fetch_one(&*pool)
        .await?;

        // Publish ServiceCreated event
        if let (Ok(user), Ok(job_queue)) = (ctx.data::<UserContext>(), ctx.data::<JobQueue>()) {
            let _ = EventPublisher::publish_and_queue(
                &pool,
                job_queue,
                EventType::ServiceCreated,
                "service",
                service.base.id,
                Some(("team_profile", user.user_id)),
                None,
            )
            .await;
        }

        Ok(ServiceType::from(service))
    }

    /// Update a service
    async fn update_service(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateServiceInput,
    ) -> Result<ServiceType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get old status for event tracking
        let old_status: Option<WorkStatus> = sqlx::query_scalar(
            "SELECT status FROM services WHERE id = $1",
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

        let new_status = input.status.map(WorkStatus::from);

        let service: Service = sqlx::query_as::<_, Service>(
            r#"
            UPDATE services
            SET
                date = COALESCE($2, date),
                status = COALESCE($3, status),
                notes = COALESCE($4, notes),
                calendar_event_id = COALESCE($5, calendar_event_id),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, account_id, account_address_id, date, status, notes, calendar_event_id
            "#,
        )
        .bind(id)
        .bind(input.date)
        .bind(new_status)
        .bind(&input.notes)
        .bind(&input.calendar_event_id)
        .fetch_one(&*pool)
        .await?;

        // Publish event for status change
        if let (Ok(user), Ok(job_queue)) = (ctx.data::<UserContext>(), ctx.data::<JobQueue>()) {
            if let (Some(old), Some(new)) = (old_status, new_status) {
                if old != new {
                    let _ = EventPublisher::publish_and_queue(
                        &pool,
                        job_queue,
                        EventType::ServiceStatusChanged,
                        "service",
                        service.base.id,
                        Some(("team_profile", user.user_id)),
                        Some(serde_json::json!({
                            "old_status": format!("{:?}", old),
                            "new_status": format!("{:?}", new)
                        })),
                    )
                    .await;
                }
            }
        }

        Ok(ServiceType::from(service))
    }

    /// Delete a service
    async fn delete_service(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Delete team member assignments first
        sqlx::query("DELETE FROM service_team_members WHERE service_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        // Delete the service
        let result = sqlx::query("DELETE FROM services WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        // Publish ServiceDeleted event
        if result.rows_affected() > 0 {
            if let (Ok(user), Ok(job_queue)) = (ctx.data::<UserContext>(), ctx.data::<JobQueue>()) {
                let _ = EventPublisher::publish_and_queue(
                    &pool,
                    job_queue,
                    EventType::ServiceDeleted,
                    "service",
                    id,
                    Some(("team_profile", user.user_id)),
                    None,
                )
                .await;
            }
        }

        Ok(result.rows_affected() > 0)
    }

    /// Assign a team member to a service
    async fn assign_service_team_member(
        &self,
        ctx: &Context<'_>,
        service_id: Uuid,
        team_profile_id: Uuid,
    ) -> Result<ServiceTeamMemberType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let member: ServiceTeamMember = sqlx::query_as::<_, ServiceTeamMember>(
            r#"
            INSERT INTO service_team_members (id, service_id, team_profile_id, created_at)
            VALUES (gen_random_uuid(), $1, $2, NOW())
            ON CONFLICT (service_id, team_profile_id) DO UPDATE SET service_id = EXCLUDED.service_id
            RETURNING id, service_id, team_profile_id, created_at
            "#,
        )
        .bind(service_id)
        .bind(team_profile_id)
        .fetch_one(&*pool)
        .await?;

        // Publish ServiceAssigned event
        if let (Ok(user), Ok(job_queue)) = (ctx.data::<UserContext>(), ctx.data::<JobQueue>()) {
            let _ = EventPublisher::publish_and_queue(
                &pool,
                job_queue,
                EventType::ServiceAssigned,
                "service",
                service_id,
                Some(("team_profile", user.user_id)),
                Some(serde_json::json!({
                    "assigned_team_profile_id": team_profile_id.to_string()
                })),
            )
            .await;
        }

        Ok(ServiceTeamMemberType::from(member))
    }

    /// Remove a team member assignment from a service
    async fn remove_service_team_member(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get the service_id and team_profile_id before deleting
        let assignment: Option<(Uuid, Uuid)> = sqlx::query_as(
            "SELECT service_id, team_profile_id FROM service_team_members WHERE id = $1",
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

        let result = sqlx::query("DELETE FROM service_team_members WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        // Publish ServiceUnassigned event
        if result.rows_affected() > 0 {
            if let Some((service_id, team_profile_id)) = assignment {
                if let (Ok(user), Ok(job_queue)) = (ctx.data::<UserContext>(), ctx.data::<JobQueue>()) {
                    let _ = EventPublisher::publish_and_queue(
                        &pool,
                        job_queue,
                        EventType::ServiceUnassigned,
                        "service",
                        service_id,
                        Some(("team_profile", user.user_id)),
                        Some(serde_json::json!({
                            "unassigned_team_profile_id": team_profile_id.to_string()
                        })),
                    )
                    .await;
                }
            }
        }

        Ok(result.rows_affected() > 0)
    }

    /// Generate services for a month based on a schedule
    ///
    /// Creates services for all days in the specified month that match the schedule's day flags.
    /// For weekend_service schedules, creates a Friday service with "Weekend service window (Fri-Sun)" note.
    /// Fails if any service already exists for the generated dates (all-or-nothing).
    async fn generate_services_by_month(
        &self,
        ctx: &Context<'_>,
        input: GenerateServicesInput,
    ) -> Result<Vec<ServiceType>> {
        // Validate month
        if input.month < 1 || input.month > 12 {
            return Err("Month must be between 1 and 12".into());
        }

        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Fetch schedule and verify ownership
        let schedule: Schedule = sqlx::query_as::<_, Schedule>(
            r#"
            SELECT id, created_at, updated_at, account_address_id, name,
                   monday_service as monday, tuesday_service as tuesday, wednesday_service as wednesday,
                   thursday_service as thursday, friday_service as friday, saturday_service as saturday,
                   sunday_service as sunday, weekend_service, schedule_exception, start_date, end_date
            FROM schedules
            WHERE id = $1
            "#,
        )
        .bind(input.schedule_id)
        .fetch_optional(&*pool)
        .await?
        .ok_or("Schedule not found")?;

        // Verify schedule belongs to the address
        if schedule.account_address_id != input.account_address_id {
            return Err("Schedule does not belong to the specified address".into());
        }

        // Get account_id from address
        let account_id: Uuid = sqlx::query_scalar(
            "SELECT account_id FROM account_addresses WHERE id = $1",
        )
        .bind(input.account_address_id)
        .fetch_one(&*pool)
        .await?;

        // Build list of target dates
        let mut targets: Vec<(NaiveDate, Option<String>)> = Vec::new();

        // Get first and last day of month
        let first_day = NaiveDate::from_ymd_opt(input.year, input.month as u32, 1)
            .ok_or("Invalid date")?;
        let last_day = if input.month == 12 {
            NaiveDate::from_ymd_opt(input.year + 1, 1, 1)
        } else {
            NaiveDate::from_ymd_opt(input.year, input.month as u32 + 1, 1)
        }
        .ok_or("Invalid date")?
        .pred_opt()
        .ok_or("Invalid date")?;

        // Iterate through all days in the month
        let mut current = first_day;
        while current <= last_day {
            // Check if schedule is active on this date
            if schedule.is_active_on(current) {
                // Get weekday (0=Monday, 6=Sunday in chrono)
                let weekday = current.weekday().num_days_from_monday();
                let (should_generate, note) = schedule.should_generate_for_weekday(weekday);

                if should_generate {
                    targets.push((current, note.map(String::from)));
                }
            }

            current = current.succ_opt().ok_or("Date overflow")?;
        }

        if targets.is_empty() {
            return Ok(Vec::new());
        }

        // Check for existing services on any of these dates
        let dates: Vec<NaiveDate> = targets.iter().map(|(d, _)| *d).collect();
        let existing_count: i64 = sqlx::query_scalar(
            r#"
            SELECT COUNT(*) FROM services
            WHERE account_address_id = $1 AND date = ANY($2)
            "#,
        )
        .bind(input.account_address_id)
        .bind(&dates)
        .fetch_one(&*pool)
        .await?;

        if existing_count > 0 {
            return Err(format!(
                "Cannot generate services: {} service(s) already exist for the selected dates",
                existing_count
            )
            .into());
        }

        // Bulk insert services
        let mut created_services = Vec::new();

        for (date, note) in &targets {
            let service: Service = sqlx::query_as::<_, Service>(
                r#"
                INSERT INTO services (
                    id, created_at, updated_at, account_id, account_address_id, date, status, notes
                )
                VALUES (
                    gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5
                )
                RETURNING id, created_at, updated_at, account_id, account_address_id, date, status, notes, calendar_event_id
                "#,
            )
            .bind(account_id)
            .bind(input.account_address_id)
            .bind(date)
            .bind(WorkStatus::Scheduled)
            .bind(note)
            .fetch_one(&*pool)
            .await?;

            created_services.push(service);
        }

        // Publish events for created services
        if let (Ok(user), Ok(job_queue)) = (ctx.data::<UserContext>(), ctx.data::<JobQueue>()) {
            for service in &created_services {
                let _ = EventPublisher::publish_and_queue(
                    &pool,
                    job_queue,
                    EventType::ServiceCreated,
                    "service",
                    service.base.id,
                    Some(("team_profile", user.user_id)),
                    Some(serde_json::json!({
                        "generated_from_schedule": input.schedule_id.to_string(),
                        "month": input.month,
                        "year": input.year
                    })),
                )
                .await;
            }
        }

        Ok(created_services.into_iter().map(ServiceType::from).collect())
    }
}
