//! Wave Accounting GraphQL mutations
//!
//! Mutations for creating Wave customers and invoices from Nexus data.

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

use crate::db::Database;
use crate::graphql::types::{
    CreateWaveCustomerInput, CreateWaveCustomerResult, CreateWaveInvoiceInput,
    CreateWaveInvoiceResult, CreateWaveProductInput, CustomerType, InvoiceType,
    UpdateWaveCustomerInput, UpdateWaveProductInput, WaveCustomerType, WaveInvoiceType,
    WaveMutationResult, WaveProductType,
};
use crate::models::{Customer, Invoice};
use crate::services::{
    WaveCreateCustomerInput, WaveCreateInvoiceInput, WaveCreateInvoiceItemInput,
    WaveCreateProductInput, WaveService, WaveUpdateCustomerInput, WaveUpdateProductInput,
};

#[derive(Default)]
pub struct WaveMutation;

#[Object]
impl WaveMutation {
    /// Create a Wave customer from a Nexus customer
    ///
    /// Links the Nexus customer to Wave by storing the wave_customer_id.
    async fn create_wave_customer(
        &self,
        ctx: &Context<'_>,
        input: CreateWaveCustomerInput,
    ) -> Result<CreateWaveCustomerResult> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get the Nexus customer
        let customer = sqlx::query_as::<_, Customer>(
            r#"
            SELECT id, created_at, updated_at, name, status, start_date, end_date,
                   billing_terms, billing_email, wave_customer_id
            FROM customers
            WHERE id = $1
            "#,
        )
        .bind(input.customer_id)
        .fetch_optional(&*pool)
        .await?
        .ok_or_else(|| async_graphql::Error::new("Customer not found"))?;

        // Check if already linked
        if customer.wave_customer_id.is_some() {
            return Ok(CreateWaveCustomerResult {
                success: false,
                error: Some("Customer is already linked to Wave".to_string()),
                customer: None,
                nexus_customer: Some(CustomerType::from(customer)),
            });
        }

        // Create Wave customer
        let wave_input = WaveCreateCustomerInput {
            business_id: wave.business_id().to_string(),
            name: customer.name.clone(),
            email: input.email.or(customer.billing_email.clone()),
            address: None, // Could be enhanced to use customer address
            currency: input.currency,
        };

        let wave_customer = match wave.create_customer(wave_input).await {
            Ok(c) => c,
            Err(e) => {
                return Ok(CreateWaveCustomerResult {
                    success: false,
                    error: Some(format!("Failed to create Wave customer: {}", e)),
                    customer: None,
                    nexus_customer: Some(CustomerType::from(customer)),
                });
            }
        };

        // Update Nexus customer with wave_customer_id
        let updated_customer = sqlx::query_as::<_, Customer>(
            r#"
            UPDATE customers
            SET wave_customer_id = $1, updated_at = NOW()
            WHERE id = $2
            RETURNING id, created_at, updated_at, name, status, start_date, end_date,
                      billing_terms, billing_email, wave_customer_id
            "#,
        )
        .bind(&wave_customer.id)
        .bind(input.customer_id)
        .fetch_one(&*pool)
        .await?;

        Ok(CreateWaveCustomerResult {
            success: true,
            error: None,
            customer: Some(WaveCustomerType::from(wave_customer)),
            nexus_customer: Some(CustomerType::from(updated_customer)),
        })
    }

    /// Create a Wave invoice from a Nexus invoice
    ///
    /// Requires:
    /// - Customer must be linked to Wave (wave_customer_id set)
    /// - Invoice items (revenues/projects) must be linked to Wave products (wave_service_id set)
    ///
    /// The amounts from Nexus override the Wave product default prices.
    async fn create_wave_invoice(
        &self,
        ctx: &Context<'_>,
        input: CreateWaveInvoiceInput,
    ) -> Result<CreateWaveInvoiceResult> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get the invoice
        let invoice = sqlx::query_as::<_, Invoice>(
            r#"
            SELECT id, created_at, updated_at, customer_id, start_date, end_date,
                   status, date_paid, payment_type, wave_invoice_id
            FROM invoices
            WHERE id = $1
            "#,
        )
        .bind(input.invoice_id)
        .fetch_optional(&*pool)
        .await?
        .ok_or_else(|| async_graphql::Error::new("Invoice not found"))?;

        // Check if already synced
        if invoice.wave_invoice_id.is_some() {
            return Ok(CreateWaveInvoiceResult {
                success: false,
                error: Some("Invoice is already synced to Wave".to_string()),
                invoice: None,
                nexus_invoice: Some(InvoiceType::from(invoice)),
            });
        }

        // Get customer's wave_customer_id
        let customer = sqlx::query_as::<_, Customer>(
            r#"
            SELECT id, created_at, updated_at, name, status, start_date, end_date,
                   billing_terms, billing_email, wave_customer_id
            FROM customers
            WHERE id = $1
            "#,
        )
        .bind(invoice.customer_id)
        .fetch_one(&*pool)
        .await?;

        let wave_customer_id = match &customer.wave_customer_id {
            Some(id) => id.clone(),
            None => {
                return Ok(CreateWaveInvoiceResult {
                    success: false,
                    error: Some("Customer is not linked to Wave".to_string()),
                    invoice: None,
                    nexus_invoice: Some(InvoiceType::from(invoice)),
                });
            }
        };

        // Get invoice revenues with wave_service_id
        let revenues: Vec<(String, String, rust_decimal::Decimal)> = sqlx::query_as(
            r#"
            SELECT r.wave_service_id, a.name as description, ir.amount
            FROM invoice_revenues ir
            JOIN revenues r ON r.id = ir.revenue_id
            JOIN accounts a ON a.id = r.account_id
            WHERE ir.invoice_id = $1 AND r.wave_service_id IS NOT NULL
            "#,
        )
        .bind(input.invoice_id)
        .fetch_all(&*pool)
        .await?;

        // Get invoice projects with wave_service_id
        let projects: Vec<(String, String, rust_decimal::Decimal)> = sqlx::query_as(
            r#"
            SELECT p.wave_service_id, p.name as description, ip.amount
            FROM invoice_projects ip
            JOIN projects p ON p.id = ip.project_id
            WHERE ip.invoice_id = $1 AND p.wave_service_id IS NOT NULL
            "#,
        )
        .bind(input.invoice_id)
        .fetch_all(&*pool)
        .await?;

        // Build Wave invoice items
        let mut items = Vec::new();

        for (wave_service_id, description, amount) in revenues {
            items.push(WaveCreateInvoiceItemInput {
                product_id: wave_service_id,
                description: Some(description),
                quantity: 1.0,
                unit_price: rust_decimal_to_f64(amount),
            });
        }

        for (wave_service_id, description, amount) in projects {
            items.push(WaveCreateInvoiceItemInput {
                product_id: wave_service_id,
                description: Some(description),
                quantity: 1.0,
                unit_price: rust_decimal_to_f64(amount),
            });
        }

        if items.is_empty() {
            return Ok(CreateWaveInvoiceResult {
                success: false,
                error: Some("No invoice items are linked to Wave products".to_string()),
                invoice: None,
                nexus_invoice: Some(InvoiceType::from(invoice)),
            });
        }

        // Create Wave invoice
        let wave_input = WaveCreateInvoiceInput {
            business_id: wave.business_id().to_string(),
            customer_id: wave_customer_id,
            items,
            invoice_date: input.invoice_date.unwrap_or_else(|| invoice.start_date.to_string()),
            due_date: input.due_date,
            memo: input.memo,
        };

        let wave_invoice = match wave.create_invoice(wave_input).await {
            Ok(inv) => inv,
            Err(e) => {
                return Ok(CreateWaveInvoiceResult {
                    success: false,
                    error: Some(format!("Failed to create Wave invoice: {}", e)),
                    invoice: None,
                    nexus_invoice: Some(InvoiceType::from(invoice)),
                });
            }
        };

        // Update Nexus invoice with wave_invoice_id
        let updated_invoice = sqlx::query_as::<_, Invoice>(
            r#"
            UPDATE invoices
            SET wave_invoice_id = $1, updated_at = NOW()
            WHERE id = $2
            RETURNING id, created_at, updated_at, customer_id, start_date, end_date,
                      status, date_paid, payment_type, wave_invoice_id
            "#,
        )
        .bind(&wave_invoice.id)
        .bind(input.invoice_id)
        .fetch_one(&*pool)
        .await?;

        Ok(CreateWaveInvoiceResult {
            success: true,
            error: None,
            invoice: Some(WaveInvoiceType::from(wave_invoice)),
            nexus_invoice: Some(InvoiceType::from(updated_invoice)),
        })
    }

    /// Link a Nexus customer to an existing Wave customer
    async fn link_customer_to_wave(
        &self,
        ctx: &Context<'_>,
        customer_id: Uuid,
        wave_customer_id: String,
    ) -> Result<CustomerType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let customer = sqlx::query_as::<_, Customer>(
            r#"
            UPDATE customers
            SET wave_customer_id = $1, updated_at = NOW()
            WHERE id = $2
            RETURNING id, created_at, updated_at, name, status, start_date, end_date,
                      billing_terms, billing_email, wave_customer_id
            "#,
        )
        .bind(wave_customer_id)
        .bind(customer_id)
        .fetch_one(&*pool)
        .await?;

        Ok(CustomerType::from(customer))
    }

    /// Unlink a Nexus customer from Wave
    async fn unlink_customer_from_wave(
        &self,
        ctx: &Context<'_>,
        customer_id: Uuid,
    ) -> Result<CustomerType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let customer = sqlx::query_as::<_, Customer>(
            r#"
            UPDATE customers
            SET wave_customer_id = NULL, 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(customer_id)
        .fetch_one(&*pool)
        .await?;

        Ok(CustomerType::from(customer))
    }

    // =========================================================================
    // Wave Invoice Lifecycle Mutations
    // =========================================================================

    /// Approve a Wave invoice (DRAFT -> SAVED)
    ///
    /// WARNING: This is a point of no return. Once approved, the invoice cannot be edited.
    async fn approve_wave_invoice(
        &self,
        ctx: &Context<'_>,
        invoice_id: String,
    ) -> Result<WaveMutationResult<WaveInvoiceType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        match wave.approve_invoice(&invoice_id).await {
            Ok(invoice) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(WaveInvoiceType::from(invoice)),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }

    /// Send a Wave invoice to the customer (SAVED -> SENT)
    ///
    /// This will email the invoice to the customer.
    async fn send_wave_invoice(
        &self,
        ctx: &Context<'_>,
        invoice_id: String,
    ) -> Result<WaveMutationResult<WaveInvoiceType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        match wave.send_invoice(&invoice_id).await {
            Ok(invoice) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(WaveInvoiceType::from(invoice)),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }

    /// Delete a Wave invoice (only DRAFT invoices can be deleted)
    async fn delete_wave_invoice(
        &self,
        ctx: &Context<'_>,
        invoice_id: String,
    ) -> Result<WaveMutationResult<bool>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        match wave.delete_invoice(&invoice_id).await {
            Ok(_) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(true),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }

    // =========================================================================
    // Wave Product Mutations
    // =========================================================================

    /// Create a new product in Wave
    async fn create_wave_product(
        &self,
        ctx: &Context<'_>,
        input: CreateWaveProductInput,
    ) -> Result<WaveMutationResult<WaveProductType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        let wave_input = WaveCreateProductInput {
            business_id: wave.business_id().to_string(),
            name: input.name,
            description: input.description,
            unit_price: input.unit_price,
            is_sold: input.is_sold.unwrap_or(true),
            income_account_id: input.income_account_id,
        };

        match wave.create_product(wave_input).await {
            Ok(product) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(WaveProductType::from(product)),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }

    /// Update an existing product in Wave
    async fn update_wave_product(
        &self,
        ctx: &Context<'_>,
        input: UpdateWaveProductInput,
    ) -> Result<WaveMutationResult<WaveProductType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        let wave_input = WaveUpdateProductInput {
            id: input.id,
            name: input.name,
            description: input.description,
            unit_price: input.unit_price,
            income_account_id: input.income_account_id,
        };

        match wave.update_product(wave_input).await {
            Ok(product) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(WaveProductType::from(product)),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }

    /// Archive a product in Wave (soft delete)
    async fn archive_wave_product(
        &self,
        ctx: &Context<'_>,
        product_id: String,
    ) -> Result<WaveMutationResult<WaveProductType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        match wave.archive_product(&product_id).await {
            Ok(product) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(WaveProductType::from(product)),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }

    // =========================================================================
    // Wave Customer Mutations
    // =========================================================================

    /// Update an existing customer in Wave
    async fn update_wave_customer(
        &self,
        ctx: &Context<'_>,
        input: UpdateWaveCustomerInput,
    ) -> Result<WaveMutationResult<WaveCustomerType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        let wave_input = WaveUpdateCustomerInput {
            id: input.id,
            name: input.name,
            email: input.email,
            address: input.address.map(|a| crate::services::WaveCreateAddressInput {
                address_line1: a.address_line1,
                address_line2: a.address_line2,
                city: a.city,
                province_code: a.province_code,
                postal_code: a.postal_code,
                country_code: a.country_code,
            }),
            currency: input.currency,
        };

        match wave.update_customer(wave_input).await {
            Ok(customer) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(WaveCustomerType::from(customer)),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }

    /// Delete a customer in Wave
    async fn delete_wave_customer(
        &self,
        ctx: &Context<'_>,
        customer_id: String,
    ) -> Result<WaveMutationResult<bool>> {
        let wave = ctx.data::<Arc<WaveService>>()?;

        match wave.delete_customer(&customer_id).await {
            Ok(_) => Ok(WaveMutationResult {
                success: true,
                error: None,
                data: Some(true),
            }),
            Err(e) => Ok(WaveMutationResult {
                success: false,
                error: Some(e.to_string()),
                data: None,
            }),
        }
    }
}

/// Convert Decimal to f64 for Wave API
fn rust_decimal_to_f64(d: rust_decimal::Decimal) -> f64 {
    use std::str::FromStr;
    f64::from_str(&d.to_string()).unwrap_or(0.0)
}
