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

use crate::db::Database;
use crate::graphql::types::{
    CreateServiceScopeTemplateAreaInput, CreateServiceScopeTemplateInput, CreateServiceScopeTemplateTaskInput,
    ImportServiceScopeTemplateInput, ServiceScopeTemplateAreaType, ServiceScopeTemplateTaskType, ServiceScopeTemplateType,
    UpdateServiceScopeTemplateAreaInput, UpdateServiceScopeTemplateInput, UpdateServiceScopeTemplateTaskInput,
};
use crate::models::{ServiceScopeTemplate, ServiceScopeTemplateArea, ServiceScopeTemplateTask, TaskFrequency};

#[derive(Default)]
pub struct ServiceScopeTemplateMutation;

#[Object]
impl ServiceScopeTemplateMutation {
    // ==================== SERVICE SCOPE TEMPLATE MUTATIONS ====================

    /// Create a new service scope template
    async fn create_service_scope_template(
        &self,
        ctx: &Context<'_>,
        input: CreateServiceScopeTemplateInput,
    ) -> Result<ServiceScopeTemplateType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

        // Create areas if provided
        if let Some(areas) = input.areas {
            for (area_order, area_input) in areas.into_iter().enumerate() {
                let area: ServiceScopeTemplateArea = sqlx::query_as::<_, ServiceScopeTemplateArea>(
                    r#"
                    INSERT INTO service_scope_template_areas (id, created_at, updated_at, template_id, name, "order")
                    VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3)
                    RETURNING id, created_at, updated_at, template_id, name, "order"
                    "#,
                )
                .bind(template.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_template_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(ServiceScopeTemplateType::from(template))
    }

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

        let template: ServiceScopeTemplate = sqlx::query_as::<_, ServiceScopeTemplate>(
            r#"
            UPDATE service_scope_templates
            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, description, is_active
            "#,
        )
        .bind(id)
        .bind(&input.name)
        .bind(&input.description)
        .bind(input.is_active)
        .fetch_one(&*pool)
        .await?;

        Ok(ServiceScopeTemplateType::from(template))
    }

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

        // CASCADE handles areas and tasks
        let result = sqlx::query("DELETE FROM service_scope_templates WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    /// Import a service scope template from JSON structure
    async fn import_service_scope_template(
        &self,
        ctx: &Context<'_>,
        input: ImportServiceScopeTemplateInput,
    ) -> Result<ServiceScopeTemplateType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Check if template with same name exists
        let existing: Option<ServiceScopeTemplate> = sqlx::query_as::<_, ServiceScopeTemplate>(
            "SELECT id, created_at, updated_at, name, description, is_active FROM service_scope_templates WHERE name = $1",
        )
        .bind(&input.name)
        .fetch_optional(&*pool)
        .await?;

        if let Some(existing_template) = existing {
            if input.replace.unwrap_or(false) {
                // Delete existing template (cascade deletes areas/tasks)
                sqlx::query("DELETE FROM service_scope_templates WHERE id = $1")
                    .bind(existing_template.base.id)
                    .execute(&*pool)
                    .await?;
            } else {
                return Err(async_graphql::Error::new(format!(
                    "Template with name '{}' already exists. Use replace: true to overwrite.",
                    input.name
                )));
            }
        }

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

        // Create areas and tasks
        for area_input in input.areas {
            let area: ServiceScopeTemplateArea = sqlx::query_as::<_, ServiceScopeTemplateArea>(
                r#"
                INSERT INTO service_scope_template_areas (id, created_at, updated_at, template_id, name, "order")
                VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3)
                RETURNING id, created_at, updated_at, template_id, name, "order"
                "#,
            )
            .bind(template.base.id)
            .bind(&area_input.name)
            .bind(area_input.order)
            .fetch_one(&*pool)
            .await?;

            for task_input in area_input.tasks {
                let frequency = task_input
                    .frequency
                    .map(TaskFrequency::from)
                    .unwrap_or(TaskFrequency::Daily);

                sqlx::query(
                    r#"
                    INSERT INTO service_scope_template_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)
                .bind(task_input.estimated_minutes)
                .execute(&*pool)
                .await?;
            }
        }

        Ok(ServiceScopeTemplateType::from(template))
    }

    // ==================== SERVICE SCOPE TEMPLATE AREA MUTATIONS ====================

    /// Create a new area in a service scope template
    async fn create_service_scope_template_area(
        &self,
        ctx: &Context<'_>,
        template_id: Uuid,
        input: CreateServiceScopeTemplateAreaInput,
    ) -> Result<ServiceScopeTemplateAreaType> {
        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_template_areas WHERE template_id = $1"#,
        )
        .bind(template_id)
        .fetch_one(&*pool)
        .await?;

        let area: ServiceScopeTemplateArea = sqlx::query_as::<_, ServiceScopeTemplateArea>(
            r#"
            INSERT INTO service_scope_template_areas (id, created_at, updated_at, template_id, name, "order")
            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3)
            RETURNING id, created_at, updated_at, template_id, name, "order"
            "#,
        )
        .bind(template_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_template_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(ServiceScopeTemplateAreaType::from(area))
    }

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

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

        Ok(ServiceScopeTemplateAreaType::from(area))
    }

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

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

        Ok(result.rows_affected() > 0)
    }

    // ==================== SERVICE SCOPE TEMPLATE TASK MUTATIONS ====================

    /// Create a new task in a service scope template area
    async fn create_service_scope_template_task(
        &self,
        ctx: &Context<'_>,
        area_id: Uuid,
        input: CreateServiceScopeTemplateTaskInput,
    ) -> Result<ServiceScopeTemplateTaskType> {
        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_template_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: ServiceScopeTemplateTask = sqlx::query_as::<_, ServiceScopeTemplateTask>(
            r#"
            INSERT INTO service_scope_template_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(ServiceScopeTemplateTaskType::from(task))
    }

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

        let task: ServiceScopeTemplateTask = sqlx::query_as::<_, ServiceScopeTemplateTask>(
            r#"
            UPDATE service_scope_template_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(ServiceScopeTemplateTaskType::from(task))
    }

    /// Delete a service scope template task
    async fn delete_service_scope_template_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_template_tasks WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    // ==================== TEMPLATE INSTANTIATION ====================

    /// Create a service scope from a service scope template
    async fn create_service_scope_from_template(
        &self,
        ctx: &Context<'_>,
        template_id: Uuid,
        account_id: Uuid,
        account_address_id: Uuid,
    ) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get the template
        let template: ServiceScopeTemplate = sqlx::query_as::<_, ServiceScopeTemplate>(
            "SELECT id, created_at, updated_at, name, description, is_active FROM service_scope_templates WHERE id = $1",
        )
        .bind(template_id)
        .fetch_one(&*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 service scope
        let scope_id: Uuid = sqlx::query_scalar(
            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
            "#,
        )
        .bind(&template.name)
        .bind(account_id)
        .bind(account_address_id)
        .bind(&template.description)
        .fetch_one(&*pool)
        .await?;

        // Get template areas
        let areas: Vec<ServiceScopeTemplateArea> = sqlx::query_as::<_, ServiceScopeTemplateArea>(
            r#"
            SELECT id, created_at, updated_at, template_id, name, "order"
            FROM service_scope_template_areas
            WHERE template_id = $1
            ORDER BY "order" ASC
            "#,
        )
        .bind(template_id)
        .fetch_all(&*pool)
        .await?;

        // Copy areas and tasks
        for area in areas {
            let new_area_id: Uuid = sqlx::query_scalar(
                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
                "#,
            )
            .bind(scope_id)
            .bind(&area.name)
            .bind(area.order)
            .fetch_one(&*pool)
            .await?;

            // Get tasks for this area
            let tasks: Vec<ServiceScopeTemplateTask> = sqlx::query_as::<_, ServiceScopeTemplateTask>(
                r#"
                SELECT id, created_at, updated_at, area_id, scope_description,
                       checklist_description, session_description, frequency, "order", estimated_minutes
                FROM service_scope_template_tasks
                WHERE area_id = $1
                ORDER BY "order" ASC
                "#,
            )
            .bind(area.base.id)
            .fetch_all(&*pool)
            .await?;

            // Copy tasks
            for task in tasks {
                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(new_area_id)
                .bind(&task.scope_description)
                .bind(&task.checklist_description)
                .bind(&task.session_description)
                .bind(&task.frequency)
                .bind(task.order)
                .bind(task.estimated_minutes)
                .execute(&*pool)
                .await?;
            }
        }

        Ok(true)
    }
}
