use std::sync::Arc;

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

use crate::db::Database;
use crate::models::{Account, Customer, Invoice, InvoiceProject, InvoiceRevenue, InvoiceStatus, Project, Revenue};
use crate::services::wave::WaveService;

use super::{AccountType, CustomerType, ProjectType, RevenueType};

/// GraphQL enum for invoice status
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum InvoiceStatusType {
    Draft,
    Sent,
    Paid,
    Overdue,
    Cancelled,
}

impl From<InvoiceStatus> for InvoiceStatusType {
    fn from(status: InvoiceStatus) -> Self {
        match status {
            InvoiceStatus::Draft => InvoiceStatusType::Draft,
            InvoiceStatus::Sent => InvoiceStatusType::Sent,
            InvoiceStatus::Paid => InvoiceStatusType::Paid,
            InvoiceStatus::Overdue => InvoiceStatusType::Overdue,
            InvoiceStatus::Cancelled => InvoiceStatusType::Cancelled,
        }
    }
}

impl From<InvoiceStatusType> for InvoiceStatus {
    fn from(status: InvoiceStatusType) -> Self {
        match status {
            InvoiceStatusType::Draft => InvoiceStatus::Draft,
            InvoiceStatusType::Sent => InvoiceStatus::Sent,
            InvoiceStatusType::Paid => InvoiceStatus::Paid,
            InvoiceStatusType::Overdue => InvoiceStatus::Overdue,
            InvoiceStatusType::Cancelled => InvoiceStatus::Cancelled,
        }
    }
}

/// Invoice GraphQL type - customer billing invoice
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct InvoiceType {
    pub id: Uuid,
    pub customer_id: Uuid,
    pub start_date: NaiveDate,
    pub end_date: NaiveDate,
    pub status: InvoiceStatusType,
    pub date_paid: Option<NaiveDate>,
    pub wave_invoice_id: Option<String>,
}

#[ComplexObject]
impl InvoiceType {
    /// Get the customer this invoice belongs to
    async fn customer(&self, ctx: &Context<'_>) -> Result<Option<CustomerType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

        Ok(customer.map(CustomerType::from))
    }

    /// Get all revenues in this invoice with their amounts
    async fn revenues(&self, ctx: &Context<'_>) -> Result<Vec<InvoiceRevenueType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let revenues: Vec<InvoiceRevenue> = sqlx::query_as::<_, InvoiceRevenue>(
            r#"
            SELECT id, invoice_id, revenue_id, amount, created_at
            FROM invoice_revenues
            WHERE invoice_id = $1
            ORDER BY created_at
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(revenues.into_iter().map(InvoiceRevenueType::from).collect())
    }

    /// Get all projects in this invoice with their amounts
    async fn projects(&self, ctx: &Context<'_>) -> Result<Vec<InvoiceProjectType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let projects: Vec<InvoiceProject> = sqlx::query_as::<_, InvoiceProject>(
            r#"
            SELECT id, invoice_id, project_id, amount, created_at
            FROM invoice_projects
            WHERE invoice_id = $1
            ORDER BY created_at
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(projects.into_iter().map(InvoiceProjectType::from).collect())
    }

    /// Total amount from all revenues in this invoice
    async fn revenues_total(&self, ctx: &Context<'_>) -> Result<Decimal> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let total: Option<Decimal> = sqlx::query_scalar(
            r#"
            SELECT COALESCE(SUM(amount), 0)
            FROM invoice_revenues
            WHERE invoice_id = $1
            "#,
        )
        .bind(self.id)
        .fetch_one(&*pool)
        .await?;

        Ok(total.unwrap_or_default())
    }

    /// Total amount from all projects in this invoice
    async fn projects_total(&self, ctx: &Context<'_>) -> Result<Decimal> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let total: Option<Decimal> = sqlx::query_scalar(
            r#"
            SELECT COALESCE(SUM(amount), 0)
            FROM invoice_projects
            WHERE invoice_id = $1
            "#,
        )
        .bind(self.id)
        .fetch_one(&*pool)
        .await?;

        Ok(total.unwrap_or_default())
    }

    /// Grand total amount (revenues + projects)
    async fn total_amount(&self, ctx: &Context<'_>) -> Result<Decimal> {
        let revenues = self.revenues_total(ctx).await?;
        let projects = self.projects_total(ctx).await?;
        Ok(revenues + projects)
    }

    /// Count of revenues in this invoice
    async fn revenue_count(&self, ctx: &Context<'_>) -> Result<i32> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let count: i64 = sqlx::query_scalar(
            r#"SELECT COUNT(*) FROM invoice_revenues WHERE invoice_id = $1"#,
        )
        .bind(self.id)
        .fetch_one(&*pool)
        .await?;

        Ok(count as i32)
    }

    /// Count of projects in this invoice
    async fn project_count(&self, ctx: &Context<'_>) -> Result<i32> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let count: i64 = sqlx::query_scalar(
            r#"SELECT COUNT(*) FROM invoice_projects WHERE invoice_id = $1"#,
        )
        .bind(self.id)
        .fetch_one(&*pool)
        .await?;

        Ok(count as i32)
    }

    /// Get the Wave invoice number if this invoice is synced to Wave
    async fn wave_invoice_number(&self, ctx: &Context<'_>) -> Result<Option<String>> {
        let Some(wave_invoice_id) = &self.wave_invoice_id else {
            return Ok(None);
        };

        let Some(wave) = ctx.data_opt::<Arc<WaveService>>() else {
            return Ok(None);
        };

        match wave.get_invoice(wave_invoice_id).await {
            Ok(invoice) => Ok(Some(invoice.invoice_number)),
            Err(_) => Ok(None),
        }
    }
}

impl From<Invoice> for InvoiceType {
    fn from(invoice: Invoice) -> Self {
        Self {
            id: invoice.base.id,
            customer_id: invoice.customer_id,
            start_date: invoice.start_date,
            end_date: invoice.end_date,
            status: invoice.status.into(),
            date_paid: invoice.date_paid,
            wave_invoice_id: invoice.wave_invoice_id,
        }
    }
}

/// Invoice-Revenue entry with snapshot amount
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct InvoiceRevenueType {
    pub id: Uuid,
    pub invoice_id: Uuid,
    pub revenue_id: Uuid,
    /// Snapshot amount - stored when added to invoice
    pub amount: Decimal,
}

#[ComplexObject]
impl InvoiceRevenueType {
    /// Get the full revenue details
    async fn revenue(&self, ctx: &Context<'_>) -> Result<Option<RevenueType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let revenue = sqlx::query_as::<_, Revenue>(
            r#"
            SELECT id, created_at, updated_at, account_id, amount, start_date, end_date, wave_service_id
            FROM revenues
            WHERE id = $1
            "#,
        )
        .bind(self.revenue_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(revenue.map(RevenueType::from))
    }

    /// Get the account this revenue belongs to
    async fn account(&self, ctx: &Context<'_>) -> Result<Option<AccountType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let account = sqlx::query_as::<_, Account>(
            r#"
            SELECT a.id, a.created_at, a.updated_at, a.customer_id, a.name, a.status, a.start_date, a.end_date
            FROM accounts a
            JOIN revenues r ON r.account_id = a.id
            WHERE r.id = $1
            "#,
        )
        .bind(self.revenue_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(account.map(AccountType::from))
    }
}

impl From<InvoiceRevenue> for InvoiceRevenueType {
    fn from(ir: InvoiceRevenue) -> Self {
        Self {
            id: ir.id,
            invoice_id: ir.invoice_id,
            revenue_id: ir.revenue_id,
            amount: ir.amount,
        }
    }
}

/// Invoice-Project entry with snapshot amount
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct InvoiceProjectType {
    pub id: Uuid,
    pub invoice_id: Uuid,
    pub project_id: Uuid,
    /// Snapshot amount - stored when added to invoice
    pub amount: Decimal,
}

#[ComplexObject]
impl InvoiceProjectType {
    /// Get the full project details
    async fn project(&self, ctx: &Context<'_>) -> Result<Option<ProjectType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let project = sqlx::query_as::<_, Project>(
            r#"
            SELECT id, created_at, updated_at, customer_id, name, date, status, labor, amount,
                   notes, calendar_event_id, wave_service_id, account_address_id,
                   street_address, city, state, zip_code
            FROM projects
            WHERE id = $1
            "#,
        )
        .bind(self.project_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(project.map(ProjectType::from))
    }

    /// Get the account this project is linked to (via account_address_id), if any
    async fn account(&self, ctx: &Context<'_>) -> Result<Option<AccountType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let account = sqlx::query_as::<_, Account>(
            r#"
            SELECT a.id, a.created_at, a.updated_at, a.customer_id, a.name, a.status, a.start_date, a.end_date
            FROM accounts a
            JOIN account_addresses aa ON aa.account_id = a.id
            JOIN projects p ON p.account_address_id = aa.id
            WHERE p.id = $1
            "#,
        )
        .bind(self.project_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(account.map(AccountType::from))
    }
}

impl From<InvoiceProject> for InvoiceProjectType {
    fn from(ip: InvoiceProject) -> Self {
        Self {
            id: ip.id,
            invoice_id: ip.invoice_id,
            project_id: ip.project_id,
            amount: ip.amount,
        }
    }
}

/// Filter input for invoice queries
#[derive(InputObject, Default)]
pub struct InvoiceFilterInput {
    /// Filter by customer ID
    pub customer_id: Option<Uuid>,
    /// Filter by status
    pub status: Option<InvoiceStatusType>,
    /// Filter by invoices that overlap with this date
    pub date: Option<NaiveDate>,
    /// Filter by invoices starting on or after this date
    pub start_date_from: Option<NaiveDate>,
    /// Filter by invoices ending on or before this date
    pub end_date_to: Option<NaiveDate>,
}

/// Paginated invoice results
#[derive(SimpleObject)]
pub struct InvoiceConnection {
    /// List of invoices
    pub items: Vec<InvoiceType>,
    /// Total count matching the filter
    pub total_count: i32,
    /// Whether there are more items after this page
    pub has_next_page: bool,
}

/// Eligible revenue for adding to an invoice
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct EligibleRevenueType {
    pub revenue_id: Uuid,
    pub account_id: Uuid,
    pub account_name: String,
    /// Revenue amount
    pub amount: Decimal,
}

#[ComplexObject]
impl EligibleRevenueType {
    /// Get the full revenue details
    async fn revenue(&self, ctx: &Context<'_>) -> Result<Option<RevenueType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let revenue = sqlx::query_as::<_, Revenue>(
            r#"
            SELECT id, created_at, updated_at, account_id, amount, start_date, end_date, wave_service_id
            FROM revenues
            WHERE id = $1
            "#,
        )
        .bind(self.revenue_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(revenue.map(RevenueType::from))
    }
}

/// Eligible project for adding to an invoice
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct EligibleInvoiceProjectType {
    pub project_id: Uuid,
    pub name: String,
    pub date: NaiveDate,
    /// Project amount
    pub amount: Decimal,
}

#[ComplexObject]
impl EligibleInvoiceProjectType {
    /// Get the full project details
    async fn project(&self, ctx: &Context<'_>) -> Result<Option<ProjectType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let project = sqlx::query_as::<_, Project>(
            r#"
            SELECT id, created_at, updated_at, customer_id, name, date, status, labor, amount,
                   notes, calendar_event_id, wave_service_id, account_address_id,
                   street_address, city, state, zip_code
            FROM projects
            WHERE id = $1
            "#,
        )
        .bind(self.project_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(project.map(ProjectType::from))
    }
}
