use std::sync::Arc;

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

use crate::db::Database;
use crate::graphql::mutations::project::ProjectScopeType;
use crate::models::{AccountAddress, Customer, Project, ProjectScope, ProjectTeamMember, TeamProfile};
use crate::services::WaveService;

use super::{AccountAddressType, CustomerType, TeamProfileType, WorkStatusType};

/// Project GraphQL type
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct ProjectType {
    pub id: Uuid,
    pub customer_id: Uuid,
    pub name: String,
    pub date: NaiveDate,
    pub status: WorkStatusType,
    pub labor: Option<Decimal>,
    pub amount: Option<Decimal>,
    pub notes: Option<String>,
    pub calendar_event_id: Option<String>,
    pub wave_service_id: Option<String>,
    /// Account address ID (if using tracked location)
    pub account_address_id: Option<Uuid>,
    /// Freeform address fields (if not using tracked location)
    pub street_address: Option<String>,
    pub city: Option<String>,
    pub state: Option<String>,
    pub zip_code: Option<String>,
}

#[ComplexObject]
impl ProjectType {
    /// Get the customer for this project
    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 the account address/location for this project (if tracked)
    async fn account_address(&self, ctx: &Context<'_>) -> Result<Option<AccountAddressType>> {
        let Some(address_id) = self.account_address_id else {
            return Ok(None);
        };

        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let address = 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 id = $1
            "#,
        )
        .bind(address_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(address.map(AccountAddressType::from))
    }

    /// Get team members assigned to this project
    async fn team_members(&self, ctx: &Context<'_>) -> Result<Vec<ProjectTeamMemberType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let members: Vec<ProjectTeamMember> = sqlx::query_as::<_, ProjectTeamMember>(
            r#"
            SELECT id, project_id, team_profile_id, created_at
            FROM project_team_members
            WHERE project_id = $1
            "#,
        )
        .bind(self.id)
        .fetch_all(&*pool)
        .await?;

        Ok(members.into_iter().map(ProjectTeamMemberType::from).collect())
    }

    /// Get scopes for this project (typically ONE active scope)
    async fn scopes(&self, ctx: &Context<'_>) -> Result<Vec<ProjectScopeType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

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

    /// Formatted address string (either from account_address or freeform)
    async fn formatted_address(&self, ctx: &Context<'_>) -> Result<Option<String>> {
        // If we have a tracked account address, fetch and format it
        if let Some(address_id) = self.account_address_id {
            let db = ctx.data::<Database>()?;
            let pool = db.pool().await;

            let address = 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 id = $1
                "#,
            )
            .bind(address_id)
            .fetch_optional(&*pool)
            .await?;

            if let Some(addr) = address {
                return Ok(Some(format!(
                    "{}, {}, {} {}",
                    addr.address.street_address, addr.address.city, addr.address.state, addr.address.zip_code
                )));
            }
        }

        // Otherwise use freeform address if available
        if let (Some(street), Some(city), Some(state), Some(zip)) = (
            &self.street_address,
            &self.city,
            &self.state,
            &self.zip_code,
        ) {
            return Ok(Some(format!("{}, {}, {} {}", street, city, state, zip)));
        }

        Ok(None)
    }

    /// 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<Project> for ProjectType {
    fn from(project: Project) -> Self {
        Self {
            id: project.base.id,
            customer_id: project.customer_id,
            name: project.name,
            date: project.date,
            status: project.status.into(),
            labor: project.labor,
            amount: project.amount,
            notes: project.notes,
            calendar_event_id: project.calendar_event_id,
            wave_service_id: project.wave_service_id,
            account_address_id: project.account_address_id,
            street_address: project.street_address,
            city: project.city,
            state: project.state,
            zip_code: project.zip_code,
        }
    }
}

/// Project team member assignment type
#[derive(SimpleObject)]
#[graphql(complex)]
pub struct ProjectTeamMemberType {
    pub id: Uuid,
    pub project_id: Uuid,
    pub team_profile_id: Uuid,
}

#[ComplexObject]
impl ProjectTeamMemberType {
    /// Get the team profile for this assignment
    async fn team_profile(&self, ctx: &Context<'_>) -> Result<Option<TeamProfileType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let profile = sqlx::query_as::<_, TeamProfile>(
            r#"
            SELECT id, created_at, updated_at, first_name, last_name, phone, email, role, status, notes
            FROM team_profiles
            WHERE id = $1
            "#,
        )
        .bind(self.team_profile_id)
        .fetch_optional(&*pool)
        .await?;

        Ok(profile.map(TeamProfileType::from))
    }
}

impl From<ProjectTeamMember> for ProjectTeamMemberType {
    fn from(member: ProjectTeamMember) -> Self {
        Self {
            id: member.id,
            project_id: member.project_id,
            team_profile_id: member.team_profile_id,
        }
    }
}

/// Filter input for project queries
#[derive(InputObject, Default)]
pub struct ProjectFilterInput {
    /// Filter by date range start (inclusive)
    pub date_from: Option<NaiveDate>,
    /// Filter by date range end (inclusive)
    pub date_to: Option<NaiveDate>,
    /// Filter by status
    pub status: Option<WorkStatusType>,
    /// Filter by customer ID - DEPRECATED: use customer_ids
    pub customer_id: Option<Uuid>,
    /// Filter by multiple customer IDs (for customer portal with multiple assigned customers)
    pub customer_ids: Option<Vec<Uuid>>,
    /// Filter by account address ID
    pub account_address_id: Option<Uuid>,
    /// Filter by assigned team profile ID (for team member portal)
    pub team_profile_id: Option<Uuid>,
}

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