use sqlx::PgPool;
use tokio::time::{sleep, Duration};
use tracing;
use uuid::Uuid;

use crate::core::chunk;

pub struct JobWorker {
    pool: PgPool,
    poll_interval: Duration,
}

impl JobWorker {
    pub fn new(pool: PgPool, poll_interval_secs: u64) -> Self {
        Self {
            pool,
            poll_interval: Duration::from_secs(poll_interval_secs),
        }
    }

    pub async fn run(&self) {
        tracing::info!(
            "🤖 Job Worker started (Polling every {}s)",
            self.poll_interval.as_secs()
        );

        loop {
            match self.process_next_job().await {
                Ok(has_work) => {
                    if !has_work {
                        // No work found, wait before polling again
                        sleep(self.poll_interval).await;
                    }
                    // If we processed a job, loop immediately to check for more
                }
                Err(e) => {
                    tracing::error!("❌ Job Worker error: {}", e);
                    sleep(Duration::from_secs(5)).await;
                }
            }
        }
    }

    async fn process_next_job(&self) -> anyhow::Result<bool> {
        // 1. Fetch a QUEUED job
        // We use a transaction to ensure no two workers pick the same job (atomic update)
        let job_row: Option<(String, String, String, String, String, i64)> = sqlx::query_as(
            r#"
            UPDATE dev.jobs 
            SET status = 'RUNNING', updated_at = NOW()
            WHERE id = (
                SELECT id FROM dev.jobs 
                WHERE status = 'QUEUED' 
                ORDER BY created_at ASC 
                LIMIT 1 
                FOR UPDATE SKIP LOCKED
            )
            RETURNING id::text, asset_uuid, rule, status, processor_list, total_frames
            "#,
        )
        .fetch_optional(&self.pool)
        .await?;

        if let Some((job_id, asset_uuid, rule, _status, _processors, total_frames)) = job_row {
            let job_uuid =
                Uuid::parse_str(&job_id).map_err(|e| anyhow::anyhow!("Invalid job UUID: {}", e))?;

            tracing::info!(
                "🚀 Processing Job {} for Asset {} (Rule: {})",
                job_id,
                asset_uuid,
                rule
            );

            // 2. Execute Logic based on Rule
            let result = match rule.as_str() {
                "rule1" => {
                    let fps = self.get_asset_fps(&asset_uuid).await?;
                    chunk::rule1_ingest::ingest_rule1(&self.pool, &asset_uuid, fps).await
                }
                _ => {
                    tracing::warn!("Unknown rule type: {}", rule);
                    Ok(0)
                }
            };

            // 3. Update Job Status
            match result {
                Ok(chunk_count) => {
                    tracing::info!(
                        "✅ Job {} completed. Processed {} items.",
                        job_id,
                        chunk_count
                    );

                    sqlx::query!(
                        "UPDATE dev.jobs SET status = 'COMPLETED', processed_frames = total_frames, updated_at = NOW() WHERE id = $1",
                        job_uuid
                    )
                    .execute(&self.pool)
                    .await?;

                    sqlx::query!(
                        "UPDATE dev.videos SET processing_status = 'COMPLETED' WHERE uuid = $1",
                        asset_uuid
                    )
                    .execute(&self.pool)
                    .await?;
                }
                Err(e) => {
                    tracing::error!("❌ Job {} failed: {}", job_id, e);
                    let err_msg = e.to_string();
                    let safe_msg = if err_msg.len() > 500 {
                        &err_msg[..500]
                    } else {
                        &err_msg
                    };

                    sqlx::query!(
                        "UPDATE dev.jobs SET status = 'FAILED', error_message = $2, updated_at = NOW() WHERE id = $1",
                        job_uuid,
                        safe_msg
                    )
                    .execute(&self.pool)
                    .await?;
                }
            }
            return Ok(true); // Processed a job
        }

        Ok(false) // No job found
    }

    async fn get_asset_fps(&self, uuid: &str) -> anyhow::Result<f64> {
        let fps: Option<f64> =
            sqlx::query_scalar("SELECT (metadata->>'fps')::float FROM dev.videos WHERE uuid = $1")
                .bind(uuid)
                .fetch_optional(&self.pool)
                .await?;

        // Fallback to 29.97 if not found
        Ok(fps.unwrap_or(29.97))
    }
}
