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

use crate::db::Database;
use crate::graphql::types::{
    PaginationInput, ServiceConnection, ServiceFilterInput, ServiceType, ServicesForAssignmentResult,
};
use crate::models::{Service, WorkStatus};

#[derive(Default)]
pub struct ServiceQuery;

#[Object]
impl ServiceQuery {
    /// Get paginated services with optional filtering
    async fn services(
        &self,
        ctx: &Context<'_>,
        filter: Option<ServiceFilterInput>,
        pagination: Option<PaginationInput>,
    ) -> Result<ServiceConnection> {
        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();

        // Convert filter status to model enum
        let status: Option<WorkStatus> = filter.status.map(WorkStatus::from);

        // Merge customer_id into customer_ids for backward compatibility
        let customer_ids: Option<Vec<Uuid>> = match (&filter.customer_ids, &filter.customer_id) {
            (Some(ids), _) if !ids.is_empty() => Some(ids.clone()),
            (_, Some(id)) => Some(vec![*id]),
            _ => None,
        };

        // Get total count first
        let total_count: i64 = sqlx::query_scalar(
            r#"
            SELECT COUNT(*)
            FROM services
            WHERE ($1::date IS NULL OR date >= $1)
              AND ($2::date IS NULL OR date <= $2)
              AND ($3::work_status IS NULL OR status = $3)
              AND ($4::uuid IS NULL OR account_id = $4)
              AND ($5::uuid IS NULL OR account_address_id = $5)
              AND ($6::uuid IS NULL OR EXISTS (
                  SELECT 1 FROM service_team_members stm
                  WHERE stm.service_id = services.id AND stm.team_profile_id = $6
              ))
              AND ($7::uuid[] IS NULL OR EXISTS (
                  SELECT 1 FROM accounts a
                  WHERE a.id = services.account_id AND a.customer_id = ANY($7)
              ))
            "#,
        )
        .bind(filter.date_from)
        .bind(filter.date_to)
        .bind(status)
        .bind(filter.account_id)
        .bind(filter.account_address_id)
        .bind(filter.team_profile_id)
        .bind(&customer_ids)
        .fetch_one(&*pool)
        .await?;

        // Get paginated services
        let services: Vec<Service> = sqlx::query_as::<_, Service>(
            r#"
            SELECT id, created_at, updated_at, account_id, account_address_id, date, status, notes, calendar_event_id
            FROM services
            WHERE ($1::date IS NULL OR date >= $1)
              AND ($2::date IS NULL OR date <= $2)
              AND ($3::work_status IS NULL OR status = $3)
              AND ($4::uuid IS NULL OR account_id = $4)
              AND ($5::uuid IS NULL OR account_address_id = $5)
              AND ($6::uuid IS NULL OR EXISTS (
                  SELECT 1 FROM service_team_members stm
                  WHERE stm.service_id = services.id AND stm.team_profile_id = $6
              ))
              AND ($7::uuid[] IS NULL OR EXISTS (
                  SELECT 1 FROM accounts a
                  WHERE a.id = services.account_id AND a.customer_id = ANY($7)
              ))
            ORDER BY date ASC, id ASC
            LIMIT $8 OFFSET $9
            "#,
        )
        .bind(filter.date_from)
        .bind(filter.date_to)
        .bind(status)
        .bind(filter.account_id)
        .bind(filter.account_address_id)
        .bind(filter.team_profile_id)
        .bind(&customer_ids)
        .bind(limit)
        .bind(offset)
        .fetch_all(&*pool)
        .await?;

        let items: Vec<ServiceType> = services.into_iter().map(ServiceType::from).collect();
        let has_next_page = (offset + limit) < total_count;

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

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

        let service = sqlx::query_as::<_, Service>(
            r#"
            SELECT id, created_at, updated_at, account_id, account_address_id, date, status, notes, calendar_event_id
            FROM services
            WHERE id = $1
            "#,
        )
        .bind(id)
        .fetch_optional(&*pool)
        .await?;

        Ok(service.map(ServiceType::from))
    }

    /// Get status counts for services in a date range (for status tabs)
    async fn service_status_counts(
        &self,
        ctx: &Context<'_>,
        date_from: Option<chrono::NaiveDate>,
        date_to: Option<chrono::NaiveDate>,
        team_profile_id: Option<Uuid>,
        customer_ids: Option<Vec<Uuid>>,
    ) -> Result<ServiceStatusCounts> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        let counts = sqlx::query_as::<_, StatusCountRow>(
            r#"
            SELECT
                COUNT(*) FILTER (WHERE status = 'SCHEDULED') as scheduled,
                COUNT(*) FILTER (WHERE status = 'IN_PROGRESS') as in_progress,
                COUNT(*) FILTER (WHERE status = 'COMPLETED') as completed,
                COUNT(*) FILTER (WHERE status = 'CANCELLED') as cancelled
            FROM services
            WHERE ($1::date IS NULL OR date >= $1)
              AND ($2::date IS NULL OR date <= $2)
              AND ($3::uuid IS NULL OR EXISTS (
                  SELECT 1 FROM service_team_members stm
                  WHERE stm.service_id = services.id AND stm.team_profile_id = $3
              ))
              AND ($4::uuid[] IS NULL OR EXISTS (
                  SELECT 1 FROM accounts a
                  WHERE a.id = services.account_id AND a.customer_id = ANY($4)
              ))
            "#,
        )
        .bind(date_from)
        .bind(date_to)
        .bind(team_profile_id)
        .bind(&customer_ids)
        .fetch_one(&*pool)
        .await?;

        Ok(ServiceStatusCounts {
            scheduled: counts.scheduled.unwrap_or(0) as i32,
            in_progress: counts.in_progress.unwrap_or(0) as i32,
            completed: counts.completed.unwrap_or(0) as i32,
            cancelled: counts.cancelled.unwrap_or(0) as i32,
        })
    }

    /// Get services grouped by assignment status for bulk assignment page
    ///
    /// Groups services in a date range into three categories:
    /// - Unassigned: No team members
    /// - Ready to assign: Has dispatch (admin) but no other team members
    /// - Assigned: Has dispatch and at least one other team member
    async fn services_for_assignment(
        &self,
        ctx: &Context<'_>,
        date_from: NaiveDate,
        date_to: NaiveDate,
    ) -> Result<ServicesForAssignmentResult> {
        let db = ctx.data::<Database>()?;
        let pool = db.pool().await;

        // Get all services in the date range with their team member assignments
        // We need to know:
        // 1. Which services have no team members
        // 2. Which have only admin(s)
        // 3. Which have admin(s) + non-admins
        let rows: Vec<ServiceAssignmentRow> = sqlx::query_as::<_, ServiceAssignmentRow>(
            r#"
            SELECT
                s.id, s.created_at, s.updated_at, s.account_id, s.account_address_id,
                s.date, s.status, s.notes, s.calendar_event_id,
                COALESCE(
                    (SELECT COUNT(*) FROM service_team_members stm
                     JOIN team_profiles tp ON tp.id = stm.team_profile_id
                     WHERE stm.service_id = s.id AND tp.role = 'ADMIN'), 0
                ) as admin_count,
                COALESCE(
                    (SELECT COUNT(*) FROM service_team_members stm
                     JOIN team_profiles tp ON tp.id = stm.team_profile_id
                     WHERE stm.service_id = s.id AND tp.role != 'ADMIN'), 0
                ) as non_admin_count
            FROM services s
            WHERE s.date >= $1 AND s.date <= $2
              AND s.status IN ('SCHEDULED', 'IN_PROGRESS')
            ORDER BY s.date ASC, s.id ASC
            "#,
        )
        .bind(date_from)
        .bind(date_to)
        .fetch_all(&*pool)
        .await?;

        let mut unassigned = Vec::new();
        let mut ready_to_assign = Vec::new();
        let mut assigned = Vec::new();

        for row in rows {
            let service = Service {
                base: crate::models::BaseFields {
                    id: row.id,
                    created_at: row.created_at,
                    updated_at: row.updated_at,
                },
                account_id: row.account_id,
                account_address_id: row.account_address_id,
                date: row.date,
                status: row.status,
                notes: row.notes,
                calendar_event_id: row.calendar_event_id,
            };
            let service_type = ServiceType::from(service);

            if row.admin_count == 0 && row.non_admin_count == 0 {
                // No team members at all
                unassigned.push(service_type);
            } else if row.admin_count > 0 && row.non_admin_count == 0 {
                // Has admin(s) but no non-admins
                ready_to_assign.push(service_type);
            } else if row.admin_count > 0 && row.non_admin_count > 0 {
                // Has both admin and non-admin
                assigned.push(service_type);
            } else {
                // Edge case: has non-admins but no admin - treat as unassigned
                unassigned.push(service_type);
            }
        }

        Ok(ServicesForAssignmentResult {
            unassigned,
            ready_to_assign,
            assigned,
        })
    }
}

/// Status counts for the status tabs
#[derive(async_graphql::SimpleObject)]
pub struct ServiceStatusCounts {
    pub scheduled: i32,
    pub in_progress: i32,
    pub completed: i32,
    pub cancelled: i32,
}

/// Helper struct for query results
#[derive(sqlx::FromRow)]
struct StatusCountRow {
    scheduled: Option<i64>,
    in_progress: Option<i64>,
    completed: Option<i64>,
    cancelled: Option<i64>,
}

/// Helper struct for services_for_assignment query
#[derive(sqlx::FromRow)]
struct ServiceAssignmentRow {
    id: Uuid,
    created_at: chrono::DateTime<chrono::Utc>,
    updated_at: chrono::DateTime<chrono::Utc>,
    account_id: Uuid,
    account_address_id: Uuid,
    date: NaiveDate,
    status: WorkStatus,
    notes: Option<String>,
    calendar_event_id: Option<String>,
    admin_count: i64,
    non_admin_count: i64,
}
