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

use crate::db::Database;
use crate::graphql::types::{ReportProjectType, ReportServiceType, ReportStatusType, ReportType};
use crate::models::{EventType, Report, ReportProject, ReportService, ReportStatus};
use crate::services::EventPublisher;
use crate::auth::UserContext;

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

/// Input for creating a report
#[derive(InputObject)]
pub struct CreateReportInput {
    pub team_profile_id: Uuid,
    pub start_date: NaiveDate,
    pub end_date: NaiveDate,
}

/// Input for updating a report
#[derive(InputObject)]
pub struct UpdateReportInput {
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
    pub status: Option<ReportStatusType>,
}

#[derive(Default)]
pub struct ReportMutation;

#[Object]
impl ReportMutation {
    /// Create a new report for a team member
    async fn create_report(&self, ctx: &Context<'_>, input: CreateReportInput) -> Result<ReportType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Validate date range
        if input.end_date < input.start_date {
            return Err("End date must be on or after start date".into());
        }

        let report: Report = sqlx::query_as::<_, Report>(
            r#"
            INSERT INTO reports (
                id, created_at, updated_at, team_profile_id, start_date, end_date, status
            )
            VALUES (
                gen_random_uuid(), NOW(), NOW(), $1, $2, $3, 'DRAFT'
            )
            RETURNING id, created_at, updated_at, team_profile_id, start_date, end_date, status
            "#,
        )
        .bind(input.team_profile_id)
        .bind(input.start_date)
        .bind(input.end_date)
        .fetch_one(&*pool)
        .await?;

        Ok(ReportType::from(report))
    }

    /// Update a report
    async fn update_report(
        &self,
        ctx: &Context<'_>,
        id: Uuid,
        input: UpdateReportInput,
    ) -> Result<ReportType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Validate date range if both provided
        if let (Some(start), Some(end)) = (input.start_date, input.end_date) {
            if end < start {
                return Err("End date must be on or after start date".into());
            }
        }

        // Get old status for event tracking
        let old_status: Option<ReportStatus> = sqlx::query_scalar(
            "SELECT status FROM reports WHERE id = $1",
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

        let new_status = input.status.map(ReportStatus::from);

        let report: Report = sqlx::query_as::<_, Report>(
            r#"
            UPDATE reports
            SET
                start_date = COALESCE($2, start_date),
                end_date = COALESCE($3, end_date),
                status = COALESCE($4, status),
                updated_at = NOW()
            WHERE id = $1
            RETURNING id, created_at, updated_at, team_profile_id, start_date, end_date, status
            "#,
        )
        .bind(id)
        .bind(input.start_date)
        .bind(input.end_date)
        .bind(new_status)
        .fetch_one(&*pool)
        .await?;

        // Publish events for status changes
        if let Ok(user) = ctx.data::<UserContext>() {
            if let (Some(old), Some(new)) = (old_status, new_status) {
                if old != new {
                    let event_type = match new {
                        ReportStatus::Finalized => Some(EventType::ReportSubmitted),
                        ReportStatus::Paid => Some(EventType::ReportApproved),
                        _ => None,
                    };

                    if let Some(event_type) = event_type {
                        let _ = EventPublisher::publish(
                            &pool,
                            event_type,
                            "report",
                            report.base.id,
                            Some(("team_profile", user.user_id)),
                            None,
                        )
                        .await;
                    }
                }
            }
        }

        Ok(ReportType::from(report))
    }

    /// Delete a report and all its entries
    async fn delete_report(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Delete entries first (cascade should handle this, but be explicit)
        sqlx::query("DELETE FROM report_services WHERE report_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        sqlx::query("DELETE FROM report_projects WHERE report_id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        let result = sqlx::query("DELETE FROM reports WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    /// Add a service to a report with calculated labor share
    /// The labor share is calculated and stored as a snapshot at this moment
    async fn add_service_to_report(
        &self,
        ctx: &Context<'_>,
        report_id: Uuid,
        service_id: Uuid,
    ) -> Result<ReportServiceType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

        // Get the report to find the team profile
        let report = sqlx::query_as::<_, Report>(
            "SELECT id, created_at, updated_at, team_profile_id, start_date, end_date, status FROM reports WHERE id = $1",
        )
        .bind(report_id)
        .fetch_optional(&*pool)
        .await?
        .ok_or("Report not found")?;

        // Verify the team member is assigned to this service
        let is_assigned: bool = sqlx::query_scalar(
            r#"
            SELECT EXISTS(
                SELECT 1 FROM service_team_members
                WHERE service_id = $1 AND team_profile_id = $2
            )
            "#,
        )
        .bind(service_id)
        .bind(report.team_profile_id)
        .fetch_one(&*pool)
        .await?;

        if !is_assigned {
            return Err("Team member is not assigned to this service".into());
        }

        // Verify the service is completed
        let is_completed: bool = sqlx::query_scalar(
            "SELECT status = 'COMPLETED' FROM services WHERE id = $1",
        )
        .bind(service_id)
        .fetch_one(&*pool)
        .await?;

        if !is_completed {
            return Err("Service must be completed before adding to report".into());
        }

        // Calculate labor share
        // 1. Get the service date and location
        let service_info: ServiceInfo = sqlx::query_as::<_, ServiceInfo>(
            "SELECT date, account_address_id FROM services WHERE id = $1",
        )
        .bind(service_id)
        .fetch_one(&*pool)
        .await?;

        // 2. Get the labor rate for the location on the service date
        let labor_amount: Option<Decimal> = sqlx::query_scalar(
            r#"
            SELECT amount
            FROM labors
            WHERE account_address_id = $1
              AND start_date <= $2
              AND (end_date IS NULL OR end_date >= $2)
            ORDER BY start_date DESC
            LIMIT 1
            "#,
        )
        .bind(service_info.account_address_id)
        .bind(service_info.date)
        .fetch_optional(&*pool)
        .await?;

        let labor_total = labor_amount.unwrap_or(Decimal::ZERO);

        // 3. Count team members (excluding dispatch)
        let team_count: i64 = sqlx::query_scalar(
            r#"
            SELECT COUNT(*)
            FROM service_team_members
            WHERE service_id = $1 AND team_profile_id != $2
            "#,
        )
        .bind(service_id)
        .bind(dispatch_id)
        .fetch_one(&*pool)
        .await?;

        if team_count == 0 {
            return Err("No eligible team members for labor calculation".into());
        }

        // 4. Calculate share
        let labor_share = labor_total / Decimal::from(team_count);

        // Insert the report service with snapshot labor share
        let entry: ReportService = sqlx::query_as::<_, ReportService>(
            r#"
            INSERT INTO report_services (id, report_id, service_id, labor_share, created_at)
            VALUES (gen_random_uuid(), $1, $2, $3, NOW())
            RETURNING id, report_id, service_id, labor_share, created_at
            "#,
        )
        .bind(report_id)
        .bind(service_id)
        .bind(labor_share)
        .fetch_one(&*pool)
        .await?;

        Ok(ReportServiceType::from(entry))
    }

    /// Add a project to a report with calculated labor share
    async fn add_project_to_report(
        &self,
        ctx: &Context<'_>,
        report_id: Uuid,
        project_id: Uuid,
    ) -> Result<ReportProjectType> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

        // Get the report to find the team profile
        let report = sqlx::query_as::<_, Report>(
            "SELECT id, created_at, updated_at, team_profile_id, start_date, end_date, status FROM reports WHERE id = $1",
        )
        .bind(report_id)
        .fetch_optional(&*pool)
        .await?
        .ok_or("Report not found")?;

        // Verify the team member is assigned to this project
        let is_assigned: bool = sqlx::query_scalar(
            r#"
            SELECT EXISTS(
                SELECT 1 FROM project_team_members
                WHERE project_id = $1 AND team_profile_id = $2
            )
            "#,
        )
        .bind(project_id)
        .bind(report.team_profile_id)
        .fetch_one(&*pool)
        .await?;

        if !is_assigned {
            return Err("Team member is not assigned to this project".into());
        }

        // Verify the project is completed
        let is_completed: bool = sqlx::query_scalar(
            "SELECT status = 'COMPLETED' FROM projects WHERE id = $1",
        )
        .bind(project_id)
        .fetch_one(&*pool)
        .await?;

        if !is_completed {
            return Err("Project must be completed before adding to report".into());
        }

        // Get project labor amount
        let labor_total: Option<Decimal> = sqlx::query_scalar(
            "SELECT labor FROM projects WHERE id = $1",
        )
        .bind(project_id)
        .fetch_one(&*pool)
        .await?;

        let labor_total = labor_total.unwrap_or(Decimal::ZERO);

        // Count team members (excluding dispatch)
        let team_count: i64 = sqlx::query_scalar(
            r#"
            SELECT COUNT(*)
            FROM project_team_members
            WHERE project_id = $1 AND team_profile_id != $2
            "#,
        )
        .bind(project_id)
        .bind(dispatch_id)
        .fetch_one(&*pool)
        .await?;

        if team_count == 0 {
            return Err("No eligible team members for labor calculation".into());
        }

        // Calculate share
        let labor_share = labor_total / Decimal::from(team_count);

        // Insert the report project with snapshot labor share
        let entry: ReportProject = sqlx::query_as::<_, ReportProject>(
            r#"
            INSERT INTO report_projects (id, report_id, project_id, labor_share, created_at)
            VALUES (gen_random_uuid(), $1, $2, $3, NOW())
            RETURNING id, report_id, project_id, labor_share, created_at
            "#,
        )
        .bind(report_id)
        .bind(project_id)
        .bind(labor_share)
        .fetch_one(&*pool)
        .await?;

        Ok(ReportProjectType::from(entry))
    }

    /// Remove a service from a report
    async fn remove_service_from_report(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let result = sqlx::query("DELETE FROM report_services WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    /// Remove a project from a report
    async fn remove_project_from_report(&self, ctx: &Context<'_>, id: Uuid) -> Result<bool> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let result = sqlx::query("DELETE FROM report_projects WHERE id = $1")
            .bind(id)
            .execute(&*pool)
            .await?;

        Ok(result.rows_affected() > 0)
    }

    /// Add all eligible services to a report at once
    /// Returns the number of services added
    async fn add_all_eligible_services_to_report(
        &self,
        ctx: &Context<'_>,
        report_id: Uuid,
    ) -> Result<i32> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

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

        // Find all eligible services and insert them
        let result = sqlx::query(
            r#"
            INSERT INTO report_services (id, report_id, service_id, labor_share, created_at)
            SELECT
                gen_random_uuid(),
                $1,
                s.id,
                COALESCE(l.amount, 0) / GREATEST(
                    (SELECT COUNT(*) FROM service_team_members stm2
                     WHERE stm2.service_id = s.id AND stm2.team_profile_id != $5),
                    1
                ),
                NOW()
            FROM services s
            JOIN service_team_members stm ON stm.service_id = s.id AND stm.team_profile_id = $2
            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 >= $3
              AND s.date <= $4
              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 = $2
              )
              AND (SELECT COUNT(*) FROM service_team_members stm2
                   WHERE stm2.service_id = s.id AND stm2.team_profile_id != $5) > 0
            "#,
        )
        .bind(report_id)
        .bind(report.team_profile_id)
        .bind(report.start_date)
        .bind(report.end_date)
        .bind(dispatch_id)
        .execute(&*pool)
        .await?;

        Ok(result.rows_affected() as i32)
    }

    /// Add all eligible projects to a report at once
    /// Returns the number of projects added
    async fn add_all_eligible_projects_to_report(
        &self,
        ctx: &Context<'_>,
        report_id: Uuid,
    ) -> Result<i32> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

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

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

        // Find all eligible projects and insert them
        let result = sqlx::query(
            r#"
            INSERT INTO report_projects (id, report_id, project_id, labor_share, created_at)
            SELECT
                gen_random_uuid(),
                $1,
                p.id,
                COALESCE(p.labor, 0) / GREATEST(
                    (SELECT COUNT(*) FROM project_team_members ptm2
                     WHERE ptm2.project_id = p.id AND ptm2.team_profile_id != $5),
                    1
                ),
                NOW()
            FROM projects p
            JOIN project_team_members ptm ON ptm.project_id = p.id AND ptm.team_profile_id = $2
            WHERE p.status = 'COMPLETED'
              AND p.date >= $3
              AND p.date <= $4
              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 = $2
              )
              AND (SELECT COUNT(*) FROM project_team_members ptm2
                   WHERE ptm2.project_id = p.id AND ptm2.team_profile_id != $5) > 0
            "#,
        )
        .bind(report_id)
        .bind(report.team_profile_id)
        .bind(report.start_date)
        .bind(report.end_date)
        .bind(dispatch_id)
        .execute(&*pool)
        .await?;

        Ok(result.rows_affected() as i32)
    }
}

/// Helper struct for service info query
#[derive(sqlx::FromRow)]
struct ServiceInfo {
    date: NaiveDate,
    account_address_id: Uuid,
}
