use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::time::Duration;

use super::executor::PythonExecutor;

const FACE_RECOGNITION_TIMEOUT: Duration = Duration::from_secs(10800); // 3 hours for recognition

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FaceRecognitionResult {
    pub frame_count: u64,
    pub fps: f64,
    pub frames: Vec<FaceRecognitionFrame>,
    pub recognized_faces: Vec<RecognizedFace>,
    pub face_clusters: Vec<FaceCluster>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FaceRecognitionFrame {
    pub frame: u64,
    pub timestamp: f64,
    pub faces: Vec<RecognizedFaceDetection>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RecognizedFaceDetection {
    pub face_id: Option<String>,
    pub x: i32,
    pub y: i32,
    pub width: i32,
    pub height: i32,
    pub confidence: f32,
    pub embedding: Option<Vec<f32>>,
    pub attributes: Option<FaceAttributes>,
    pub identity: Option<FaceIdentity>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FaceAttributes {
    pub age: Option<u8>,
    pub gender: Option<String>,
    pub emotion: Option<String>,
    pub glasses: Option<bool>,
    pub mask: Option<bool>,
    pub pose: Option<FacePose>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FacePose {
    pub yaw: f32,
    pub pitch: f32,
    pub roll: f32,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FaceIdentity {
    pub name: Option<String>,
    pub confidence: f32,
    pub database_id: Option<String>,
    pub metadata: Option<serde_json::Value>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RecognizedFace {
    pub face_id: String,
    pub embedding: Vec<f32>,
    pub first_seen: f64,
    pub last_seen: f64,
    pub total_appearances: u32,
    pub attributes: Option<FaceAttributes>,
    pub identities: Vec<FaceIdentity>,
    pub cluster_id: Option<String>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FaceCluster {
    pub cluster_id: String,
    pub face_ids: Vec<String>,
    pub centroid: Vec<f32>,
    pub size: u32,
    pub representative_face_id: Option<String>,
    pub metadata: Option<serde_json::Value>,
}

pub async fn process_face_recognition(
    video_path: &str,
    output_path: &str,
    uuid: Option<&str>,
    enable_recognition: bool,
    enable_tracking: bool,
    enable_clustering: bool,
) -> Result<FaceRecognitionResult> {
    let executor = PythonExecutor::new()?;
    let script_path = executor.script_path("face_recognition_processor.py");

    tracing::info!(
        "[FACE_RECOGNITION] Starting face recognition: {}",
        video_path
    );

    if !script_path.exists() {
        tracing::warn!("[FACE_RECOGNITION] Script not found, returning empty result");
        return Ok(FaceRecognitionResult {
            frame_count: 0,
            fps: 0.0,
            frames: vec![],
            recognized_faces: vec![],
            face_clusters: vec![],
        });
    }

    let args = vec![
        video_path,
        output_path,
        if enable_recognition { "1" } else { "0" },
        if enable_tracking { "1" } else { "0" },
        if enable_clustering { "1" } else { "0" },
    ];

    executor
        .run(
            "face_recognition_processor.py",
            &args,
            uuid,
            "FACE_RECOGNITION",
            Some(FACE_RECOGNITION_TIMEOUT),
        )
        .await
        .with_context(|| format!("Failed to run {:?}", script_path))?;

    let json_str =
        std::fs::read_to_string(output_path).context("Failed to read FACE_RECOGNITION output")?;

    let result: FaceRecognitionResult =
        serde_json::from_str(&json_str).context("Failed to parse FACE_RECOGNITION output")?;

    tracing::info!(
        "[FACE_RECOGNITION] Result: {} frames, {} recognized faces, {} clusters",
        result.frames.len(),
        result.recognized_faces.len(),
        result.face_clusters.len()
    );

    Ok(result)
}

pub async fn register_face(
    image_path: &str,
    name: &str,
    metadata: Option<serde_json::Value>,
) -> Result<FaceRegistrationResult> {
    let executor = PythonExecutor::new()?;
    let script_path = executor.script_path("face_registration.py");

    tracing::info!("[FACE_REGISTRATION] Registering face: {}", name);

    if !script_path.exists() {
        anyhow::bail!("Face registration script not found");
    }

    let output_path = format!("/tmp/face_registration_{}.json", uuid::Uuid::new_v4());

    // Handle metadata separately to avoid lifetime issues
    let meta_temp_file = metadata.as_ref().map(|meta| {
        let meta_path = format!("/tmp/face_metadata_{}.json", uuid::Uuid::new_v4());
        std::fs::write(&meta_path, serde_json::to_string(meta).unwrap()).unwrap();
        meta_path
    });

    // Build arguments - use output_path as database path so Python writes there
    let mut args = vec![
        image_path.to_string(),
        output_path.clone(),
        name.to_string(),
    ];

    // Add database parameter (point to same output for now)
    let database_path = output_path.clone();
    args.push("--database".to_string());
    args.push(database_path.clone());

    if let Some(ref meta_path) = meta_temp_file {
        args.push("--metadata".to_string());
        args.push(meta_path.clone());
    }

    let args_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect();
    executor
        .run(
            "face_registration.py",
            &args_refs,
            None,
            "FACE_REGISTRATION",
            Some(Duration::from_secs(300)),
        )
        .await
        .with_context(|| format!("Failed to run {:?}", script_path))?;

    let json_str =
        std::fs::read_to_string(&output_path).context("Failed to read registration output")?;

    let result: FaceRegistrationResult =
        serde_json::from_str(&json_str).context("Failed to parse registration output")?;

    // Clean up temp files
    let _ = std::fs::remove_file(&output_path);
    if let Some(meta_path) = meta_temp_file {
        let _ = std::fs::remove_file(&meta_path);
    }

    tracing::info!("[FACE_REGISTRATION] Registered face: {}", result.face_id);

    Ok(result)
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FaceRegistrationResult {
    pub face_id: String,
    pub embedding: Vec<f32>,
    pub attributes: Option<FaceAttributes>,
    pub success: bool,
    pub message: String,
}

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

    #[test]
    fn test_face_recognition_result_serialization() {
        let result = FaceRecognitionResult {
            frame_count: 100,
            fps: 30.0,
            frames: vec![FaceRecognitionFrame {
                frame: 0,
                timestamp: 0.0,
                faces: vec![RecognizedFaceDetection {
                    face_id: Some("face_1".to_string()),
                    x: 100,
                    y: 100,
                    width: 50,
                    height: 60,
                    confidence: 0.95,
                    embedding: Some(vec![0.1, 0.2, 0.3]),
                    attributes: Some(FaceAttributes {
                        age: Some(30),
                        gender: Some("male".to_string()),
                        emotion: Some("neutral".to_string()),
                        glasses: Some(false),
                        mask: Some(false),
                        pose: Some(FacePose {
                            yaw: 0.1,
                            pitch: 0.2,
                            roll: 0.3,
                        }),
                    }),
                    identity: Some(FaceIdentity {
                        name: Some("John Doe".to_string()),
                        confidence: 0.85,
                        database_id: Some("user_123".to_string()),
                        metadata: Some(serde_json::json!({"role": "employee"})),
                    }),
                }],
            }],
            recognized_faces: vec![RecognizedFace {
                face_id: "face_1".to_string(),
                embedding: vec![0.1, 0.2, 0.3],
                first_seen: 0.0,
                last_seen: 10.0,
                total_appearances: 5,
                attributes: Some(FaceAttributes {
                    age: Some(30),
                    gender: Some("male".to_string()),
                    emotion: Some("neutral".to_string()),
                    glasses: Some(false),
                    mask: Some(false),
                    pose: Some(FacePose {
                        yaw: 0.1,
                        pitch: 0.2,
                        roll: 0.3,
                    }),
                }),
                identities: vec![FaceIdentity {
                    name: Some("John Doe".to_string()),
                    confidence: 0.85,
                    database_id: Some("user_123".to_string()),
                    metadata: Some(serde_json::json!({"role": "employee"})),
                }],
                cluster_id: Some("cluster_1".to_string()),
            }],
            face_clusters: vec![FaceCluster {
                cluster_id: "cluster_1".to_string(),
                face_ids: vec!["face_1".to_string()],
                centroid: vec![0.1, 0.2, 0.3],
                size: 1,
                representative_face_id: Some("face_1".to_string()),
                metadata: Some(serde_json::json!({"description": "main person"})),
            }],
        };

        let json = serde_json::to_string(&result).unwrap();
        assert!(json.contains("face_1"));
        assert!(json.contains("John Doe"));
        assert!(json.contains("cluster_1"));
    }

    #[test]
    fn test_face_attributes_serialization() {
        let attributes = FaceAttributes {
            age: Some(25),
            gender: Some("female".to_string()),
            emotion: Some("happy".to_string()),
            glasses: Some(true),
            mask: Some(false),
            pose: Some(FacePose {
                yaw: -0.1,
                pitch: 0.05,
                roll: 0.02,
            }),
        };

        let json = serde_json::to_string(&attributes).unwrap();
        assert!(json.contains("\"age\":25"));
        assert!(json.contains("\"gender\":\"female\""));
        assert!(json.contains("\"emotion\":\"happy\""));
    }

    #[test]
    fn test_face_identity_serialization() {
        let identity = FaceIdentity {
            name: Some("Alice Smith".to_string()),
            confidence: 0.92,
            database_id: Some("employee_456".to_string()),
            metadata: Some(serde_json::json!({
                "department": "engineering",
                "position": "senior developer"
            })),
        };

        let json = serde_json::to_string(&identity).unwrap();
        assert!(json.contains("Alice Smith"));
        assert!(json.contains("\"confidence\":0.92"));
        assert!(json.contains("engineering"));
    }
}
