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

use crate::db::Database;
use crate::graphql::types::{
    EligibleInvoiceProjectType, EligibleRevenueType, InvoiceConnection, InvoiceFilterInput,
    InvoiceType, PaginationInput,
};
use crate::models::{Invoice, InvoiceStatus};

#[derive(Default)]
pub struct InvoiceQuery;

#[Object]
impl InvoiceQuery {
    /// Get paginated invoices with optional filtering
    async fn invoices(
        &self,
        ctx: &Context<'_>,
        filter: Option<InvoiceFilterInput>,
        pagination: Option<PaginationInput>,
    ) -> Result<InvoiceConnection> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let filter = filter.unwrap_or_default();
        let pagination = pagination.unwrap_or_default();
        let offset = pagination.offset();
        let limit = pagination.limit();

        let status: Option<InvoiceStatus> = filter.status.map(InvoiceStatus::from);

        // Get total count
        let total_count: i64 = sqlx::query_scalar(
            r#"
            SELECT COUNT(*)
            FROM invoices
            WHERE ($1::uuid IS NULL OR customer_id = $1)
              AND ($2::invoice_status IS NULL OR status = $2)
              AND ($3::date IS NULL OR (start_date <= $3 AND end_date >= $3))
              AND ($4::date IS NULL OR start_date >= $4)
              AND ($5::date IS NULL OR end_date <= $5)
            "#,
        )
        .bind(filter.customer_id)
        .bind(status)
        .bind(filter.date)
        .bind(filter.start_date_from)
        .bind(filter.end_date_to)
        .fetch_one(&*pool)
        .await?;

        // Get paginated invoices
        let invoices: Vec<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 ($1::uuid IS NULL OR customer_id = $1)
              AND ($2::invoice_status IS NULL OR status = $2)
              AND ($3::date IS NULL OR (start_date <= $3 AND end_date >= $3))
              AND ($4::date IS NULL OR start_date >= $4)
              AND ($5::date IS NULL OR end_date <= $5)
            ORDER BY start_date DESC, id ASC
            LIMIT $6 OFFSET $7
            "#,
        )
        .bind(filter.customer_id)
        .bind(status)
        .bind(filter.date)
        .bind(filter.start_date_from)
        .bind(filter.end_date_to)
        .bind(limit)
        .bind(offset)
        .fetch_all(&*pool)
        .await?;

        let items: Vec<InvoiceType> = invoices.into_iter().map(InvoiceType::from).collect();
        let has_next_page = (offset + limit) < total_count;

        Ok(InvoiceConnection {
            items,
            total_count: total_count as i32,
            has_next_page,
        })
    }

    /// Get a single invoice by ID
    async fn invoice(&self, ctx: &Context<'_>, id: Uuid) -> Result<Option<InvoiceType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        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(id)
        .fetch_optional(&*pool)
        .await?;

        Ok(invoice.map(InvoiceType::from))
    }

    /// Get eligible revenues for a customer that can be added to an invoice
    /// Returns revenues from accounts belonging to the customer that are not already on any invoice
    async fn eligible_revenues_for_invoice(
        &self,
        ctx: &Context<'_>,
        customer_id: Uuid,
        date_from: NaiveDate,
        date_to: NaiveDate,
    ) -> Result<Vec<EligibleRevenueType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Find revenues that:
        // 1. Belong to accounts under this customer
        // 2. Are active during the date range
        // 3. Are not already on an invoice with an overlapping period
        let rows: Vec<EligibleRevenueRow> = sqlx::query_as::<_, EligibleRevenueRow>(
            r#"
            SELECT
                r.id as revenue_id,
                r.account_id,
                a.name as account_name,
                r.amount
            FROM revenues r
            JOIN accounts a ON a.id = r.account_id
            WHERE a.customer_id = $1
              AND r.start_date <= $3
              AND (r.end_date IS NULL OR r.end_date >= $2)
              AND NOT EXISTS (
                  SELECT 1 FROM invoice_revenues ir
                  JOIN invoices i ON i.id = ir.invoice_id
                  WHERE ir.revenue_id = r.id
                    AND i.start_date <= $3
                    AND i.end_date >= $2
              )
            ORDER BY a.name, r.start_date
            "#,
        )
        .bind(customer_id)
        .bind(date_from)
        .bind(date_to)
        .fetch_all(&*pool)
        .await?;

        let items = rows
            .into_iter()
            .map(|r| EligibleRevenueType {
                revenue_id: r.revenue_id,
                account_id: r.account_id,
                account_name: r.account_name,
                amount: r.amount,
            })
            .collect();

        Ok(items)
    }

    /// Get eligible projects for a customer that can be added to an invoice
    /// Returns completed projects for the customer that are not already on any invoice
    async fn eligible_projects_for_invoice(
        &self,
        ctx: &Context<'_>,
        customer_id: Uuid,
        date_from: NaiveDate,
        date_to: NaiveDate,
    ) -> Result<Vec<EligibleInvoiceProjectType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Find projects that:
        // 1. Belong to this customer
        // 2. Are completed
        // 3. Are within the date range
        // 4. Are not already on any invoice
        let rows: Vec<EligibleProjectRow> = sqlx::query_as::<_, EligibleProjectRow>(
            r#"
            SELECT
                p.id as project_id,
                p.name,
                p.date,
                COALESCE(p.amount, 0) as amount
            FROM projects p
            WHERE p.customer_id = $1
              AND p.status = 'COMPLETED'
              AND p.date >= $2
              AND p.date <= $3
              AND NOT EXISTS (
                  SELECT 1 FROM invoice_projects ip WHERE ip.project_id = p.id
              )
            ORDER BY p.date, p.name
            "#,
        )
        .bind(customer_id)
        .bind(date_from)
        .bind(date_to)
        .fetch_all(&*pool)
        .await?;

        let items = rows
            .into_iter()
            .map(|r| EligibleInvoiceProjectType {
                project_id: r.project_id,
                name: r.name,
                date: r.date,
                amount: r.amount,
            })
            .collect();

        Ok(items)
    }
}

/// Helper struct for eligible revenue query
#[derive(sqlx::FromRow)]
struct EligibleRevenueRow {
    revenue_id: Uuid,
    account_id: Uuid,
    account_name: String,
    amount: Decimal,
}

/// Helper struct for eligible project query
#[derive(sqlx::FromRow)]
struct EligibleProjectRow {
    project_id: Uuid,
    name: String,
    date: NaiveDate,
    amount: Decimal,
}
