use std::sync::Arc;

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

use rust_decimal::Decimal;

use crate::db::Database;
use crate::models::{
    Account, AccountAddress, AccountContact, Customer, Labor, Revenue, Schedule, ServiceScope,
    ServiceScopeArea, ServiceScopeTask, TaskFrequency,
};
use crate::services::WaveService;

use super::{CustomerType, EntityStatusType};

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

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

        let contacts: Vec<AccountContact> = sqlx::query_as::<_, AccountContact>(
            r#"
            SELECT id, created_at, updated_at, account_id, first_name, last_name, phone,
                   email, is_active, is_primary, notes
            FROM account_contacts
            WHERE account_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(AccountContactType::from).collect())
    }

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

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

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

    /// Get the parent customer for this account
    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 for this account (active and historical)
    async fn revenues(&self, ctx: &Context<'_>) -> Result<Vec<RevenueType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let revenues: Vec<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 account_id = $1
            ORDER BY start_date DESC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

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

impl From<Account> for AccountType {
    fn from(account: Account) -> Self {
        let is_active = account.is_active();
        Self {
            id: account.base.id,
            customer_id: account.customer_id,
            name: account.name,
            status: account.status.into(),
            start_date: account.start_date,
            end_date: account.end_date,
            is_active,
        }
    }
}

/// Filter input for account queries
#[derive(InputObject, Default)]
pub struct AccountFilter {
    /// 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>,
}

/// Account contact GraphQL type
#[derive(SimpleObject)]
pub struct AccountContactType {
    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<AccountContact> for AccountContactType {
    fn from(contact: AccountContact) -> 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,
        }
    }
}

/// Account address GraphQL type
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct AccountAddressType {
    pub id: Uuid,
    pub account_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>,
}

#[ComplexObject]
impl AccountAddressType {
    /// Get labor rates for this address
    async fn labor(&self, ctx: &Context<'_>) -> Result<Vec<LaborType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let labor: Vec<Labor> = sqlx::query_as::<_, Labor>(
            r#"
            SELECT id, created_at, updated_at, account_address_id, amount, start_date, end_date
            FROM labor
            WHERE account_address_id = $1
            ORDER BY start_date DESC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(labor.into_iter().map(LaborType::from).collect())
    }

    /// Get schedules for this address
    async fn schedules(&self, ctx: &Context<'_>) -> Result<Vec<ScheduleType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let schedules: Vec<Schedule> = sqlx::query_as::<_, Schedule>(
            r#"
            SELECT id, created_at, updated_at, account_address_id, name,
                   monday_service as monday, tuesday_service as tuesday, wednesday_service as wednesday,
                   thursday_service as thursday, friday_service as friday, saturday_service as saturday,
                   sunday_service as sunday, weekend_service, schedule_exception, start_date, end_date
            FROM schedules
            WHERE account_address_id = $1
            ORDER BY start_date DESC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(schedules.into_iter().map(ScheduleType::from).collect())
    }

    /// Get service scopes for this address
    async fn scopes(&self, ctx: &Context<'_>) -> Result<Vec<ServiceScopeType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let scopes: Vec<ServiceScope> = sqlx::query_as::<_, ServiceScope>(
            r#"
            SELECT id, created_at, updated_at, name, account_id, account_address_id, description, is_active
            FROM service_scopes
            WHERE account_address_id = $1
            ORDER BY is_active DESC, name ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(scopes.into_iter().map(ServiceScopeType::from).collect())
    }

    /// Get the active service scope for this address
    async fn active_scope(&self, ctx: &Context<'_>) -> Result<Option<ServiceScopeType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let scope: Option<ServiceScope> = sqlx::query_as::<_, ServiceScope>(
            r#"
            SELECT id, created_at, updated_at, name, account_id, account_address_id, description, is_active
            FROM service_scopes
            WHERE account_address_id = $1 AND is_active = true
            LIMIT 1
            "#,
        )
        .bind(self.id)
        .fetch_optional(&*pool)
        .await?;

        Ok(scope.map(ServiceScopeType::from))
    }

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

        let account: 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
            WHERE a.id = $1
            "#,
        )
        .bind(self.account_id)
        .fetch_one(&*pool)
        .await?;

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

impl From<AccountAddress> for AccountAddressType {
    fn from(address: AccountAddress) -> Self {
        Self {
            id: address.base.id,
            account_id: address.account_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,
        }
    }
}

/// Revenue GraphQL type
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct RevenueType {
    pub id: Uuid,
    pub amount: Decimal,
    pub start_date: NaiveDate,
    pub end_date: Option<NaiveDate>,
    pub wave_service_id: Option<String>,
    /// Computed: whether revenue is currently active (no end_date or end_date >= today)
    pub is_active: bool,
}

#[ComplexObject]
impl RevenueType {
    /// Get the linked Wave product name (if wave_service_id is set)
    async fn wave_product_name(&self, ctx: &Context<'_>) -> Result<Option<String>> {
        let Some(wave_service_id) = &self.wave_service_id else {
            return Ok(None);
        };

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

        match wave.get_product(wave_service_id).await {
            Ok(product) => Ok(Some(product.name)),
            Err(_) => Ok(None),
        }
    }
}

impl From<Revenue> for RevenueType {
    fn from(revenue: Revenue) -> Self {
        let today = chrono::Utc::now().date_naive();
        let is_active = revenue.is_active_on(today);
        Self {
            id: revenue.base.id,
            amount: revenue.amount,
            start_date: revenue.start_date,
            end_date: revenue.end_date,
            wave_service_id: revenue.wave_service_id,
            is_active,
        }
    }
}

/// Labor rate GraphQL type
#[derive(SimpleObject)]
pub struct LaborType {
    pub id: Uuid,
    pub amount: Decimal,
    pub start_date: NaiveDate,
    pub end_date: Option<NaiveDate>,
    /// Computed: whether labor rate is currently active
    pub is_active: bool,
}

impl From<Labor> for LaborType {
    fn from(labor: Labor) -> Self {
        let today = chrono::Utc::now().date_naive();
        let is_active = labor.is_active_on(today);
        Self {
            id: labor.base.id,
            amount: labor.amount,
            start_date: labor.start_date,
            end_date: labor.end_date,
            is_active,
        }
    }
}

/// Schedule GraphQL type
#[derive(SimpleObject)]
pub struct ScheduleType {
    pub id: Uuid,
    pub name: Option<String>,
    pub monday: bool,
    pub tuesday: bool,
    pub wednesday: bool,
    pub thursday: bool,
    pub friday: bool,
    pub saturday: bool,
    pub sunday: bool,
    pub weekend_service: bool,
    pub schedule_exception: Option<String>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
    /// Computed: whether schedule is currently active
    pub is_active: bool,
}

impl From<Schedule> for ScheduleType {
    fn from(schedule: Schedule) -> Self {
        let today = chrono::Utc::now().date_naive();
        let is_active = match (schedule.start_date, schedule.end_date) {
            (Some(start), Some(end)) => today >= start && today <= end,
            (Some(start), None) => today >= start,
            (None, Some(end)) => today <= end,
            (None, None) => true,
        };
        Self {
            id: schedule.base.id,
            name: schedule.name,
            monday: schedule.monday,
            tuesday: schedule.tuesday,
            wednesday: schedule.wednesday,
            thursday: schedule.thursday,
            friday: schedule.friday,
            saturday: schedule.saturday,
            sunday: schedule.sunday,
            weekend_service: schedule.weekend_service,
            schedule_exception: schedule.schedule_exception,
            start_date: schedule.start_date,
            end_date: schedule.end_date,
            is_active,
        }
    }
}

/// Task frequency enum for GraphQL
#[derive(async_graphql::Enum, Copy, Clone, Eq, PartialEq)]
pub enum TaskFrequencyType {
    Daily,
    Weekly,
    Monthly,
    Quarterly,
    Triannual,
    Annual,
    AsNeeded,
}

impl From<TaskFrequency> for TaskFrequencyType {
    fn from(freq: TaskFrequency) -> Self {
        match freq {
            TaskFrequency::Daily => TaskFrequencyType::Daily,
            TaskFrequency::Weekly => TaskFrequencyType::Weekly,
            TaskFrequency::Monthly => TaskFrequencyType::Monthly,
            TaskFrequency::Quarterly => TaskFrequencyType::Quarterly,
            TaskFrequency::Triannual => TaskFrequencyType::Triannual,
            TaskFrequency::Annual => TaskFrequencyType::Annual,
            TaskFrequency::AsNeeded => TaskFrequencyType::AsNeeded,
        }
    }
}

/// Service scope GraphQL type
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct ServiceScopeType {
    pub id: Uuid,
    pub name: String,
    pub description: Option<String>,
    pub is_active: bool,
}

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

        let areas: Vec<ServiceScopeArea> = sqlx::query_as::<_, ServiceScopeArea>(
            r#"
            SELECT id, created_at, updated_at, scope_id, name, "order"
            FROM service_scope_areas
            WHERE scope_id = $1
            ORDER BY "order" ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(areas.into_iter().map(ServiceScopeAreaType::from).collect())
    }
}

impl From<ServiceScope> for ServiceScopeType {
    fn from(scope: ServiceScope) -> Self {
        Self {
            id: scope.base.id,
            name: scope.name,
            description: scope.description,
            is_active: scope.is_active,
        }
    }
}

/// Service scope area GraphQL type
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct ServiceScopeAreaType {
    pub id: Uuid,
    pub name: String,
    pub order: i32,
}

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

        let tasks: Vec<ServiceScopeTask> = sqlx::query_as::<_, ServiceScopeTask>(
            r#"
            SELECT id, created_at, updated_at, area_id, scope_description, checklist_description,
                   session_description, frequency, "order", estimated_minutes
            FROM service_scope_tasks
            WHERE area_id = $1
            ORDER BY "order" ASC
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(tasks.into_iter().map(ServiceScopeTaskType::from).collect())
    }
}

impl From<ServiceScopeArea> for ServiceScopeAreaType {
    fn from(area: ServiceScopeArea) -> Self {
        Self {
            id: area.base.id,
            name: area.name,
            order: area.order,
        }
    }
}

/// Service scope task GraphQL type
#[derive(SimpleObject)]
pub struct ServiceScopeTaskType {
    pub id: Uuid,
    pub scope_description: String,
    pub checklist_description: String,
    pub session_description: String,
    pub frequency: TaskFrequencyType,
    pub order: i32,
    pub estimated_minutes: Option<i32>,
}

impl From<ServiceScopeTask> for ServiceScopeTaskType {
    fn from(task: ServiceScopeTask) -> Self {
        Self {
            id: task.base.id,
            scope_description: task.scope_description,
            checklist_description: task.checklist_description,
            session_description: task.session_description,
            frequency: task.frequency.into(),
            order: task.order,
            estimated_minutes: task.estimated_minutes,
        }
    }
}

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

/// Input for creating a labor rate
#[derive(InputObject)]
pub struct CreateLaborInput {
    pub amount: Decimal,
    pub start_date: NaiveDate,
}

/// Input for updating a labor rate
#[derive(InputObject)]
pub struct UpdateLaborInput {
    pub amount: Option<Decimal>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
}

/// Input for creating a schedule
#[derive(InputObject)]
pub struct CreateScheduleInput {
    pub name: Option<String>,
    pub monday: Option<bool>,
    pub tuesday: Option<bool>,
    pub wednesday: Option<bool>,
    pub thursday: Option<bool>,
    pub friday: Option<bool>,
    pub saturday: Option<bool>,
    pub sunday: Option<bool>,
    pub weekend_service: Option<bool>,
    pub schedule_exception: Option<String>,
    pub start_date: Option<NaiveDate>,
}

/// Input for updating a schedule
#[derive(InputObject)]
pub struct UpdateScheduleInput {
    pub name: Option<String>,
    pub monday: Option<bool>,
    pub tuesday: Option<bool>,
    pub wednesday: Option<bool>,
    pub thursday: Option<bool>,
    pub friday: Option<bool>,
    pub saturday: Option<bool>,
    pub sunday: Option<bool>,
    pub weekend_service: Option<bool>,
    pub schedule_exception: Option<String>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
}

/// Input for creating a service scope
#[derive(InputObject)]
pub struct CreateServiceScopeInput {
    pub name: String,
    pub description: Option<String>,
    pub areas: Option<Vec<CreateScopeAreaInput>>,
}

/// Input for updating a service scope
#[derive(InputObject)]
pub struct UpdateServiceScopeInput {
    pub name: Option<String>,
    pub description: Option<String>,
    pub is_active: Option<bool>,
}

/// Input for creating a scope area
#[derive(InputObject)]
pub struct CreateScopeAreaInput {
    pub name: String,
    pub order: Option<i32>,
    pub tasks: Option<Vec<CreateScopeTaskInput>>,
}

/// Input for updating a scope area
#[derive(InputObject)]
pub struct UpdateScopeAreaInput {
    pub name: Option<String>,
    pub order: Option<i32>,
}

/// Input for creating a scope task
#[derive(InputObject)]
pub struct CreateScopeTaskInput {
    pub scope_description: String,
    pub checklist_description: Option<String>,
    pub session_description: Option<String>,
    pub frequency: Option<TaskFrequencyType>,
    pub order: Option<i32>,
    pub estimated_minutes: Option<i32>,
}

/// Input for updating a scope task
#[derive(InputObject)]
pub struct UpdateScopeTaskInput {
    pub scope_description: Option<String>,
    pub checklist_description: Option<String>,
    pub session_description: Option<String>,
    pub frequency: Option<TaskFrequencyType>,
    pub order: Option<i32>,
    pub estimated_minutes: Option<i32>,
}

/// Input for updating an account address (location)
#[derive(InputObject)]
pub struct UpdateAccountAddressInput {
    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>,
}
