use axum::{
    extract::State,
    http::StatusCode,
    response::Json,
    routing::{get, post},
    Router,
};
use serde::{Deserialize, Serialize};
use sqlx::Row;
use std::path::PathBuf;

use crate::api::server::AppState;

pub fn identity_agent_routes() -> Router<AppState> {
    Router::new()
        .route("/api/v1/agents/identity/analyze", post(analyze_identity))
        .route("/api/v1/agents/identity/suggest", post(suggest_merges))
        .route("/api/v1/agents/identity/status", get(get_identity_status))
        .route(
            "/api/v1/agents/suggest/clustering",
            post(suggest_clustering),
        )
        .route("/api/v1/agents/suggest/merge", post(suggest_merge))
}

#[derive(Debug, Deserialize)]
pub struct AnalyzeIdentityRequest {
    pub file_uuid: String,
    pub auto_merge_threshold: Option<f64>,
    pub llm_threshold: Option<f64>,
    pub use_llm: Option<bool>,
    pub model: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct AnalyzeIdentityResponse {
    pub success: bool,
    pub file_uuid: String,
    pub identities: Vec<IdentityResult>,
    pub processing_status: IdentityProcessingStatus,
}

#[derive(Debug, Serialize)]
pub struct IdentityResult {
    pub identity_id: String,
    pub person_ids: Vec<String>,
    pub speaker_ids: Vec<String>,
    pub confidence: f64,
    pub evidence: IdentityEvidence,
    pub reasoning: String,
}

#[derive(Debug, Serialize)]
pub struct IdentityEvidence {
    pub face_similarity: Option<f64>,
    pub speaker_overlap: f64,
    pub time_overlap: f64,
    pub frame_ratio: f64,
}

#[derive(Debug, Serialize)]
pub struct IdentityProcessingStatus {
    pub status: String,
    pub persons_analyzed: i32,
    pub identities_created: i32,
    pub merges_suggested: i32,
}

#[derive(Debug, Deserialize)]
pub struct SuggestMergesRequest {
    pub file_uuid: String,
}

#[derive(Debug, Serialize)]
pub struct SuggestMergesResponse {
    pub success: bool,
    pub file_uuid: String,
    pub merge_suggestions: Vec<MergeSuggestion>,
    pub naming_suggestions: Vec<NamingSuggestion>,
}

#[derive(Debug, Serialize)]
pub struct MergeSuggestion {
    pub target_person_id: String,
    pub source_person_ids: Vec<String>,
    pub confidence: f64,
    pub reasons: Vec<String>,
    pub action: String,
}

#[derive(Debug, Serialize)]
pub struct NamingSuggestion {
    pub person_id: String,
    pub suggested_name: String,
    pub confidence: f64,
    pub reasoning: String,
}

#[derive(Debug, Serialize)]
pub struct IdentityStatusResponse {
    pub success: bool,
    pub agent_name: String,
    pub version: String,
    pub supported_models: Vec<String>,
    pub default_thresholds: DefaultThresholds,
}

#[derive(Debug, Serialize)]
pub struct DefaultThresholds {
    pub auto_merge_threshold: f64,
    pub llm_threshold: f64,
    pub face_similarity_threshold: f64,
}

async fn analyze_identity(
    State(state): State<AppState>,
    Json(req): Json<AnalyzeIdentityRequest>,
) -> Result<Json<AnalyzeIdentityResponse>, (StatusCode, String)> {
    let output_dir = std::env::var("MOMENTRY_OUTPUT_DIR")
        .unwrap_or_else(|_| "/Users/accusys/momentry/output".to_string());

    let video_dir = PathBuf::from(&output_dir).join(&req.file_uuid);

    let face_clustered_path = video_dir.join(format!("{}.face_clustered.json", req.file_uuid));
    let asrx_path = video_dir.join(format!("{}.asrx.json", req.file_uuid));

    if !face_clustered_path.exists() {
        return Err((
            StatusCode::NOT_FOUND,
            format!("Face clustered data not found for video: {}", req.file_uuid),
        ));
    }

    let face_data: serde_json::Value = std::fs::read_to_string(&face_clustered_path)
        .map_err(|e| {
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to read face data: {}", e),
            )
        })?
        .parse()
        .map_err(|e| {
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to parse face data: {}", e),
            )
        })?;

    let asrx_data: Option<serde_json::Value> = if asrx_path.exists() {
        Some(
            std::fs::read_to_string(&asrx_path)
                .map_err(|e| {
                    (
                        StatusCode::INTERNAL_SERVER_ERROR,
                        format!("Failed to read asrx data: {}", e),
                    )
                })?
                .parse()
                .map_err(|e| {
                    (
                        StatusCode::INTERNAL_SERVER_ERROR,
                        format!("Failed to parse asrx data: {}", e),
                    )
                })?,
        )
    } else {
        None
    };

    let persons = extract_persons_from_face_data(&face_data);
    let speakers = extract_speakers_from_asrx_data(&asrx_data);

    let identities = analyze_person_speaker_overlap(&persons, &speakers);

    let processing_status = IdentityProcessingStatus {
        status: "completed".to_string(),
        persons_analyzed: persons.len() as i32,
        identities_created: identities.len() as i32,
        merges_suggested: 0,
    };

    Ok(Json(AnalyzeIdentityResponse {
        success: true,
        file_uuid: req.file_uuid.clone(),
        identities,
        processing_status,
    }))
}

async fn suggest_merges(
    State(state): State<AppState>,
    Json(req): Json<SuggestMergesRequest>,
) -> Result<Json<SuggestMergesResponse>, (StatusCode, String)> {
    let analyze_req = AnalyzeIdentityRequest {
        file_uuid: req.file_uuid.clone(),
        auto_merge_threshold: Some(0.8),
        llm_threshold: Some(0.5),
        use_llm: Some(true),
        model: Some("gemma4".to_string()),
    };

    let analyze_result = analyze_identity(State(state), Json(analyze_req)).await?;

    let merge_suggestions: Vec<MergeSuggestion> = analyze_result
        .identities
        .iter()
        .filter(|id| id.person_ids.len() > 1)
        .map(|id| {
            let reasons = vec![
                format!(
                    "Shared speaker overlap: {:.0}%",
                    id.evidence.speaker_overlap * 100.0
                ),
                format!(
                    "Face similarity: {:.2}",
                    id.evidence.face_similarity.unwrap_or(0.0)
                ),
                format!("Confidence: {:.2}", id.confidence),
            ];

            MergeSuggestion {
                target_person_id: id.person_ids[0].clone(),
                source_person_ids: id.person_ids[1..].to_vec(),
                confidence: id.confidence,
                reasons,
                action: if id.confidence > 0.8 {
                    "auto_apply"
                } else {
                    "review_needed"
                }
                .to_string(),
            }
        })
        .collect();

    Ok(Json(SuggestMergesResponse {
        success: true,
        file_uuid: req.file_uuid,
        merge_suggestions,
        naming_suggestions: vec![],
    }))
}

async fn get_identity_status() -> Result<Json<IdentityStatusResponse>, (StatusCode, String)> {
    Ok(Json(IdentityStatusResponse {
        success: true,
        agent_name: "Identity Agent".to_string(),
        version: "1.0.0".to_string(),
        supported_models: vec!["gemma4".to_string(), "qwen3".to_string()],
        default_thresholds: DefaultThresholds {
            auto_merge_threshold: 0.8,
            llm_threshold: 0.5,
            face_similarity_threshold: 0.3,
        },
    }))
}

fn extract_persons_from_face_data(face_data: &serde_json::Value) -> Vec<PersonData> {
    let mut persons = Vec::new();

    if let Some(frames) = face_data.get("frames").and_then(|f| f.as_array()) {
        let mut person_frames_map: std::collections::HashMap<String, Vec<i32>> =
            std::collections::HashMap::new();

        for frame in frames {
            if let Some(frame_num) = frame.get("frame").and_then(|f| f.as_i64()) {
                if let Some(person_id) = frame.get("person_id").and_then(|p| p.as_str()) {
                    person_frames_map
                        .entry(person_id.to_string())
                        .or_insert_with(Vec::new)
                        .push(frame_num as i32);
                }
            }
        }

        for (person_id, frames) in person_frames_map {
            persons.push(PersonData {
                person_id,
                frames,
                avg_embedding: None,
            });
        }
    }

    persons
}

fn extract_speakers_from_asrx_data(asrx_data: &Option<serde_json::Value>) -> Vec<SpeakerData> {
    let mut speakers = Vec::new();

    if let Some(data) = asrx_data {
        if let Some(segments) = data.get("segments").and_then(|s| s.as_array()) {
            let mut speaker_segments_map: std::collections::HashMap<String, Vec<(f64, f64)>> =
                std::collections::HashMap::new();

            for segment in segments {
                if let Some(speaker_id) = segment.get("speaker").and_then(|s| s.as_str()) {
                    let start = segment.get("start").and_then(|s| s.as_f64()).unwrap_or(0.0);
                    let end = segment.get("end").and_then(|e| e.as_f64()).unwrap_or(0.0);

                    speaker_segments_map
                        .entry(speaker_id.to_string())
                        .or_insert_with(Vec::new)
                        .push((start, end));
                }
            }

            for (speaker_id, segments) in speaker_segments_map {
                speakers.push(SpeakerData {
                    speaker_id,
                    segments,
                });
            }
        }
    }

    speakers
}

fn analyze_person_speaker_overlap(
    persons: &[PersonData],
    speakers: &[SpeakerData],
) -> Vec<IdentityResult> {
    let mut identities = Vec::new();

    for (i, person) in persons.iter().enumerate() {
        let identity_id = format!("identity_{}", i + 1);

        let mut speaker_ids = Vec::new();
        let mut max_overlap: f64 = 0.0;

        for speaker in speakers {
            let overlap_frames = calculate_overlap(person, speaker);
            let overlap_ratio = overlap_frames as f64 / person.frames.len() as f64;

            if overlap_ratio > 0.5 {
                speaker_ids.push(speaker.speaker_id.clone());
                max_overlap = max_overlap.max(overlap_ratio);
            }
        }

        let confidence = if speaker_ids.len() > 0 {
            0.7 + max_overlap * 0.2
        } else {
            0.5
        };

        let reasoning = if speaker_ids.len() > 0 {
            format!(
                "Person has high overlap with speakers: {}",
                speaker_ids.join(", ")
            )
        } else {
            "Person has no speaker overlap".to_string()
        };

        identities.push(IdentityResult {
            identity_id,
            person_ids: vec![person.person_id.clone()],
            speaker_ids,
            confidence,
            evidence: IdentityEvidence {
                face_similarity: None,
                speaker_overlap: max_overlap,
                time_overlap: max_overlap,
                frame_ratio: person.frames.len() as f64 / 1000.0,
            },
            reasoning,
        });
    }

    identities
}

fn calculate_overlap(person: &PersonData, speaker: &SpeakerData) -> i32 {
    let mut overlap_count = 0;

    for frame_num in &person.frames {
        let frame_time = *frame_num as f64 / 23.976;

        for (start, end) in &speaker.segments {
            if frame_time >= *start && frame_time <= *end {
                overlap_count += 1;
                break;
            }
        }
    }

    overlap_count
}

#[derive(Debug, Deserialize)]
pub struct SuggestClusteringRequest {
    pub file_uuid: Option<String>,
    pub min_cluster_size: Option<usize>,
    pub similarity_threshold: Option<f64>,
}

#[derive(Debug, Serialize)]
pub struct SuggestClusteringResponse {
    pub success: bool,
    pub suggestions: Vec<ClusteringSuggestion>,
    pub total_unclustered: usize,
}

#[derive(Debug, Serialize)]
pub struct ClusteringSuggestion {
    pub cluster_id: String,
    pub face_count: usize,
    pub avg_confidence: f64,
    pub suggested_name: Option<String>,
    pub representative_face: Option<String>,
}

async fn suggest_clustering(
    State(state): State<AppState>,
    Json(req): Json<SuggestClusteringRequest>,
) -> Result<Json<SuggestClusteringResponse>, (StatusCode, String)> {
    let min_cluster_size = req.min_cluster_size.unwrap_or(3);

    let file_filter = match &req.file_uuid {
        Some(uuid) => format!("AND fc.file_uuid = '{}'", uuid),
        None => String::new(),
    };

    let query = format!(
        r#"
        SELECT fc.cluster_id, fc.file_uuid, fc.n_faces, fc.metadata
        FROM face_clusters fc
        WHERE fc.n_faces >= $1
        AND NOT EXISTS (
            SELECT 1 FROM identities i 
            WHERE i.metadata->>'cluster_id' = fc.cluster_id
        )
        {}
        ORDER BY fc.n_faces DESC
        "#,
        file_filter
    );

    let pool = state.db.pool();
    let rows = sqlx::query(&query)
        .bind(min_cluster_size as i64)
        .fetch_all(pool)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let suggestions: Vec<ClusteringSuggestion> = rows
        .into_iter()
        .map(|row| {
            let cluster_id: String = row.get("cluster_id");
            let n_faces: i32 = row.get("n_faces");
            let metadata: serde_json::Value =
                row.try_get("metadata").unwrap_or(serde_json::Value::Null);

            let avg_confidence = metadata
                .get("avg_confidence")
                .and_then(|v| v.as_f64())
                .unwrap_or(0.0);

            let representative_face = metadata
                .get("representative_face_id")
                .and_then(|v| v.as_str())
                .map(|s| s.to_string());

            ClusteringSuggestion {
                cluster_id,
                face_count: n_faces as usize,
                avg_confidence,
                suggested_name: None,
                representative_face,
            }
        })
        .collect();

    let total_unclustered: i64 = sqlx::query_scalar(
        r#"
        SELECT COUNT(*) FROM face_detections fd
        WHERE fd.identity_id IS NULL
        "#,
    )
    .fetch_one(pool)
    .await
    .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    Ok(Json(SuggestClusteringResponse {
        success: true,
        suggestions,
        total_unclustered: total_unclustered as usize,
    }))
}

#[derive(Debug, Deserialize)]
pub struct SuggestMergeRequest {
    pub identity_id: Option<String>,
    pub similarity_threshold: Option<f64>,
}

#[derive(Debug, Serialize)]
pub struct SuggestMergeResponse {
    pub success: bool,
    pub suggestions: Vec<IdentityMergeSuggestion>,
}

#[derive(Debug, Serialize)]
pub struct IdentityMergeSuggestion {
    pub source_identity_id: String,
    pub target_identity_id: String,
    pub source_name: String,
    pub target_name: String,
    pub similarity_score: f64,
    pub shared_files: usize,
    pub reason: String,
}

async fn suggest_merge(
    State(state): State<AppState>,
    Json(req): Json<SuggestMergeRequest>,
) -> Result<Json<SuggestMergeResponse>, (StatusCode, String)> {
    let similarity_threshold = req.similarity_threshold.unwrap_or(0.8);

    let identity_filter = match &req.identity_id {
        Some(id) => format!("AND i1.uuid = '{}' OR i2.uuid = '{}'", id, id),
        None => String::new(),
    };

    let query = format!(
        r#"
        SELECT 
            i1.uuid as source_uuid,
            i2.uuid as target_uuid,
            i1.name as source_name,
            i2.name as target_name,
            COUNT(DISTINCT fi1.file_uuid) as shared_files
        FROM identities i1
        JOIN identities i2 ON i1.id < i2.id
        LEFT JOIN file_identities fi1 ON fi1.identity_id = i1.id
        LEFT JOIN file_identities fi2 ON fi2.identity_id = i2.id AND fi1.file_uuid = fi2.file_uuid
        WHERE i1.identity_type = 'people'
        AND i2.identity_type = 'people'
        AND i1.id != i2.id
        {}
        GROUP BY i1.uuid, i2.uuid, i1.name, i2.name
        HAVING COUNT(DISTINCT fi1.file_uuid) > 0
        ORDER BY shared_files DESC
        LIMIT 50
        "#,
        identity_filter
    );

    let pool = state.db.pool();
    let rows = sqlx::query(&query)
        .fetch_all(pool)
        .await
        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    let suggestions: Vec<IdentityMergeSuggestion> = rows
        .into_iter()
        .filter_map(|row| {
            let shared_files: i64 = row.get("shared_files");
            if shared_files > 0 {
                let similarity = (shared_files as f64 / 10.0).min(1.0);
                if similarity >= similarity_threshold {
                    Some(IdentityMergeSuggestion {
                        source_identity_id: row.get("source_uuid"),
                        target_identity_id: row.get("target_uuid"),
                        source_name: row.get("source_name"),
                        target_name: row.get("target_name"),
                        similarity_score: similarity,
                        shared_files: shared_files as usize,
                        reason: format!(
                            "Share {} file(s) - similarity: {:.1}%",
                            shared_files,
                            similarity * 100.0
                        ),
                    })
                } else {
                    None
                }
            } else {
                None
            }
        })
        .collect();

    Ok(Json(SuggestMergeResponse {
        success: true,
        suggestions,
    }))
}

#[derive(Debug)]
struct PersonData {
    person_id: String,
    frames: Vec<i32>,
    avg_embedding: Option<Vec<f64>>,
}

#[derive(Debug)]
struct SpeakerData {
    speaker_id: String,
    segments: Vec<(f64, f64)>,
}
