//! Wave Accounting GraphQL queries
//!
//! Queries for fetching Wave products, customers, and invoices.

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

use crate::graphql::types::{
    WaveCustomerType, WaveInvoiceConnectionType, WaveInvoiceListItemType, WaveInvoiceReadiness,
    WaveInvoiceType, WavePageInfoType, WaveProductType,
};
use crate::services::WaveService;

#[derive(Default)]
pub struct WaveQuery;

#[Object]
impl WaveQuery {
    /// List all products from Wave
    async fn wave_products(&self, ctx: &Context<'_>) -> Result<Vec<WaveProductType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let products = wave.list_products().await?;
        Ok(products.into_iter().map(WaveProductType::from).collect())
    }

    /// List all customers from Wave
    async fn wave_customers(&self, ctx: &Context<'_>) -> Result<Vec<WaveCustomerType>> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let customers = wave.list_customers().await?;
        Ok(customers.into_iter().map(WaveCustomerType::from).collect())
    }

    /// Get a single invoice from Wave by ID
    async fn wave_invoice(&self, ctx: &Context<'_>, invoice_id: String) -> Result<WaveInvoiceType> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let invoice = wave.get_invoice(&invoice_id).await?;
        Ok(WaveInvoiceType::from(invoice))
    }

    /// List invoices from Wave with pagination
    async fn wave_invoices(
        &self,
        ctx: &Context<'_>,
        #[graphql(default = 1)] page: i32,
        #[graphql(default = 20)] page_size: i32,
    ) -> Result<WaveInvoiceConnectionType> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let connection = wave.list_invoices(page, page_size).await?;
        Ok(WaveInvoiceConnectionType {
            page_info: WavePageInfoType {
                current_page: connection.page_info.current_page,
                total_pages: connection.page_info.total_pages,
                total_count: connection.page_info.total_count,
            },
            invoices: connection
                .invoices
                .into_iter()
                .map(WaveInvoiceListItemType::from)
                .collect(),
        })
    }

    /// Get a single customer from Wave by ID
    async fn wave_customer(
        &self,
        ctx: &Context<'_>,
        customer_id: String,
    ) -> Result<WaveCustomerType> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let customer = wave.get_customer(&customer_id).await?;
        Ok(WaveCustomerType::from(customer))
    }

    /// Get a single product from Wave by ID
    async fn wave_product(&self, ctx: &Context<'_>, product_id: String) -> Result<WaveProductType> {
        let wave = ctx.data::<Arc<WaveService>>()?;
        let product = wave.get_product(&product_id).await?;
        Ok(WaveProductType::from(product))
    }

    /// Check if a Nexus invoice is ready to be synced to Wave
    async fn wave_invoice_readiness(
        &self,
        ctx: &Context<'_>,
        invoice_id: Uuid,
    ) -> Result<WaveInvoiceReadiness> {
        let db = ctx.data::<crate::db::Database>()?;
        let pool = db.pool().await;

        // Get the invoice
        let invoice = sqlx::query_as::<_, crate::models::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(invoice_id)
        .fetch_optional(&*pool)
        .await?
        .ok_or_else(|| async_graphql::Error::new("Invoice not found"))?;

        let mut issues = Vec::new();

        // Check if already synced
        if invoice.wave_invoice_id.is_some() {
            issues.push("Invoice is already synced to Wave".to_string());
        }

        // Get customer's wave_customer_id
        let customer = sqlx::query_as::<_, crate::models::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?;

        // Empty string '' means unlinked (backend uses '' to clear fields, not NULL)
        let wave_customer_id = customer.wave_customer_id.clone();
        let has_wave_customer = wave_customer_id.as_ref().map(|id| !id.is_empty()).unwrap_or(false);
        if !has_wave_customer {
            issues.push("Customer is not linked to Wave".to_string());
        }

        // Count revenues with and without wave_service_id
        // Use COALESCE to handle NULL as unlinked (same as empty string)
        let revenue_stats: (i64, i64) = sqlx::query_as(
            r#"
            SELECT
                COUNT(*) FILTER (WHERE COALESCE(r.wave_service_id, '') != '') as linked,
                COUNT(*) FILTER (WHERE COALESCE(r.wave_service_id, '') = '') as unlinked
            FROM invoice_revenues ir
            JOIN revenues r ON r.id = ir.revenue_id
            WHERE ir.invoice_id = $1
            "#,
        )
        .bind(invoice_id)
        .fetch_one(&*pool)
        .await?;

        // Count projects with and without wave_service_id
        // Use COALESCE to handle NULL as unlinked (same as empty string)
        let project_stats: (i64, i64) = sqlx::query_as(
            r#"
            SELECT
                COUNT(*) FILTER (WHERE COALESCE(p.wave_service_id, '') != '') as linked,
                COUNT(*) FILTER (WHERE COALESCE(p.wave_service_id, '') = '') as unlinked
            FROM invoice_projects ip
            JOIN projects p ON p.id = ip.project_id
            WHERE ip.invoice_id = $1
            "#,
        )
        .bind(invoice_id)
        .fetch_one(&*pool)
        .await?;

        let ready_count = (revenue_stats.0 + project_stats.0) as i32;
        let unlinked_count = (revenue_stats.1 + project_stats.1) as i32;

        // Report unlinked revenues separately
        if revenue_stats.1 > 0 {
            issues.push(format!(
                "{} revenue(s) not linked to a Wave product",
                revenue_stats.1
            ));
        }

        // Report unlinked projects separately
        if project_stats.1 > 0 {
            issues.push(format!(
                "{} project(s) not linked to a Wave product",
                project_stats.1
            ));
        }

        if ready_count == 0 && unlinked_count == 0 {
            issues.push("Invoice has no items to sync".to_string());
        }

        // Get total amount
        let total: rust_decimal::Decimal = sqlx::query_scalar(
            r#"
            SELECT COALESCE(
                (SELECT SUM(amount) FROM invoice_revenues WHERE invoice_id = $1),
                0
            ) + COALESCE(
                (SELECT SUM(amount) FROM invoice_projects WHERE invoice_id = $1),
                0
            )
            "#,
        )
        .bind(invoice_id)
        .fetch_one(&*pool)
        .await?;

        Ok(WaveInvoiceReadiness {
            ready: issues.is_empty(),
            issues,
            wave_customer_id,
            ready_item_count: ready_count,
            missing_wave_link_count: unlinked_count,
            total_amount: total,
        })
    }
}
