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

use super::executor::PythonExecutor;

const POSE_TIMEOUT: Duration = Duration::from_secs(7200);

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PoseResult {
    pub frame_count: u64,
    pub fps: f64,
    pub frames: Vec<PoseFrame>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PoseFrame {
    pub frame: u64,
    pub timestamp: f64,
    pub persons: Vec<PersonPose>,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PersonPose {
    pub keypoints: Vec<Keypoint>,
    pub bbox: Bbox,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Keypoint {
    pub name: String,
    pub x: f32,
    pub y: f32,
    pub confidence: f32,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Bbox {
    pub x: i32,
    pub y: i32,
    pub width: i32,
    pub height: i32,
}

pub async fn process_pose(
    video_path: &str,
    output_path: &str,
    uuid: Option<&str>,
) -> Result<PoseResult> {
    let executor = PythonExecutor::new()?;
    let script_path = executor.script_path("pose_processor.py");

    tracing::info!("[POSE] Starting pose estimation: {}", video_path);

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

    executor
        .run(
            "pose_processor.py",
            &[video_path, output_path],
            uuid,
            "POSE",
            Some(POSE_TIMEOUT),
        )
        .await
        .with_context(|| format!("Failed to run {:?}", script_path))?;

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

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

    tracing::info!("[POSE] Result: {} frames", result.frames.len());

    Ok(result)
}

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

    #[test]
    fn test_pose_result_serialization() {
        let result = PoseResult {
            frame_count: 100,
            fps: 30.0,
            frames: vec![PoseFrame {
                frame: 0,
                timestamp: 0.0,
                persons: vec![PersonPose {
                    keypoints: vec![Keypoint {
                        name: "nose".to_string(),
                        x: 100.0,
                        y: 50.0,
                        confidence: 0.9,
                    }],
                    bbox: Bbox {
                        x: 80,
                        y: 30,
                        width: 40,
                        height: 80,
                    },
                }],
            }],
        };

        let json = serde_json::to_string(&result).unwrap();
        assert!(json.contains("nose"));
        assert!(json.contains("\"confidence\":0.9"));
    }

    #[test]
    fn test_pose_result_deserialization() {
        let json = r#"{
            "frame_count": 50,
            "fps": 25.0,
            "frames": [
                {
                    "frame": 30,
                    "timestamp": 1.2,
                    "persons": [
                        {
                            "keypoints": [{"name": "left_eye", "x": 100.5, "y": 50.2, "confidence": 0.85}],
                            "bbox": {"x": 90, "y": 40, "width": 20, "height": 30}
                        }
                    ]
                }
            ]
        }"#;

        let result: PoseResult = serde_json::from_str(json).unwrap();
        assert_eq!(result.frame_count, 50);
        assert_eq!(result.frames.len(), 1);
        assert_eq!(result.frames[0].persons[0].keypoints[0].name, "left_eye");
    }

    #[test]
    fn test_pose_result_empty_frames() {
        let result = PoseResult {
            frame_count: 0,
            fps: 0.0,
            frames: vec![],
        };
        assert!(result.frames.is_empty());
    }

    #[test]
    fn test_keypoint_confidence() {
        let kp = Keypoint {
            name: "test".to_string(),
            x: 0.0,
            y: 0.0,
            confidence: 0.75,
        };
        assert!(kp.confidence >= 0.0 && kp.confidence <= 1.0);
    }

    #[test]
    fn test_bbox_dimensions() {
        let bbox = Bbox {
            x: 10,
            y: 20,
            width: 50,
            height: 100,
        };
        assert!(bbox.width > 0);
        assert!(bbox.height > 0);
    }
}
