use std::sync::Arc;

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

use crate::db::Database;
use crate::models::{Account, Customer, CustomerAddress, CustomerContact, EntityStatus};
use crate::services::WaveService;

use super::AccountType;

/// GraphQL enum for entity status
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
pub enum EntityStatusType {
    Active,
    Inactive,
    Pending,
}

impl From<EntityStatus> for EntityStatusType {
    fn from(status: EntityStatus) -> Self {
        match status {
            EntityStatus::Active => EntityStatusType::Active,
            EntityStatus::Inactive => EntityStatusType::Inactive,
            EntityStatus::Pending => EntityStatusType::Pending,
        }
    }
}

impl From<EntityStatusType> for EntityStatus {
    fn from(status: EntityStatusType) -> Self {
        match status {
            EntityStatusType::Active => EntityStatus::Active,
            EntityStatusType::Inactive => EntityStatus::Inactive,
            EntityStatusType::Pending => EntityStatus::Pending,
        }
    }
}

/// Customer GraphQL type
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct CustomerType {
    pub id: Uuid,
    pub name: String,
    pub status: EntityStatusType,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
    pub billing_terms: Option<String>,
    pub billing_email: Option<String>,
    pub wave_customer_id: Option<String>,
    /// Computed: whether customer is currently active based on status and date range
    pub is_active: bool,
}

#[ComplexObject]
impl CustomerType {
    /// Get all contacts for this customer
    async fn contacts(&self, ctx: &Context<'_>) -> Result<Vec<CustomerContactType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let contacts: Vec<CustomerContact> = sqlx::query_as::<_, CustomerContact>(
            r#"
            SELECT id, created_at, updated_at, customer_id, first_name, last_name, phone,
                   email, is_active, is_primary, notes
            FROM customer_contacts
            WHERE customer_id = $1 AND is_active = true
            ORDER BY is_primary DESC, last_name ASC, first_name ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(contacts.into_iter().map(CustomerContactType::from).collect())
    }

    /// Get all addresses for this customer (active and inactive for history)
    async fn addresses(&self, ctx: &Context<'_>) -> Result<Vec<CustomerAddressType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let addresses: Vec<CustomerAddress> = sqlx::query_as::<_, CustomerAddress>(
            r#"
            SELECT id, created_at, updated_at, customer_id, street_address, city, state, zip_code,
                   is_active, is_primary, name, notes
            FROM customer_addresses
            WHERE customer_id = $1
            ORDER BY is_active DESC, is_primary DESC, name ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(addresses.into_iter().map(CustomerAddressType::from).collect())
    }

    /// Get all accounts linked to this customer
    async fn accounts(&self, ctx: &Context<'_>) -> Result<Vec<AccountType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let accounts: Vec<Account> = sqlx::query_as::<_, Account>(
            r#"
            SELECT id, created_at, updated_at, customer_id, name, status, start_date, end_date
            FROM accounts
            WHERE customer_id = $1
            ORDER BY name ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(accounts.into_iter().map(AccountType::from).collect())
    }

    /// Get the linked Wave customer name (if wave_customer_id is set)
    async fn wave_customer_name(&self, ctx: &Context<'_>) -> Result<Option<String>> {
        let Some(wave_customer_id) = &self.wave_customer_id else {
            return Ok(None);
        };

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

        match wave.get_customer(wave_customer_id).await {
            Ok(customer) => Ok(Some(customer.name)),
            Err(_) => Ok(None),
        }
    }
}

impl From<Customer> for CustomerType {
    fn from(customer: Customer) -> Self {
        let is_active = customer.is_active();
        Self {
            id: customer.base.id,
            name: customer.name,
            status: customer.status.into(),
            start_date: customer.start_date,
            end_date: customer.end_date,
            billing_terms: customer.billing_terms,
            billing_email: customer.billing_email,
            wave_customer_id: customer.wave_customer_id,
            is_active,
        }
    }
}

/// Filter input for customer queries
#[derive(InputObject, Default)]
pub struct CustomerFilter {
    /// Filter by name (partial match, case-insensitive)
    pub name: Option<String>,
    /// Filter by status
    pub status: Option<EntityStatusType>,
    /// Filter by active state (computed from status + date range)
    pub is_active: Option<bool>,
}

// ==================== MUTATION INPUT TYPES ====================

/// Input for creating a customer
#[derive(InputObject)]
pub struct CreateCustomerInput {
    pub name: String,
    pub status: Option<EntityStatusType>,
    pub billing_email: Option<String>,
    pub billing_terms: Option<String>,
    pub wave_customer_id: Option<String>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
}

/// Input for updating a customer
#[derive(InputObject)]
pub struct UpdateCustomerInput {
    pub name: Option<String>,
    pub status: Option<EntityStatusType>,
    pub billing_email: Option<String>,
    pub billing_terms: Option<String>,
    pub wave_customer_id: Option<String>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
}

/// Input for creating a customer contact
#[derive(InputObject)]
pub struct CreateCustomerContactInput {
    pub first_name: String,
    pub last_name: String,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub notes: Option<String>,
    pub is_primary: Option<bool>,
}

/// Input for updating a customer contact
#[derive(InputObject)]
pub struct UpdateCustomerContactInput {
    pub first_name: Option<String>,
    pub last_name: Option<String>,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub notes: Option<String>,
    pub is_primary: Option<bool>,
    pub is_active: Option<bool>,
}

/// Input for creating a customer address
#[derive(InputObject)]
pub struct CreateCustomerAddressInput {
    pub name: Option<String>,
    pub street_address: String,
    pub city: String,
    pub state: String,
    pub zip_code: String,
    pub notes: Option<String>,
    pub is_primary: Option<bool>,
}

/// Input for updating a customer address
#[derive(InputObject)]
pub struct UpdateCustomerAddressInput {
    pub name: Option<String>,
    pub street_address: Option<String>,
    pub city: Option<String>,
    pub state: Option<String>,
    pub zip_code: Option<String>,
    pub notes: Option<String>,
    pub is_primary: Option<bool>,
    pub is_active: Option<bool>,
}

/// Customer contact GraphQL type
#[derive(SimpleObject)]
pub struct CustomerContactType {
    pub id: Uuid,
    pub first_name: String,
    pub last_name: String,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub is_active: bool,
    pub is_primary: bool,
    pub notes: Option<String>,
}

impl From<CustomerContact> for CustomerContactType {
    fn from(contact: CustomerContact) -> Self {
        Self {
            id: contact.base.id,
            first_name: contact.contact.first_name,
            last_name: contact.contact.last_name,
            email: contact.email,
            phone: contact.contact.phone,
            is_active: contact.is_active,
            is_primary: contact.is_primary,
            notes: contact.notes,
        }
    }
}

/// Customer address GraphQL type
#[derive(SimpleObject)]
pub struct CustomerAddressType {
    pub id: Uuid,
    pub name: Option<String>,
    pub street_address: String,
    pub city: String,
    pub state: String,
    pub zip_code: String,
    pub is_active: bool,
    pub is_primary: bool,
    pub notes: Option<String>,
}

impl From<CustomerAddress> for CustomerAddressType {
    fn from(address: CustomerAddress) -> Self {
        Self {
            id: address.base.id,
            name: address.name,
            street_address: address.address.street_address,
            city: address.address.city,
            state: address.address.state,
            zip_code: address.address.zip_code,
            is_active: address.is_active,
            is_primary: address.is_primary,
            notes: address.notes,
        }
    }
}
