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

use crate::auth::UserContext;
use crate::db::Database;
use crate::graphql::types::{
    CreateCustomerAddressInput, CreateCustomerContactInput, CreateCustomerInput,
    CustomerAddressType, CustomerContactType, CustomerType, UpdateCustomerAddressInput,
    UpdateCustomerContactInput, UpdateCustomerInput,
};
use crate::models::{Customer, CustomerAddress, CustomerContact, EntityStatus, EventType};
use crate::services::{EventPublisher, JobQueue};

#[derive(Default)]
pub struct CustomerMutation;

#[Object]
impl CustomerMutation {
    // ==================== CUSTOMER MUTATIONS ====================

    /// Create a new customer
    async fn create_customer(
        &self,
        ctx: &Context<'_>,
        input: CreateCustomerInput,
    ) -> Result<CustomerType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let customer: Customer = sqlx::query_as::<_, Customer>(
            r#"
            INSERT INTO customers (
                id, created_at, updated_at, name, status, start_date, end_date,
                billing_terms, billing_email, wave_customer_id
            )
            VALUES (
                gen_random_uuid(), NOW(), NOW(), $1, $2, $3, $4, $5, $6, $7
            )
            RETURNING id, created_at, updated_at, name, status, start_date, end_date,
                      billing_terms, billing_email, wave_customer_id
            "#,
        )
        .bind(&input.name)
        .bind(input.status.map(EntityStatus::from).unwrap_or(EntityStatus::Active))
        .bind(input.start_date)
        .bind(input.end_date)
        .bind(&input.billing_terms)
        .bind(&input.billing_email)
        .bind(&input.wave_customer_id)
        .fetch_one(&*pool)
        .await?;

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

        Ok(CustomerType::from(customer))
    }

    /// Update a customer
    async fn update_customer(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateCustomerInput,
    ) -> Result<CustomerType> {
        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 customers WHERE id = $1",
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

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

        // Build dynamic update query
        let customer: Customer = sqlx::query_as::<_, Customer>(
            r#"
            UPDATE customers
            SET
                name = COALESCE($2, name),
                status = COALESCE($3, status),
                billing_email = COALESCE($4, billing_email),
                billing_terms = COALESCE($5, billing_terms),
                wave_customer_id = COALESCE($6, wave_customer_id),
                start_date = COALESCE($7, start_date),
                end_date = COALESCE($8, end_date),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, name, status, start_date, end_date,
                      billing_terms, billing_email, wave_customer_id
            "#,
        )
        .bind(id)
        .bind(&input.name)
        .bind(new_status)
        .bind(&input.billing_email)
        .bind(&input.billing_terms)
        .bind(&input.wave_customer_id)
        .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::CustomerStatusChanged,
                        "customer",
                        customer.base.id,
                        Some(("team_profile", user.user_id)),
                        Some(serde_json::json!({
                            "old_status": format!("{:?}", old),
                            "new_status": format!("{:?}", new)
                        })),
                    )
                    .await;
                }
            }
        }

        Ok(CustomerType::from(customer))
    }

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

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

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

        // Unlink accounts (set customer_id to NULL instead of deleting)
        sqlx::query("UPDATE accounts SET customer_id = NULL WHERE customer_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

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

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

        Ok(result.rows_affected() > 0)
    }

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

    /// Create a new contact for a customer
    async fn create_customer_contact(
        &self,
        ctx: &Context<'_>,
        customer_id: Uuid,
        input: CreateCustomerContactInput,
    ) -> Result<CustomerContactType> {
        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 customer_contacts SET is_primary = false WHERE customer_id = $1 AND is_primary = true",
            )
            .bind(customer_id)
            .execute(&*pool)
            .await?;
        }

        let contact: CustomerContact = sqlx::query_as::<_, CustomerContact>(
            r#"
            INSERT INTO customer_contacts (
                id, created_at, updated_at, customer_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, customer_id, first_name, last_name,
                      phone, email, is_active, is_primary, notes
            "#,
        )
        .bind(customer_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(CustomerContactType::from(contact))
    }

    /// Update a customer contact
    async fn update_customer_contact(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateCustomerContactInput,
    ) -> Result<CustomerContactType> {
        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) {
            // First get the customer_id for this contact
            let customer_id: Uuid =
                sqlx::query_scalar("SELECT customer_id FROM customer_contacts WHERE id = $1")
                    .bind(id)
                    .fetch_one(&*pool)
                    .await?;

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

        let contact: CustomerContact = sqlx::query_as::<_, CustomerContact>(
            r#"
            UPDATE customer_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, customer_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(CustomerContactType::from(contact))
    }

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

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

        Ok(result.rows_affected() > 0)
    }

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

    /// Create a new address for a customer
    async fn create_customer_address(
        &self,
        ctx: &Context<'_>,
        customer_id: Uuid,
        input: CreateCustomerAddressInput,
    ) -> Result<CustomerAddressType> {
        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 customer_addresses SET is_primary = false WHERE customer_id = $1 AND is_primary = true",
            )
            .bind(customer_id)
            .execute(&*pool)
            .await?;
        }

        let address: CustomerAddress = sqlx::query_as::<_, CustomerAddress>(
            r#"
            INSERT INTO customer_addresses (
                id, created_at, updated_at, customer_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, customer_id, street_address, city, state,
                      zip_code, is_active, is_primary, name, notes
            "#,
        )
        .bind(customer_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(CustomerAddressType::from(address))
    }

    /// Update a customer address
    async fn update_customer_address(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateCustomerAddressInput,
    ) -> Result<CustomerAddressType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

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

        let address: CustomerAddress = sqlx::query_as::<_, CustomerAddress>(
            r#"
            UPDATE customer_addresses
            SET
                name = COALESCE($2, name),
                street_address = COALESCE($3, street_address),
                city = COALESCE($4, city),
                state = COALESCE($5, state),
                zip_code = COALESCE($6, zip_code),
                notes = COALESCE($7, notes),
                is_primary = COALESCE($8, is_primary),
                is_active = COALESCE($9, is_active),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, customer_id, street_address, city, state,
                      zip_code, is_active, is_primary, name, notes
            "#,
        )
        .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(CustomerAddressType::from(address))
    }

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

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

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