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

use crate::db::Database;
use crate::graphql::types::{
    CreateProjectScopeTemplateCategoryInput, CreateProjectScopeTemplateInput, CreateProjectScopeTemplateTaskInput,
    ImportProjectScopeTemplateInput, ProjectScopeTemplateCategoryType, ProjectScopeTemplateTaskType, ProjectScopeTemplateType,
    UpdateProjectScopeTemplateCategoryInput, UpdateProjectScopeTemplateInput, UpdateProjectScopeTemplateTaskInput,
};
use crate::models::{ProjectScopeTemplate, ProjectScopeTemplateCategory, ProjectScopeTemplateTask};

#[derive(Default)]
pub struct ProjectScopeTemplateMutation;

#[Object]
impl ProjectScopeTemplateMutation {
    // ==================== PROJECT SCOPE TEMPLATE MUTATIONS ====================

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

        // Create the template
        let template: ProjectScopeTemplate = sqlx::query_as::<_, ProjectScopeTemplate>(
            r#"
            INSERT INTO project_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 categories if provided
        if let Some(categories) = input.categories {
            for (cat_order, cat_input) in categories.into_iter().enumerate() {
                let category: ProjectScopeTemplateCategory = sqlx::query_as::<_, ProjectScopeTemplateCategory>(
                    r#"
                    INSERT INTO project_scope_template_categories (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(&cat_input.name)
                .bind(cat_input.order.unwrap_or(cat_order as i32))
                .fetch_one(&*pool)
                .await?;

                if let Some(tasks) = cat_input.tasks {
                    for (task_order, task_input) in tasks.into_iter().enumerate() {
                        sqlx::query(
                            r#"
                            INSERT INTO project_scope_template_tasks (
                                id, created_at, updated_at, category_id, scope_description,
                                checklist_description, session_description, "order", estimated_minutes
                            )
                            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6)
                            "#,
                        )
                        .bind(category.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(task_input.order.unwrap_or(task_order as i32))
                        .bind(task_input.estimated_minutes)
                        .execute(&*pool)
                        .await?;
                    }
                }
            }
        }

        Ok(ProjectScopeTemplateType::from(template))
    }

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

        let template: ProjectScopeTemplate = sqlx::query_as::<_, ProjectScopeTemplate>(
            r#"
            UPDATE project_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(ProjectScopeTemplateType::from(template))
    }

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

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

        Ok(result.rows_affected() > 0)
    }

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

        // Check if template with same name exists
        let existing: Option<ProjectScopeTemplate> = sqlx::query_as::<_, ProjectScopeTemplate>(
            "SELECT id, created_at, updated_at, name, description, is_active FROM project_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 categories/tasks)
                sqlx::query("DELETE FROM project_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: ProjectScopeTemplate = sqlx::query_as::<_, ProjectScopeTemplate>(
            r#"
            INSERT INTO project_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 categories and tasks
        for cat_input in input.categories {
            let category: ProjectScopeTemplateCategory = sqlx::query_as::<_, ProjectScopeTemplateCategory>(
                r#"
                INSERT INTO project_scope_template_categories (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(&cat_input.name)
            .bind(cat_input.order)
            .fetch_one(&*pool)
            .await?;

            for task_input in cat_input.tasks {
                sqlx::query(
                    r#"
                    INSERT INTO project_scope_template_tasks (
                        id, created_at, updated_at, category_id, scope_description,
                        checklist_description, session_description, "order", estimated_minutes
                    )
                    VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6)
                    "#,
                )
                .bind(category.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(task_input.order)
                .bind(task_input.estimated_minutes)
                .execute(&*pool)
                .await?;
            }
        }

        Ok(ProjectScopeTemplateType::from(template))
    }

    // ==================== PROJECT SCOPE TEMPLATE CATEGORY MUTATIONS ====================

    /// Create a new category in a project scope template
    async fn create_project_scope_template_category(
        &self,
        ctx: &Context<'_>,
        template_id: Uuid,
        input: CreateProjectScopeTemplateCategoryInput,
    ) -> Result<ProjectScopeTemplateCategoryType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

        let category: ProjectScopeTemplateCategory = sqlx::query_as::<_, ProjectScopeTemplateCategory>(
            r#"
            INSERT INTO project_scope_template_categories (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() {
                sqlx::query(
                    r#"
                    INSERT INTO project_scope_template_tasks (
                        id, created_at, updated_at, category_id, scope_description,
                        checklist_description, session_description, "order", estimated_minutes
                    )
                    VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6)
                    "#,
                )
                .bind(category.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(task_input.order.unwrap_or(task_order as i32))
                .bind(task_input.estimated_minutes)
                .execute(&*pool)
                .await?;
            }
        }

        Ok(ProjectScopeTemplateCategoryType::from(category))
    }

    /// Update a project scope template category
    async fn update_project_scope_template_category(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateProjectScopeTemplateCategoryInput,
    ) -> Result<ProjectScopeTemplateCategoryType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let category: ProjectScopeTemplateCategory = sqlx::query_as::<_, ProjectScopeTemplateCategory>(
            r#"
            UPDATE project_scope_template_categories
            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(ProjectScopeTemplateCategoryType::from(category))
    }

    /// Delete a project scope template category (cascades to tasks)
    async fn delete_project_scope_template_category(&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 project_scope_template_categories WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    // ==================== PROJECT SCOPE TEMPLATE TASK MUTATIONS ====================

    /// Create a new task in a project scope template category
    async fn create_project_scope_template_task(
        &self,
        ctx: &Context<'_>,
        category_id: Uuid,
        input: CreateProjectScopeTemplateTaskInput,
    ) -> Result<ProjectScopeTemplateTaskType> {
        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 project_scope_template_tasks WHERE category_id = $1"#,
        )
        .bind(category_id)
        .fetch_one(&*pool)
        .await?;

        let task: ProjectScopeTemplateTask = sqlx::query_as::<_, ProjectScopeTemplateTask>(
            r#"
            INSERT INTO project_scope_template_tasks (
                id, created_at, updated_at, category_id, scope_description,
                checklist_description, session_description, "order", estimated_minutes
            )
            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6)
            RETURNING id, created_at, updated_at, category_id, scope_description,
                checklist_description, session_description, "order", estimated_minutes
            "#,
        )
        .bind(category_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(input.order.unwrap_or(max_order.unwrap_or(0) + 1))
        .bind(input.estimated_minutes)
        .fetch_one(&*pool)
        .await?;

        Ok(ProjectScopeTemplateTaskType::from(task))
    }

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

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

        Ok(ProjectScopeTemplateTaskType::from(task))
    }

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

        Ok(result.rows_affected() > 0)
    }

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

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

        // Get the template
        let template: ProjectScopeTemplate = sqlx::query_as::<_, ProjectScopeTemplate>(
            "SELECT id, created_at, updated_at, name, description, is_active FROM project_scope_templates WHERE id = $1",
        )
        .bind(template_id)
        .fetch_one(&*pool)
        .await?;

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

        // Create the project scope
        let scope_id: Uuid = sqlx::query_scalar(
            r#"
            INSERT INTO project_scopes (
                id, created_at, updated_at, name, project_id, account_id, account_address_id, description, is_active
            )
            VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, NULL, NULL, $3, true)
            RETURNING id
            "#,
        )
        .bind(&template.name)
        .bind(project_id)
        .bind(&template.description)
        .fetch_one(&*pool)
        .await?;

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

        // Copy categories and tasks
        for category in categories {
            let new_category_id: Uuid = sqlx::query_scalar(
                r#"
                INSERT INTO project_scope_categories (id, created_at, updated_at, scope_id, name, "order")
                VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3)
                RETURNING id
                "#,
            )
            .bind(scope_id)
            .bind(&category.name)
            .bind(category.order)
            .fetch_one(&*pool)
            .await?;

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

            // Copy tasks (project tasks don't have frequency)
            for task in tasks {
                sqlx::query(
                    r#"
                    INSERT INTO project_scope_tasks (
                        id, created_at, updated_at, category_id, scope_description,
                        checklist_description, session_description, "order", estimated_minutes
                    )
                    VALUES (gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6)
                    "#,
                )
                .bind(new_category_id)
                .bind(&task.scope_description)
                .bind(&task.checklist_description)
                .bind(&task.session_description)
                .bind(task.order)
                .bind(task.estimated_minutes)
                .execute(&*pool)
                .await?;
            }
        }

        Ok(true)
    }
}
