use anyhow::Result;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tokio::time::sleep;
use tracing::{error, info, warn};

use crate::core::chunk::{rule1_ingest, rule3_ingest};
use crate::core::db::{
    MonitorJobStatus, PostgresDb, ProcessorJobStatus, ProcessorType, RedisClient, VideoStatus,
};
use crate::worker::config::WorkerConfig;
use crate::worker::processor::{ProcessorPool, ProcessorTask};

pub struct JobWorker {
    db: Arc<PostgresDb>,
    redis: Arc<RedisClient>,
    config: WorkerConfig,
    processor_pool: ProcessorPool,
}

impl JobWorker {
    pub fn new(db: Arc<PostgresDb>, redis: Arc<RedisClient>, config: WorkerConfig) -> Self {
        let processor_pool = ProcessorPool::new(db.clone(), redis.clone(), config.max_concurrent);

        Self {
            db,
            redis,
            config,
            processor_pool,
        }
    }

    pub async fn run(&self) -> Result<()> {
        info!(
            "Job Worker started with config: max_concurrent={}, poll_interval={}s",
            self.config.max_concurrent, self.config.poll_interval_secs
        );

        loop {
            if !self.config.enabled {
                warn!("Worker is disabled, sleeping for 60s...");
                sleep(Duration::from_secs(60)).await;
                continue;
            }

            if let Err(e) = self.poll_and_process().await {
                error!("Error during poll and process: {}", e);
            }

            sleep(Duration::from_secs(self.config.poll_interval_secs)).await;
        }
    }

    async fn poll_and_process(&self) -> Result<()> {
        // Always check for completion of running jobs first
        // This ensures jobs with all processors in terminal states are marked complete/failed
        let running_jobs_done = self
            .db
            .get_running_jobs_with_all_processors_done(self.config.batch_size)
            .await?;
        for job in running_jobs_done {
            if let Err(e) = self.check_and_complete_job(job.id, &job.uuid).await {
                error!("Failed to complete job {}: {}", job.uuid, e);
            }
        }

        // Process pending jobs if any
        let pending_jobs = self.db.get_pending_jobs(self.config.batch_size).await?;

        if !pending_jobs.is_empty() {
            info!("Found {} pending jobs", pending_jobs.len());

            for job in pending_jobs {
                if !self.processor_pool.can_start().await {
                    info!("Max concurrent processors reached, waiting...");
                    break;
                }

                if let Err(e) = self.process_job(job).await {
                    error!("Failed to process job: {}", e);
                }
            }
        }

        Ok(())
    }

    async fn process_job(&self, job: crate::core::db::MonitorJob) -> Result<()> {
        info!("Processing job: {} ({})", job.uuid, job.id);

        let total_processors = ProcessorType::all().len() as i32;

        self.db
            .update_job_status(job.id, MonitorJobStatus::Running)
            .await?;

        self.redis
            .update_worker_job_status(&job.uuid, job.id, "running", None, 0, total_processors)
            .await?;

        // Get existing processor results for this job
        let existing_results = self.db.get_processor_results_by_job(job.id).await?;
        let mut result_map = HashMap::new();
        for result in existing_results {
            result_map.insert(result.processor_type, result);
        }

        for processor_type in ProcessorType::all() {
            // Check if processor already in terminal state
            if let Some(result) = result_map.get(&processor_type) {
                match result.status {
                    ProcessorJobStatus::Completed | ProcessorJobStatus::Skipped => {
                        info!(
                            "Processor {} already completed, skipping",
                            processor_type.as_str()
                        );
                        continue;
                    }
                    ProcessorJobStatus::Failed => {
                        info!("Processor {} failed, skipping", processor_type.as_str());
                        continue;
                    }
                    ProcessorJobStatus::Running => {
                        info!(
                            "Processor {} already running, skipping",
                            processor_type.as_str()
                        );
                        continue;
                    }
                    ProcessorJobStatus::Pending => {
                        // Continue to start processor
                    }
                }
            }

            // Check capacity before starting processor
            if !self.processor_pool.can_start().await {
                info!(
                    "Max concurrent processors reached, skipping remaining processors for job {}",
                    job.uuid
                );
                break;
            }

            let processor_result_id = self
                .db
                .create_processor_result(job.id, processor_type)
                .await?;

            self.redis
                .update_worker_processor_status(&job.uuid, processor_type.as_str(), "pending", None)
                .await?;

            let task = ProcessorTask {
                job: job.clone(),
                processor_type,
                processor_result_id,
            };

            self.processor_pool.start_processor(task).await?;
        }

        self.check_and_complete_job(job.id, &job.uuid).await?;

        Ok(())
    }

    async fn check_and_complete_job(&self, job_id: i32, uuid: &str) -> Result<()> {
        let results = self.db.get_processor_results_by_job(job_id).await?;

        let all_completed = results.iter().all(|r| {
            matches!(
                r.status,
                crate::core::db::ProcessorJobStatus::Completed
                    | crate::core::db::ProcessorJobStatus::Skipped
            )
        });

        let any_failed = results
            .iter()
            .any(|r| matches!(r.status, crate::core::db::ProcessorJobStatus::Failed));

        let completed_count = results
            .iter()
            .filter(|r| {
                matches!(
                    r.status,
                    crate::core::db::ProcessorJobStatus::Completed
                        | crate::core::db::ProcessorJobStatus::Skipped
                )
            })
            .count() as i32;

        // Compute completed and failed processor arrays
        let completed_processors: Vec<String> = results
            .iter()
            .filter(|r| {
                matches!(
                    r.status,
                    crate::core::db::ProcessorJobStatus::Completed
                        | crate::core::db::ProcessorJobStatus::Skipped
                )
            })
            .map(|r| r.processor_type.as_str().to_string())
            .collect();

        let failed_processors: Vec<String> = results
            .iter()
            .filter(|r| matches!(r.status, crate::core::db::ProcessorJobStatus::Failed))
            .map(|r| r.processor_type.as_str().to_string())
            .collect();

        // Check prerequisites for Rule 1 Chunking BEFORE moving arrays
        let has_asr = completed_processors.iter().any(|p| p == "asr");
        let has_asrx = completed_processors.iter().any(|p| p == "asrx");
        let has_cut = completed_processors.iter().any(|p| p == "cut");

        // Update processor arrays in job record
        self.db
            .update_job_processors_arrays(job_id, completed_processors, failed_processors)
            .await?;

        if all_completed && !any_failed {
            // 🚀 P1 Trigger: Rule 1 Chunking
            if has_asr && has_asrx {
                info!("📝 Prerequisites met for Rule 1 Chunking. Starting ingestion...");
                let db_clone = self.db.clone();
                let uuid_clone = uuid.to_string();
                tokio::spawn(async move {
                    match db_clone.get_video_by_uuid(&uuid_clone).await {
                        Ok(Some(video)) => {
                            let fps = video.fps;
                            match rule1_ingest::ingest_rule1(db_clone.pool(), &uuid_clone, fps)
                                .await
                            {
                                Ok(count) => info!(
                                    "✅ Rule 1 Ingestion completed: {} chunks inserted.",
                                    count
                                ),
                                Err(e) => error!("❌ Rule 1 Ingestion failed: {}", e),
                            }
                        }
                        Ok(None) => error!("Video not found for chunking: {}", uuid_clone),
                        Err(e) => error!("Failed to get video info for chunking: {}", e),
                    }
                });
            }

            // 🚀 P1 Trigger: Rule 3 Scene Chunking
            if has_cut && has_asr {
                info!("📝 Prerequisites met for Rule 3 Scene Chunking. Starting ingestion...");
                let db_clone = self.db.clone();
                let uuid_clone = uuid.to_string();
                tokio::spawn(async move {
                    match rule3_ingest::ingest_rule3(db_clone.pool(), &uuid_clone).await {
                        Ok(count) => info!(
                            "✅ Rule 3 Scene Ingestion completed: {} scenes processed.",
                            count
                        ),
                        Err(e) => error!("❌ Rule 3 Scene Ingestion failed: {}", e),
                    }
                });
            }

            self.db
                .update_job_status(job_id, MonitorJobStatus::Completed)
                .await?;

            self.db
                .update_video_status(uuid, VideoStatus::Completed)
                .await?;

            self.redis
                .update_worker_job_status(uuid, job_id, "completed", None, completed_count, 7)
                .await?;

            self.redis.delete_worker_job(uuid).await?;

            info!("Job {} completed successfully", job_id);
        } else if any_failed {
            self.db
                .update_job_status(job_id, MonitorJobStatus::Failed)
                .await?;

            self.db
                .update_video_status(uuid, VideoStatus::Failed)
                .await?;

            self.redis
                .update_worker_job_status(uuid, job_id, "failed", None, completed_count, 7)
                .await?;

            warn!("Job {} completed with failures", job_id);
        } else {
            self.redis
                .update_worker_job_status(uuid, job_id, "running", None, completed_count, 7)
                .await?;
        }

        Ok(())
    }

    pub async fn shutdown(&self) {
        info!("Shutting down worker...");
        self.processor_pool.cancel_all().await;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_worker_config() {
        let config = WorkerConfig::default();
        assert!(config.enabled);
        assert!(config.max_concurrent >= 1);
    }
}
