use async_graphql::{Context, Object, Result};
use uuid::Uuid;

use crate::db::Database;
use crate::graphql::types::{
    AccountAddressType, CreateLaborInput, CreateScheduleInput, CreateServiceScopeInput,
    CreateScopeAreaInput, CreateScopeTaskInput, LaborType, ScheduleType, ServiceScopeAreaType,
    ServiceScopeTaskType, ServiceScopeType, TaskFrequencyType, UpdateAccountAddressInput,
    UpdateLaborInput, UpdateScheduleInput, UpdateServiceScopeInput, UpdateScopeAreaInput,
    UpdateScopeTaskInput,
};
use crate::models::{
    AccountAddress, Labor, Schedule, ServiceScope, ServiceScopeArea, ServiceScopeTask, TaskFrequency,
};

#[derive(Default)]
pub struct LocationMutation;

#[Object]
impl LocationMutation {
    // ==================== LABOR MUTATIONS ====================

    /// Create a new labor rate for an address (deactivates existing active labor)
    async fn create_labor(
        &self,
        ctx: &Context<'_>,
        account_address_id: Uuid,
        input: CreateLaborInput,
    ) -> Result<LaborType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // End any existing active labor rates for this address
        let today = chrono::Utc::now().date_naive();
        sqlx::query(
            r#"
            UPDATE labor
            SET end_date = $2, updated_at = NOW()
            WHERE account_address_id = $1 AND (end_date IS NULL OR end_date > $2)
            "#,
        )
        .bind(account_address_id)
        .bind(today)
        .execute(&*pool)
        .await?;

        let labor: Labor = sqlx::query_as::<_, Labor>(
            r#"
            INSERT INTO labor (id, created_at, updated_at, account_address_id, amount, start_date, end_date)
            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, NULL)
            RETURNING id, created_at, updated_at, account_address_id, amount, start_date, end_date
            "#,
        )
        .bind(account_address_id)
        .bind(input.amount)
        .bind(input.start_date)
        .fetch_one(&*pool)
        .await?;

        Ok(LaborType::from(labor))
    }

    /// Update a labor rate
    async fn update_labor(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateLaborInput,
    ) -> Result<LaborType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let labor: Labor = sqlx::query_as::<_, Labor>(
            r#"
            UPDATE labor
            SET
                amount = COALESCE($2, amount),
                start_date = COALESCE($3, start_date),
                end_date = COALESCE($4, end_date),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, account_address_id, amount, start_date, end_date
            "#,
        )
        .bind(id)
        .bind(input.amount)
        .bind(input.start_date)
        .bind(input.end_date)
        .fetch_one(&*pool)
        .await?;

        Ok(LaborType::from(labor))
    }

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

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

        Ok(result.rows_affected() > 0)
    }

    // ==================== SCHEDULE MUTATIONS ====================

    /// Create a new schedule for an address (ends existing active schedules)
    async fn create_schedule(
        &self,
        ctx: &Context<'_>,
        account_address_id: Uuid,
        input: CreateScheduleInput,
    ) -> Result<ScheduleType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // End any existing active schedules for this address
        let today = chrono::Utc::now().date_naive();
        sqlx::query(
            r#"
            UPDATE schedules
            SET end_date = $2, updated_at = NOW()
            WHERE account_address_id = $1 AND (end_date IS NULL OR end_date > $2)
            "#,
        )
        .bind(account_address_id)
        .bind(today)
        .execute(&*pool)
        .await?;

        let schedule: Schedule = sqlx::query_as::<_, Schedule>(
            r#"
            INSERT INTO schedules (
                id, created_at, updated_at, account_address_id, name,
                monday_service, tuesday_service, wednesday_service, thursday_service,
                friday_service, saturday_service, sunday_service, weekend_service,
                schedule_exception, start_date, end_date
            )
            VALUES (
                gen_random_uuid(), NOW(), NOW(), $1, $2,
                $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, NULL
            )
            RETURNING 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
            "#,
        )
        .bind(account_address_id)
        .bind(&input.name)
        .bind(input.monday.unwrap_or(false))
        .bind(input.tuesday.unwrap_or(false))
        .bind(input.wednesday.unwrap_or(false))
        .bind(input.thursday.unwrap_or(false))
        .bind(input.friday.unwrap_or(false))
        .bind(input.saturday.unwrap_or(false))
        .bind(input.sunday.unwrap_or(false))
        .bind(input.weekend_service.unwrap_or(false))
        .bind(&input.schedule_exception)
        .bind(input.start_date)
        .fetch_one(&*pool)
        .await?;

        Ok(ScheduleType::from(schedule))
    }

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

        let schedule: Schedule = sqlx::query_as::<_, Schedule>(
            r#"
            UPDATE schedules
            SET
                name = COALESCE($2, name),
                monday_service = COALESCE($3, monday_service),
                tuesday_service = COALESCE($4, tuesday_service),
                wednesday_service = COALESCE($5, wednesday_service),
                thursday_service = COALESCE($6, thursday_service),
                friday_service = COALESCE($7, friday_service),
                saturday_service = COALESCE($8, saturday_service),
                sunday_service = COALESCE($9, sunday_service),
                weekend_service = COALESCE($10, weekend_service),
                schedule_exception = COALESCE($11, schedule_exception),
                start_date = COALESCE($12, start_date),
                end_date = COALESCE($13, end_date),
                updated_at = NOW()
            WHERE id = $1
            RETURNING 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
            "#,
        )
        .bind(id)
        .bind(&input.name)
        .bind(input.monday)
        .bind(input.tuesday)
        .bind(input.wednesday)
        .bind(input.thursday)
        .bind(input.friday)
        .bind(input.saturday)
        .bind(input.sunday)
        .bind(input.weekend_service)
        .bind(&input.schedule_exception)
        .bind(input.start_date)
        .bind(input.end_date)
        .fetch_one(&*pool)
        .await?;

        Ok(ScheduleType::from(schedule))
    }

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

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

        Ok(result.rows_affected() > 0)
    }

    // ==================== SERVICE SCOPE MUTATIONS ====================

    /// Create a new service scope for an address (deactivates existing active scopes)
    async fn create_service_scope(
        &self,
        ctx: &Context<'_>,
        account_id: Uuid,
        account_address_id: Uuid,
        input: CreateServiceScopeInput,
    ) -> Result<ServiceScopeType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Deactivate any existing active scopes for this address
        sqlx::query(
            r#"
            UPDATE service_scopes
            SET is_active = false, updated_at = NOW()
            WHERE account_address_id = $1 AND is_active = true
            "#,
        )
        .bind(account_address_id)
        .execute(&*pool)
        .await?;

        // Create the scope
        let scope: ServiceScope = sqlx::query_as::<_, ServiceScope>(
            r#"
            INSERT INTO service_scopes (
                id, created_at, updated_at, name, account_id, account_address_id, description, is_active
            )
            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, true)
            RETURNING id, created_at, updated_at, name, account_id, account_address_id, description, is_active
            "#,
        )
        .bind(&input.name)
        .bind(account_id)
        .bind(account_address_id)
        .bind(&input.description)
        .fetch_one(&*pool)
        .await?;

        // Create areas and tasks if provided
        if let Some(areas) = input.areas {
            for (area_order, area_input) in areas.into_iter().enumerate() {
                let area: ServiceScopeArea = sqlx::query_as::<_, ServiceScopeArea>(
                    r#"
                    INSERT INTO service_scope_areas (id, created_at, updated_at, scope_id, name, "order")
                    VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3)
                    RETURNING id, created_at, updated_at, scope_id, name, "order"
                    "#,
                )
                .bind(scope.base.id)
                .bind(&area_input.name)
                .bind(area_input.order.unwrap_or(area_order as i32))
                .fetch_one(&*pool)
                .await?;

                if let Some(tasks) = area_input.tasks {
                    for (task_order, task_input) in tasks.into_iter().enumerate() {
                        let frequency = task_input
                            .frequency
                            .map(TaskFrequency::from)
                            .unwrap_or(TaskFrequency::Daily);

                        sqlx::query(
                            r#"
                            INSERT INTO service_scope_tasks (
                                id, created_at, updated_at, area_id, scope_description,
                                checklist_description, session_description, frequency, "order", estimated_minutes
                            )
                            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6, $7)
                            "#,
                        )
                        .bind(area.base.id)
                        .bind(&task_input.scope_description)
                        .bind(task_input.checklist_description.as_deref().unwrap_or(&task_input.scope_description))
                        .bind(task_input.session_description.as_deref().unwrap_or(&task_input.scope_description))
                        .bind(frequency)
                        .bind(task_input.order.unwrap_or(task_order as i32))
                        .bind(task_input.estimated_minutes)
                        .execute(&*pool)
                        .await?;
                    }
                }
            }
        }

        Ok(ServiceScopeType::from(scope))
    }

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

        // If activating this scope, deactivate others for same address
        if input.is_active == Some(true) {
            let address_id: Uuid = sqlx::query_scalar(
                "SELECT account_address_id FROM service_scopes WHERE id = $1",
            )
            .bind(id)
            .fetch_one(&*pool)
            .await?;

            sqlx::query(
                r#"
                UPDATE service_scopes
                SET is_active = false, updated_at = NOW()
                WHERE account_address_id = $1 AND id != $2 AND is_active = true
                "#,
            )
            .bind(address_id)
            .bind(id)
            .execute(&*pool)
            .await?;
        }

        let scope: ServiceScope = sqlx::query_as::<_, ServiceScope>(
            r#"
            UPDATE service_scopes
            SET
                name = COALESCE($2, name),
                description = COALESCE($3, description),
                is_active = COALESCE($4, is_active),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, name, account_id, account_address_id, description, is_active
            "#,
        )
        .bind(id)
        .bind(&input.name)
        .bind(&input.description)
        .bind(input.is_active)
        .fetch_one(&*pool)
        .await?;

        Ok(ServiceScopeType::from(scope))
    }

    /// Delete a service scope (soft-delete if sessions exist, hard delete otherwise)
    async fn delete_service_scope(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Check if any service_sessions reference this scope
        let session_count: i64 = sqlx::query_scalar(
            "SELECT COUNT(*) FROM service_sessions WHERE scope_id = $1",
        )
        .bind(id)
        .fetch_one(&*pool)
        .await?;

        if session_count > 0 {
            // Soft-delete: just mark as inactive to preserve historical session data
            let result = sqlx::query(
                "UPDATE service_scopes SET is_active = false, updated_at = NOW() WHERE id = $1",
            )
            .bind(id)
            .execute(&*pool)
            .await?;

            return Ok(result.rows_affected() > 0);
        }

        // Hard delete: no sessions reference this scope, safe to remove completely
        // Get all area IDs for this scope
        let area_ids: Vec<Uuid> = sqlx::query_scalar(
            "SELECT id FROM service_scope_areas WHERE scope_id = $1",
        )
        .bind(id)
        .fetch_all(&*pool)
        .await?;

        // Delete tasks for all areas
        for area_id in &area_ids {
            sqlx::query("DELETE FROM service_scope_tasks WHERE area_id = $1")
                .bind(area_id)
                .execute(&*pool)
                .await?;
        }

        // Delete areas
        sqlx::query("DELETE FROM service_scope_areas WHERE scope_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

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

        Ok(result.rows_affected() > 0)
    }

    // ==================== SCOPE AREA MUTATIONS ====================

    /// Create a new area within a scope
    async fn create_scope_area(
        &self,
        ctx: &Context<'_>,
        scope_id: Uuid,
        input: CreateScopeAreaInput,
    ) -> Result<ServiceScopeAreaType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get max order for existing areas
        let max_order: Option<i32> = sqlx::query_scalar(
            r#"SELECT MAX("order") FROM service_scope_areas WHERE scope_id = $1"#,
        )
        .bind(scope_id)
        .fetch_one(&*pool)
        .await?;

        let area: ServiceScopeArea = sqlx::query_as::<_, ServiceScopeArea>(
            r#"
            INSERT INTO service_scope_areas (id, created_at, updated_at, scope_id, name, "order")
            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3)
            RETURNING id, created_at, updated_at, scope_id, name, "order"
            "#,
        )
        .bind(scope_id)
        .bind(&input.name)
        .bind(input.order.unwrap_or(max_order.unwrap_or(0) + 1))
        .fetch_one(&*pool)
        .await?;

        // Create tasks if provided
        if let Some(tasks) = input.tasks {
            for (task_order, task_input) in tasks.into_iter().enumerate() {
                let frequency = task_input
                    .frequency
                    .map(TaskFrequency::from)
                    .unwrap_or(TaskFrequency::Daily);

                sqlx::query(
                    r#"
                    INSERT INTO service_scope_tasks (
                        id, created_at, updated_at, area_id, scope_description,
                        checklist_description, session_description, frequency, "order", estimated_minutes
                    )
                    VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6, $7)
                    "#,
                )
                .bind(area.base.id)
                .bind(&task_input.scope_description)
                .bind(task_input.checklist_description.as_deref().unwrap_or(&task_input.scope_description))
                .bind(task_input.session_description.as_deref().unwrap_or(&task_input.scope_description))
                .bind(frequency)
                .bind(task_input.order.unwrap_or(task_order as i32))
                .bind(task_input.estimated_minutes)
                .execute(&*pool)
                .await?;
            }
        }

        Ok(ServiceScopeAreaType::from(area))
    }

    /// Update a scope area
    async fn update_scope_area(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateScopeAreaInput,
    ) -> Result<ServiceScopeAreaType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let area: ServiceScopeArea = sqlx::query_as::<_, ServiceScopeArea>(
            r#"
            UPDATE service_scope_areas
            SET
                name = COALESCE($2, name),
                "order" = COALESCE($3, "order"),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, scope_id, name, "order"
            "#,
        )
        .bind(id)
        .bind(&input.name)
        .bind(input.order)
        .fetch_one(&*pool)
        .await?;

        Ok(ServiceScopeAreaType::from(area))
    }

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

        // Delete tasks first
        sqlx::query("DELETE FROM service_scope_tasks WHERE area_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

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

        Ok(result.rows_affected() > 0)
    }

    // ==================== SCOPE TASK MUTATIONS ====================

    /// Create a new task within an area
    async fn create_scope_task(
        &self,
        ctx: &Context<'_>,
        area_id: Uuid,
        input: CreateScopeTaskInput,
    ) -> Result<ServiceScopeTaskType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get max order for existing tasks
        let max_order: Option<i32> = sqlx::query_scalar(
            r#"SELECT MAX("order") FROM service_scope_tasks WHERE area_id = $1"#,
        )
        .bind(area_id)
        .fetch_one(&*pool)
        .await?;

        let frequency = input
            .frequency
            .map(TaskFrequency::from)
            .unwrap_or(TaskFrequency::Daily);

        let task: ServiceScopeTask = sqlx::query_as::<_, ServiceScopeTask>(
            r#"
            INSERT INTO service_scope_tasks (
                id, created_at, updated_at, area_id, scope_description,
                checklist_description, session_description, frequency, "order", estimated_minutes
            )
            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6, $7)
            RETURNING id, created_at, updated_at, area_id, scope_description,
                checklist_description, session_description, frequency, "order", estimated_minutes
            "#,
        )
        .bind(area_id)
        .bind(&input.scope_description)
        .bind(input.checklist_description.as_deref().unwrap_or(&input.scope_description))
        .bind(input.session_description.as_deref().unwrap_or(&input.scope_description))
        .bind(frequency)
        .bind(input.order.unwrap_or(max_order.unwrap_or(0) + 1))
        .bind(input.estimated_minutes)
        .fetch_one(&*pool)
        .await?;

        Ok(ServiceScopeTaskType::from(task))
    }

    /// Update a scope task
    async fn update_scope_task(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateScopeTaskInput,
    ) -> Result<ServiceScopeTaskType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let task: ServiceScopeTask = sqlx::query_as::<_, ServiceScopeTask>(
            r#"
            UPDATE service_scope_tasks
            SET
                scope_description = COALESCE($2, scope_description),
                checklist_description = COALESCE($3, checklist_description),
                session_description = COALESCE($4, session_description),
                frequency = COALESCE($5, frequency),
                "order" = COALESCE($6, "order"),
                estimated_minutes = COALESCE($7, estimated_minutes),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, area_id, scope_description,
                checklist_description, session_description, frequency, "order", estimated_minutes
            "#,
        )
        .bind(id)
        .bind(&input.scope_description)
        .bind(&input.checklist_description)
        .bind(&input.session_description)
        .bind(input.frequency.map(TaskFrequency::from))
        .bind(input.order)
        .bind(input.estimated_minutes)
        .fetch_one(&*pool)
        .await?;

        Ok(ServiceScopeTaskType::from(task))
    }

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

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

        Ok(result.rows_affected() > 0)
    }

    // ==================== ACCOUNT ADDRESS (LOCATION) MUTATIONS ====================

    /// Update an account address (location)
    async fn update_account_address(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateAccountAddressInput,
    ) -> Result<AccountAddressType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // If setting as primary, unset other addresses for this account
        if input.is_primary == Some(true) {
            let account_id: Uuid = sqlx::query_scalar(
                "SELECT account_id FROM account_addresses WHERE id = $1",
            )
            .bind(id)
            .fetch_one(&*pool)
            .await?;

            sqlx::query(
                r#"
                UPDATE account_addresses
                SET is_primary = false, updated_at = NOW()
                WHERE account_id = $1 AND id != $2 AND is_primary = true
                "#,
            )
            .bind(account_id)
            .bind(id)
            .execute(&*pool)
            .await?;
        }

        // For nullable fields (name, notes), use the value directly (allows clearing to null)
        // For required fields, use COALESCE as a safety net
        let address: AccountAddress = sqlx::query_as::<_, AccountAddress>(
            r#"
            UPDATE account_addresses
            SET
                name = $2,
                street_address = COALESCE($3, street_address),
                city = COALESCE($4, city),
                state = COALESCE($5, state),
                zip_code = COALESCE($6, zip_code),
                notes = $7,
                is_primary = COALESCE($8, is_primary),
                is_active = COALESCE($9, is_active),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, account_id, name, street_address, city, state, zip_code, notes, is_primary, is_active
            "#,
        )
        .bind(id)
        .bind(&input.name)
        .bind(&input.street_address)
        .bind(&input.city)
        .bind(&input.state)
        .bind(&input.zip_code)
        .bind(&input.notes)
        .bind(input.is_primary)
        .bind(input.is_active)
        .fetch_one(&*pool)
        .await?;

        Ok(AccountAddressType::from(address))
    }
}

// Helper conversion from GraphQL enum to model enum
impl From<TaskFrequencyType> for TaskFrequency {
    fn from(freq: TaskFrequencyType) -> Self {
        match freq {
            TaskFrequencyType::Daily => TaskFrequency::Daily,
            TaskFrequencyType::Weekly => TaskFrequency::Weekly,
            TaskFrequencyType::Monthly => TaskFrequency::Monthly,
            TaskFrequencyType::Quarterly => TaskFrequency::Quarterly,
            TaskFrequencyType::Triannual => TaskFrequency::Triannual,
            TaskFrequencyType::Annual => TaskFrequency::Annual,
            TaskFrequencyType::AsNeeded => TaskFrequency::AsNeeded,
        }
    }
}
