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

use crate::db::Database;
use crate::graphql::types::{
    EligibleProjectType, EligibleServiceType, PaginationInput, ReportConnection, ReportFilterInput,
    ReportType,
};
use crate::models::{Report, ReportStatus};

/// Dispatch profile ID - excluded from labor share calculations
/// Replace with your dispatch profile UUID
const DISPATCH_PROFILE_ID: &str = "00000000-0000-0000-0000-000000000000";

#[derive(Default)]
pub struct ReportQuery;

#[Object]
impl ReportQuery {
    /// Get paginated reports with optional filtering
    async fn reports(
        &self,
        ctx: &Context<'_>,
        filter: Option<ReportFilterInput>,
        pagination: Option<PaginationInput>,
    ) -> Result<ReportConnection> {
        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<ReportStatus> = filter.status.map(ReportStatus::from);

        // Get total count
        let total_count: i64 = sqlx::query_scalar(
            r#"
            SELECT COUNT(*)
            FROM reports
            WHERE ($1::uuid IS NULL OR team_profile_id = $1)
              AND ($2::report_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.team_profile_id)
        .bind(status)
        .bind(filter.date)
        .bind(filter.start_date_from)
        .bind(filter.end_date_to)
        .fetch_one(&*pool)
        .await?;

        // Get paginated reports
        let reports: Vec<Report> = sqlx::query_as::<_, Report>(
            r#"
            SELECT id, created_at, updated_at, team_profile_id, start_date, end_date, status
            FROM reports
            WHERE ($1::uuid IS NULL OR team_profile_id = $1)
              AND ($2::report_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.team_profile_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<ReportType> = reports.into_iter().map(ReportType::from).collect();
        let has_next_page = (offset + limit) < total_count;

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

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

        let report = sqlx::query_as::<_, Report>(
            r#"
            SELECT id, created_at, updated_at, team_profile_id, start_date, end_date, status
            FROM reports
            WHERE id = $1
            "#,
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

        Ok(report.map(ReportType::from))
    }

    /// Get eligible services for a team member that can be added to a report
    /// Returns completed services where the team member is assigned and not already in a report
    async fn eligible_services_for_report(
        &self,
        ctx: &Context<'_>,
        team_profile_id: Uuid,
        date_from: NaiveDate,
        date_to: NaiveDate,
    ) -> Result<Vec<EligibleServiceType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let dispatch_id = Uuid::parse_str(DISPATCH_PROFILE_ID)?;

        // Find completed services where:
        // 1. Team member is assigned
        // 2. Service is within date range
        // 3. Service is not already in a report for this team member
        let rows: Vec<EligibleServiceRow> = sqlx::query_as::<_, EligibleServiceRow>(
            r#"
            SELECT
                s.id as service_id,
                s.date,
                COALESCE(l.amount, 0) as labor_total,
                (
                    SELECT COUNT(*)
                    FROM service_team_members stm2
                    WHERE stm2.service_id = s.id
                      AND stm2.team_profile_id != $4
                ) as team_member_count
            FROM services s
            JOIN service_team_members stm ON stm.service_id = s.id AND stm.team_profile_id = $1
            LEFT JOIN account_addresses aa ON aa.id = s.account_address_id
            LEFT JOIN LATERAL (
                SELECT amount
                FROM labors
                WHERE account_address_id = aa.id
                  AND start_date <= s.date
                  AND (end_date IS NULL OR end_date >= s.date)
                ORDER BY start_date DESC
                LIMIT 1
            ) l ON true
            WHERE s.status = 'COMPLETED'
              AND s.date >= $2
              AND s.date <= $3
              AND NOT EXISTS (
                  SELECT 1
                  FROM report_services rs
                  JOIN reports r ON r.id = rs.report_id
                  WHERE rs.service_id = s.id
                    AND r.team_profile_id = $1
              )
            ORDER BY s.date ASC
            "#,
        )
        .bind(team_profile_id)
        .bind(date_from)
        .bind(date_to)
        .bind(dispatch_id)
        .fetch_all(&*pool)
        .await?;

        let items = rows
            .into_iter()
            .filter(|r| r.team_member_count > 0)
            .map(|r| {
                let labor_share = if r.team_member_count > 0 {
                    r.labor_total / Decimal::from(r.team_member_count)
                } else {
                    Decimal::ZERO
                };

                EligibleServiceType {
                    service_id: r.service_id,
                    date: r.date,
                    labor_share,
                    labor_total: r.labor_total,
                    team_member_count: r.team_member_count as i32,
                }
            })
            .collect();

        Ok(items)
    }

    /// Get eligible projects for a team member that can be added to a report
    /// Returns completed projects where the team member is assigned and not already in a report
    async fn eligible_projects_for_report(
        &self,
        ctx: &Context<'_>,
        team_profile_id: Uuid,
        date_from: NaiveDate,
        date_to: NaiveDate,
    ) -> Result<Vec<EligibleProjectType>> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let dispatch_id = Uuid::parse_str(DISPATCH_PROFILE_ID)?;

        // Find completed projects where:
        // 1. Team member is assigned
        // 2. Project is within date range
        // 3. Project is not already in a report for this team member
        let rows: Vec<EligibleProjectRow> = sqlx::query_as::<_, EligibleProjectRow>(
            r#"
            SELECT
                p.id as project_id,
                p.date,
                COALESCE(p.labor, 0) as labor_total,
                (
                    SELECT COUNT(*)
                    FROM project_team_members ptm2
                    WHERE ptm2.project_id = p.id
                      AND ptm2.team_profile_id != $4
                ) as team_member_count
            FROM projects p
            JOIN project_team_members ptm ON ptm.project_id = p.id AND ptm.team_profile_id = $1
            WHERE p.status = 'COMPLETED'
              AND p.date >= $2
              AND p.date <= $3
              AND NOT EXISTS (
                  SELECT 1
                  FROM report_projects rp
                  JOIN reports r ON r.id = rp.report_id
                  WHERE rp.project_id = p.id
                    AND r.team_profile_id = $1
              )
            ORDER BY p.date ASC
            "#,
        )
        .bind(team_profile_id)
        .bind(date_from)
        .bind(date_to)
        .bind(dispatch_id)
        .fetch_all(&*pool)
        .await?;

        let items = rows
            .into_iter()
            .filter(|r| r.team_member_count > 0)
            .map(|r| {
                let labor_share = if r.team_member_count > 0 {
                    r.labor_total / Decimal::from(r.team_member_count)
                } else {
                    Decimal::ZERO
                };

                EligibleProjectType {
                    project_id: r.project_id,
                    date: r.date,
                    labor_share,
                    labor_total: r.labor_total,
                    team_member_count: r.team_member_count as i32,
                }
            })
            .collect();

        Ok(items)
    }
}

/// Helper struct for eligible service query
#[derive(sqlx::FromRow)]
struct EligibleServiceRow {
    service_id: Uuid,
    date: NaiveDate,
    labor_total: Decimal,
    team_member_count: i64,
}

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