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

use crate::auth::UserContext;
use crate::db::Database;
use crate::graphql::types::{AccountAddressType, AccountContactType, AccountType, EntityStatusType, RevenueType};
use crate::models::{Account, AccountAddress, AccountContact, EntityStatus, EventType, Revenue};
use crate::services::{EventPublisher, JobQueue};

/// Input for creating an account
#[derive(InputObject)]
pub struct CreateAccountInput {
    pub customer_id: Uuid,
    pub name: String,
    pub status: Option<EntityStatusType>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
}

/// Input for updating an account
#[derive(InputObject)]
pub struct UpdateAccountInput {
    pub name: Option<String>,
    pub status: Option<EntityStatusType>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
}

// ==================== CONTACT INPUT TYPES ====================

/// Input for creating an account contact
#[derive(InputObject)]
pub struct CreateAccountContactInput {
    pub first_name: String,
    pub last_name: String,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub notes: Option<String>,
    pub is_primary: Option<bool>,
}

/// Input for updating an account contact
#[derive(InputObject)]
pub struct UpdateAccountContactInput {
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub notes: Option<String>,
    pub is_primary: Option<bool>,
    pub is_active: Option<bool>,
}

// ==================== ADDRESS INPUT TYPES ====================

/// Input for creating an account address
#[derive(InputObject)]
pub struct CreateAccountAddressInput {
    pub name: Option<String>,
    pub street_address: String,
    pub city: String,
    pub state: String,
    pub zip_code: String,
    pub notes: Option<String>,
    pub is_primary: Option<bool>,
}

// ==================== REVENUE INPUT TYPES ====================

/// Input for creating a revenue
#[derive(InputObject)]
pub struct CreateRevenueInput {
    pub amount: Decimal,
    pub start_date: NaiveDate,
    pub wave_service_id: Option<String>,
}

/// Input for updating a revenue
#[derive(InputObject)]
pub struct UpdateRevenueInput {
    pub amount: Option<Decimal>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
    pub wave_service_id: Option<String>,
}

#[derive(Default)]
pub struct AccountMutation;

#[Object]
impl AccountMutation {
    /// Create a new account
    async fn create_account(
        &self,
        ctx: &Context<'_>,
        input: CreateAccountInput,
    ) -> Result<AccountType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let status = input.status.map(EntityStatus::from).unwrap_or(EntityStatus::Active);

        let account: Account = sqlx::query_as::<_, Account>(
            r#"
            INSERT INTO accounts (
                id, created_at, updated_at, customer_id, name, status, start_date, end_date
            )
            VALUES (
                gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5
            )
            RETURNING id, created_at, updated_at, customer_id, name, status, start_date, end_date
            "#,
        )
        .bind(input.customer_id)
        .bind(&input.name)
        .bind(status)
        .bind(input.start_date)
        .bind(input.end_date)
        .fetch_one(&*pool)
        .await?;

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

        Ok(AccountType::from(account))
    }

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

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

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

        let account: Account = sqlx::query_as::<_, Account>(
            r#"
            UPDATE accounts
            SET
                name = COALESCE($2, name),
                status = COALESCE($3, status),
                start_date = COALESCE($4, start_date),
                end_date = COALESCE($5, end_date),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, customer_id, name, status, start_date, end_date
            "#,
        )
        .bind(id)
        .bind(&input.name)
        .bind(new_status)
        .bind(input.start_date)
        .bind(input.end_date)
        .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::AccountStatusChanged,
                        "account",
                        account.base.id,
                        Some(("team_profile", user.user_id)),
                        Some(serde_json::json!({
                            "old_status": format!("{:?}", old),
                            "new_status": format!("{:?}", new)
                        })),
                    )
                    .await;
                }
            }
        }

        Ok(AccountType::from(account))
    }

    /// Delete an account (cascades to addresses, contacts, revenues, services)
    async fn delete_account(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Delete services first (they reference account_id)
        sqlx::query("DELETE FROM services WHERE account_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

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

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

        // Delete addresses (need to delete child records first)
        // Get all address IDs for this account
        let address_ids: Vec<Uuid> = sqlx::query_scalar(
            "SELECT id FROM account_addresses WHERE account_id = $1",
        )
        .bind(id)
        .fetch_all(&*pool)
        .await?;

        for address_id in address_ids {
            // Delete labor rates
            sqlx::query("DELETE FROM labor WHERE account_address_id = $1")
                .bind(address_id)
                .execute(&*pool)
                .await?;

            // Delete schedules
            sqlx::query("DELETE FROM schedules WHERE account_address_id = $1")
                .bind(address_id)
                .execute(&*pool)
                .await?;

            // Delete service scope tasks and areas first
            let scope_ids: Vec<Uuid> = sqlx::query_scalar(
                "SELECT id FROM service_scopes WHERE account_address_id = $1",
            )
            .bind(address_id)
            .fetch_all(&*pool)
            .await?;

            for scope_id in scope_ids {
                let area_ids: Vec<Uuid> = sqlx::query_scalar(
                    "SELECT id FROM service_scope_areas WHERE scope_id = $1",
                )
                .bind(scope_id)
                .fetch_all(&*pool)
                .await?;

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

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

            sqlx::query("DELETE FROM service_scopes WHERE account_address_id = $1")
                .bind(address_id)
                .execute(&*pool)
                .await?;
        }

        // Now delete addresses
        sqlx::query("DELETE FROM account_addresses WHERE account_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        // Finally delete the account
        let result = sqlx::query("DELETE FROM accounts WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        // Publish AccountDeleted 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::AccountDeleted,
                    "account",
                    id,
                    Some(("team_profile", user.user_id)),
                    None,
                )
                .await;
            }
        }

        Ok(result.rows_affected() > 0)
    }

    // ==================== ACCOUNT CONTACT MUTATIONS ====================

    /// Create a new contact for an account
    async fn create_account_contact(
        &self,
        ctx: &Context<'_>,
        account_id: Uuid,
        input: CreateAccountContactInput,
    ) -> Result<AccountContactType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // If this contact is primary, unset other primary contacts
        if input.is_primary.unwrap_or(false) {
            sqlx::query(
                "UPDATE account_contacts SET is_primary = false WHERE account_id = $1 AND is_primary = true",
            )
            .bind(account_id)
            .execute(&*pool)
            .await?;
        }

        let contact: AccountContact = sqlx::query_as::<_, AccountContact>(
            r#"
            INSERT INTO account_contacts (
                id, created_at, updated_at, account_id, first_name, last_name,
                phone, email, is_active, is_primary, notes
            )
            VALUES (
                gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, true, $6, $7
            )
            RETURNING id, created_at, updated_at, account_id, first_name, last_name,
                      phone, email, is_active, is_primary, notes
            "#,
        )
        .bind(account_id)
        .bind(&input.first_name)
        .bind(&input.last_name)
        .bind(&input.phone)
        .bind(&input.email)
        .bind(input.is_primary.unwrap_or(false))
        .bind(&input.notes)
        .fetch_one(&*pool)
        .await?;

        Ok(AccountContactType::from(contact))
    }

    /// Update an account contact
    async fn update_account_contact(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateAccountContactInput,
    ) -> Result<AccountContactType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

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

        let contact: AccountContact = sqlx::query_as::<_, AccountContact>(
            r#"
            UPDATE account_contacts
            SET
                first_name = COALESCE($2, first_name),
                last_name = COALESCE($3, last_name),
                email = COALESCE($4, email),
                phone = COALESCE($5, phone),
                notes = COALESCE($6, notes),
                is_primary = COALESCE($7, is_primary),
                is_active = COALESCE($8, is_active),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, account_id, first_name, last_name,
                      phone, email, is_active, is_primary, notes
            "#,
        )
        .bind(id)
        .bind(&input.first_name)
        .bind(&input.last_name)
        .bind(&input.email)
        .bind(&input.phone)
        .bind(&input.notes)
        .bind(input.is_primary)
        .bind(input.is_active)
        .fetch_one(&*pool)
        .await?;

        Ok(AccountContactType::from(contact))
    }

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

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

        Ok(result.rows_affected() > 0)
    }

    // ==================== ACCOUNT ADDRESS MUTATIONS ====================

    /// Create a new address for an account
    async fn create_account_address(
        &self,
        ctx: &Context<'_>,
        account_id: Uuid,
        input: CreateAccountAddressInput,
    ) -> Result<AccountAddressType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // If this address is primary, unset other primary addresses
        if input.is_primary.unwrap_or(false) {
            sqlx::query(
                "UPDATE account_addresses SET is_primary = false WHERE account_id = $1 AND is_primary = true",
            )
            .bind(account_id)
            .execute(&*pool)
            .await?;
        }

        let address: AccountAddress = sqlx::query_as::<_, AccountAddress>(
            r#"
            INSERT INTO account_addresses (
                id, created_at, updated_at, account_id, street_address, city, state,
                zip_code, is_active, is_primary, name, notes
            )
            VALUES (
                gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, true, $6, $7, $8
            )
            RETURNING id, created_at, updated_at, account_id, street_address, city, state,
                      zip_code, is_active, is_primary, name, notes
            "#,
        )
        .bind(account_id)
        .bind(&input.street_address)
        .bind(&input.city)
        .bind(&input.state)
        .bind(&input.zip_code)
        .bind(input.is_primary.unwrap_or(false))
        .bind(&input.name)
        .bind(&input.notes)
        .fetch_one(&*pool)
        .await?;

        Ok(AccountAddressType::from(address))
    }

    /// Delete an account address (cascades to labor, schedules, scopes)
    async fn delete_account_address(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

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

        // Delete service scope tasks and areas first
        let scope_ids: Vec<Uuid> = sqlx::query_scalar(
            "SELECT id FROM service_scopes WHERE account_address_id = $1",
        )
        .bind(id)
        .fetch_all(&*pool)
        .await?;

        for scope_id in scope_ids {
            let area_ids: Vec<Uuid> = sqlx::query_scalar(
                "SELECT id FROM service_scope_areas WHERE scope_id = $1",
            )
            .bind(scope_id)
            .fetch_all(&*pool)
            .await?;

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

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

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

        // Finally delete the address
        let result = sqlx::query("DELETE FROM account_addresses WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    // ==================== REVENUE MUTATIONS ====================

    /// Create a new revenue for an account (deactivates existing active revenues)
    async fn create_revenue(
        &self,
        ctx: &Context<'_>,
        account_id: Uuid,
        input: CreateRevenueInput,
    ) -> Result<RevenueType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

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

        Ok(RevenueType::from(revenue))
    }

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

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

        Ok(RevenueType::from(revenue))
    }

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

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

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