use crate::core::config::OUTPUT_DIR;
use crate::core::llm::client::generate_5w1h_summary;
use anyhow::{Context, Result};
use serde::Deserialize;
use sqlx::PgPool;
use std::fs;
use tracing::{info, warn};

#[derive(Debug, Deserialize)]
struct CutScene {
    scene_number: u32,
    start_frame: u64,
    end_frame: u64,
    start_time: f64,
    end_time: f64,
}

#[derive(Debug, Deserialize)]
struct CutResult {
    scenes: Vec<CutScene>,
}

#[derive(Debug, Deserialize)]
struct AsrSegment {
    start: f64,
    end: f64,
    text: String,
}

/// Executes Rule 3 Ingestion: Scene-based Chunking with LLM 5W1H+ Summary.
/// 1. Reads CUT data to identify scenes.
/// 2. Aggregates Rule 1 (Sentence) chunks falling within each scene.
/// 3. Calls LLM to generate 5W1H+ summary.
/// 4. Inserts parent chunks into `dev.chunks`.
pub async fn ingest_rule3(pool: &PgPool, asset_uuid: &str) -> Result<usize> {
    let cut_path = format!("{}/{}.cut.json", *OUTPUT_DIR, asset_uuid);
    let asr_path = format!("{}/{}.asr.json", *OUTPUT_DIR, asset_uuid);

    // 1. Load CUT and ASR data
    let cut_content = fs::read_to_string(&cut_path)
        .with_context(|| format!("Failed to read CUT file: {}", cut_path))?;
    let cut_result: CutResult = serde_json::from_str(&cut_content).context("Invalid CUT JSON")?;

    let asr_segments: Vec<AsrSegment> = match fs::read_to_string(&asr_path) {
        Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
        Err(_) => {
            warn!("ASR file not found, proceeding with empty transcript for scenes");
            vec![]
        }
    };

    let mut count = 0;
    let mut tx = pool.begin().await?;

    // 2. Process each scene
    for scene in &cut_result.scenes {
        let chunk_id = format!("scene_{}", scene.scene_number);

        // Aggregate text from Rule 1 chunks
        let mut scene_text = String::new();
        let mut child_ids: Vec<String> = Vec::new();

        for seg in &asr_segments {
            if seg.start >= scene.start_time && seg.end <= scene.end_time {
                scene_text.push_str(&seg.text);
                scene_text.push(' ');
                // We'll look up the chunk_id from Rule 1 later if needed,
                // but for now we just group by text overlap.
                // A better approach is to query Rule 1 table for this range.
            }
        }

        // Query Rule 1 table for better linking
        let rule1_rows: Vec<(String,)> = sqlx::query_as(
            r#"
            SELECT id::text FROM chunks_rule1
            WHERE asset_uuid = $1
            AND start_frame >= $2
            AND end_frame <= $3
            "#,
        )
        .bind(asset_uuid)
        .bind(scene.start_frame as i64)
        .bind(scene.end_frame as i64)
        .fetch_all(&mut *tx)
        .await?;

        for row in &rule1_rows {
            child_ids.push(row.0.clone());
        }

        // Fallback to simple aggregation if query didn't get text (due to frame boundaries)
        if scene_text.is_empty() {
            // Try to grab text directly if rule1 table doesn't have it or boundaries differ
            // But rule1 table has start_frame/end_frame which should match.
            // Let's re-query text directly.
        }

        let texts: Vec<String> = sqlx::query_scalar(
            r#"
            SELECT content FROM chunks_rule1
            WHERE asset_uuid = $1
            AND start_frame >= $2
            AND end_frame <= $3
            ORDER BY start_frame ASC
            "#,
        )
        .bind(asset_uuid)
        .bind(scene.start_frame as i64)
        .bind(scene.end_frame as i64)
        .fetch_all(&mut *tx)
        .await?;

        let aggregated_text = texts.join(" ");

        // 3. Call LLM for Summary
        let summary = if !aggregated_text.is_empty() {
            match generate_5w1h_summary(&aggregated_text).await {
                Ok(s) => s,
                Err(e) => {
                    warn!("LLM Summary failed for scene {}: {}", scene.scene_number, e);
                    "LLM Error".to_string()
                }
            }
        } else {
            "No Audio".to_string()
        };

        info!(
            "Scene {}: {} -> {} ({} sentences)",
            scene.scene_number,
            scene.start_time,
            scene.end_time,
            texts.len()
        );

        // 4. Insert into dev.chunks
        let fps_query: Option<f64> = sqlx::query_scalar("SELECT fps FROM videos WHERE uuid = $1")
            .bind(asset_uuid)
            .fetch_optional(&mut *tx)
            .await?;
        let fps = fps_query.unwrap_or(29.97);

        // Prepare metadata JSON
        let metadata = serde_json::json!({
            "type": "scene",
            "scene_number": scene.scene_number
        });

        sqlx::query(
            r#"
            INSERT INTO chunks (
                uuid, chunk_id, chunk_index, chunk_type,
                start_time, end_time, fps, start_frame, end_frame,
                content, text_content, summary_text, metadata, child_chunk_ids
            ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
            ON CONFLICT (uuid, chunk_id) DO NOTHING
            "#,
        )
        .bind(asset_uuid)
        .bind(&chunk_id)
        .bind(scene.scene_number as i32)
        .bind("cut") // Chunk type
        .bind(scene.start_time)
        .bind(scene.end_time)
        .bind(fps)
        .bind(scene.start_frame as i64)
        .bind(scene.end_frame as i64)
        .bind(&metadata) // Content JSON
        .bind(&aggregated_text) // Text content
        .bind(&summary) // Summary
        .bind(&metadata) // Metadata
        .bind(&child_ids) // Child IDs
        .execute(&mut *tx)
        .await?;

        count += 1;
    }

    tx.commit().await?;
    Ok(count)
}
