use crate::core::time::FrameTime;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ChunkType {
    TimeBased,
    Sentence,
    Cut,
    Trace,
    Story,  // Parent chunk from story analysis
    Visual, // Visual object-based chunk from YOLO detection
}

impl ChunkType {
    pub fn as_str(&self) -> &'static str {
        match self {
            ChunkType::TimeBased => "time",
            ChunkType::Sentence => "sentence",
            ChunkType::Cut => "cut",
            ChunkType::Trace => "trace",
            ChunkType::Story => "story",
            ChunkType::Visual => "visual",
        }
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ChunkRule {
    Rule1, // 直接轉換
    Rule2, // 集合內容
}

/// 關鍵幀的物件列表
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct KeyframeObjects {
    /// 關鍵幀時間 (秒)
    pub timestamp: f64,
    /// 關鍵幀幀號
    pub frame_number: u64,
    /// 檢測到的物件
    pub objects: Vec<DetectedObject>,
}

/// 檢測到的物件
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DetectedObject {
    /// 物件類別名稱
    pub class_name: String,
    /// 物件類別 ID
    pub class_id: u32,
    /// 信心值 (0.0-1.0)
    pub confidence: f32,
    /// 邊界框 (x, y, width, height)
    pub bbox: Option<BoundingBox>,
    /// 出現次數 (在分片內)
    pub occurrence: u32,
}

/// 邊界框
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VisualChunkContent {
    pub start_time: f64,
    pub end_time: f64,
    pub keyframe_objects: Vec<KeyframeObjects>,
    pub dominant_objects: Vec<String>,
    pub object_relationships: Vec<(String, String, String)>, // (object1, relationship, object2)
    pub scene_description: Option<String>,
    pub metadata: VisualMetadata,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VisualMetadata {
    pub object_count: u32,
    pub unique_classes: Vec<String>,
    pub max_confidence: f32,
    pub avg_confidence: f32,
    pub spatial_density: f32, // objects per frame
}

impl ChunkRule {
    pub fn as_str(&self) -> &'static str {
        match self {
            ChunkRule::Rule1 => "rule_1",
            ChunkRule::Rule2 => "rule_2",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chunk {
    pub file_id: i32,
    pub uuid: String,
    pub chunk_id: String,
    pub chunk_index: u32,
    pub chunk_type: ChunkType,
    pub rule: ChunkRule,
    /// Frames per second (can be fractional, e.g., 29.97, 23.976)
    pub fps: f64,
    /// Start frame (0-based)
    pub start_frame: i64,
    /// End frame (exclusive)
    pub end_frame: i64,
    pub text_content: Option<String>,
    pub content: serde_json::Value,
    pub metadata: Option<serde_json::Value>,
    pub vector_id: Option<String>,
    pub frame_count: i32,
    pub pre_chunk_ids: Vec<i32>,
    pub parent_chunk_id: Option<String>, // For parent-child chunk hierarchy
    pub child_chunk_ids: Vec<String>,    // Child chunk IDs (for parent chunks)
    pub visual_stats: Option<serde_json::Value>,
}




        id: i64,
        video_id: i64,
        yolo_result: &crate::core::processor::yolo::YoloResult,
        min_frames_per_chunk: usize,
        similarity_threshold: f32,
    ) -> Vec<Self> {
        if yolo_result.frames.is_empty() {
            return vec![];
        }

        let mut chunks = Vec::new();
        let mut current_chunk_frames = Vec::new();
        let mut current_id = id;

        for (i, frame) in yolo_result.frames.iter().enumerate() {
            if current_chunk_frames.is_empty() {
                current_chunk_frames.push(frame);
                continue;
            }

            // Check similarity with last frame in current chunk
            let last_frame = current_chunk_frames.last().unwrap();
            let similarity = VisualChunkContent::frame_similarity(last_frame, frame);

            if similarity >= similarity_threshold && current_chunk_frames.len() < 100 {
                // Similar enough, add to current chunk
                current_chunk_frames.push(frame);
            } else {
                // Not similar enough or chunk too large, create new chunk
                if current_chunk_frames.len() >= min_frames_per_chunk {
                    if let Some(chunk) =
                        Self::create_chunk_from_frames(current_id, video_id, &current_chunk_frames)
                    {
                        chunks.push(chunk);
                        current_id += 1;
                    }
                }

                #[cfg(test)]
                mod tests {
                    use super::*;
                    use crate::core::processor::yolo::{YoloFrame, YoloObject, YoloResult};

                    #[test]
                    fn test_chunk_type_visual_serialization() {
                        let chunk_type = ChunkType::Visual;
                        let json = serde_json::to_string(&chunk_type).unwrap();
                        assert_eq!(json, "\"visual\"");

                        let deserialized: ChunkType = serde_json::from_str(&json).unwrap();
                        assert_eq!(deserialized, ChunkType::Visual);
                    }

                    #[test]
                    fn test_visual_chunk_creation() {
                        // Create a mock YOLO result
                        let yolo_result = YoloResult {
                            frame_count: 2,
                            fps: 30.0,
                            frames: vec![
                                YoloFrame {
                                    frame: 0,
                                    timestamp: 0.0,
                                    objects: vec![
                                        YoloObject {
                                            class_name: "person".to_string(),
                                            class_id: 0,
                                            x: 100,
                                            y: 200,
                                            width: 50,
                                            height: 100,
                                            confidence: 0.95,
                                        },
                                        YoloObject {
                                            class_name: "car".to_string(),
                                            class_id: 2,
                                            x: 300,
                                            y: 150,
                                            width: 80,
                                            height: 60,
                                            confidence: 0.87,
                                        },
                                    ],
                                },
                                YoloFrame {
                                    frame: 1,
                                    timestamp: 0.033, // 1/30 second
                                    objects: vec![YoloObject {
                                        class_name: "person".to_string(),
                                        class_id: 0,
                                        x: 110,
                                        y: 210,
                                        width: 52,
                                        height: 102,
                                        confidence: 0.92,
                                    }],
                                },
                            ],
                        };

                        // Create visual chunk from YOLO result
                        let chunk = Chunk::from_yolo_result(1, 100, &yolo_result, 0, 1).unwrap();

                        // Verify chunk properties
                        assert_eq!(chunk.id, 1);
                        assert_eq!(chunk.video_id, 100);
                        assert_eq!(chunk.chunk_type, ChunkType::Visual);
                        assert_eq!(chunk.start_time, 0.0);
                        assert_eq!(chunk.end_time, 0.033);

                        // Verify visual content
                        if let ChunkContent::Visual(content) = chunk.content {
                            assert_eq!(content.metadata.object_count, 3);
                            assert_eq!(content.metadata.unique_classes.len(), 2);
                            assert!(content
                                .metadata
                                .unique_classes
                                .contains(&"person".to_string()));
                            assert!(content.metadata.unique_classes.contains(&"car".to_string()));
                            assert_eq!(content.dominant_objects, vec!["person"]);
                            assert_eq!(content.keyframe_objects.len(), 2);
                        } else {
                            panic!("Expected Visual content type");
                        }
                    }

                    #[test]
                    fn test_visual_chunk_content_methods() {
                        let content = VisualChunkContent {
                            start_time: 0.0,
                            end_time: 5.0,
                            keyframe_objects: vec![KeyframeObjects {
                                frame: 0,
                                timestamp: 0.0,
                                objects: vec![
                                    DetectedObject {
                                        class_name: "person".to_string(),
                                        class_id: 0,
                                        bounding_box: BoundingBox {
                                            x: 100,
                                            y: 200,
                                            width: 50,
                                            height: 100,
                                        },
                                        confidence: 0.95,
                                    },
                                    DetectedObject {
                                        class_name: "car".to_string(),
                                        class_id: 2,
                                        bounding_box: BoundingBox {
                                            x: 300,
                                            y: 150,
                                            width: 80,
                                            height: 60,
                                        },
                                        confidence: 0.87,
                                    },
                                ],
                            }],
                            dominant_objects: vec!["person".to_string()],
                            object_relationships: vec![],
                            scene_description: Some("A person near a car".to_string()),
                            metadata: VisualMetadata {
                                object_count: 2,
                                unique_classes: vec!["person".to_string(), "car".to_string()],
                                max_confidence: 0.95,
                                avg_confidence: 0.91,
                                spatial_density: 2.0,
                            },
                        };

                        // Test summary method
                        let summary = content.summary();
                        assert!(summary.contains("Visual chunk from 0.0s to 5.0s"));
                        assert!(summary.contains("person"));

                        // Test contains_object method
                        assert!(content.contains_object("person"));
                        assert!(content.contains_object("car"));
                        assert!(!content.contains_object("dog"));

                        // Test high_confidence_objects method
                        let high_conf_objects = content.high_confidence_objects(0.9);
                        assert_eq!(high_conf_objects.len(), 1);
                        assert_eq!(high_conf_objects[0].class_name, "person");
                    }

                    #[test]
                    fn test_frame_similarity() {
                        let frame1 = YoloFrame {
                            frame: 0,
                            timestamp: 0.0,
                            objects: vec![
                                YoloObject {
                                    class_name: "person".to_string(),
                                    class_id: 0,
                                    x: 100,
                                    y: 200,
                                    width: 50,
                                    height: 100,
                                    confidence: 0.95,
                                },
                                YoloObject {
                                    class_name: "car".to_string(),
                                    class_id: 2,
                                    x: 300,
                                    y: 150,
                                    width: 80,
                                    height: 60,
                                    confidence: 0.87,
                                },
                            ],
                        };

                        let frame2 = YoloFrame {
                            frame: 1,
                            timestamp: 0.033,
                            objects: vec![
                                YoloObject {
                                    class_name: "person".to_string(),
                                    class_id: 0,
                                    x: 110,
                                    y: 210,
                                    width: 52,
                                    height: 102,
                                    confidence: 0.92,
                                },
                                YoloObject {
                                    class_name: "car".to_string(),
                                    class_id: 2,
                                    x: 310,
                                    y: 155,
                                    width: 82,
                                    height: 62,
                                    confidence: 0.85,
                                },
                            ],
                        };

                        let frame3 = YoloFrame {
                            frame: 2,
                            timestamp: 0.066,
                            objects: vec![YoloObject {
                                class_name: "dog".to_string(),
                                class_id: 16,
                                x: 150,
                                y: 250,
                                width: 40,
                                height: 60,
                                confidence: 0.78,
                            }],
                        };

                        // Test similar frames (same objects)
                        let similarity_same =
                            VisualChunkContent::frame_similarity(&frame1, &frame2);
                        assert!((similarity_same - 1.0).abs() < 0.001);

                        // Test dissimilar frames (different objects)
                        let similarity_diff =
                            VisualChunkContent::frame_similarity(&frame1, &frame3);
                        assert!((similarity_diff - 0.0).abs() < 0.001);

                        // Test empty frames
                        let empty_frame = YoloFrame {
                            frame: 3,
                            timestamp: 0.1,
                            objects: vec![],
                        };
                        let similarity_empty =
                            VisualChunkContent::frame_similarity(&empty_frame, &empty_frame);
                        assert!((similarity_empty - 1.0).abs() < 0.001);

                        let similarity_mixed =
                            VisualChunkContent::frame_similarity(&empty_frame, &frame1);
                        assert!((similarity_mixed - 0.0).abs() < 0.001);
                    }
                }
                current_chunk_frames = vec![frame];
            }
        }

        // Handle last chunk
        if current_chunk_frames.len() >= min_frames_per_chunk {
            if let Some(chunk) =
                Self::create_chunk_from_frames(current_id, video_id, &current_chunk_frames)
            {
                chunks.push(chunk);
            }
        }

        chunks
    }

    fn create_chunk_from_frames(
        id: i64,
        video_id: i64,
        frames: &[&crate::core::processor::yolo::YoloFrame],
    ) -> Option<Self> {
        if frames.is_empty() {
            return None;
        }

        // Simple conversion - could use the from_yolo_result method
        let start_frame = frames.first().unwrap().frame;
        let end_frame = frames.last().unwrap().frame;
        let dummy_yolo_result = crate::core::processor::yolo::YoloResult {
            frame_count: frames.len() as u64,
            fps: 0.0, // Not used in this context
            frames: frames.iter().map(|f| (*f).clone()).collect(),
        };

        Self::from_yolo_result(id, video_id, &dummy_yolo_result, start_frame, end_frame)
    }

    /// Creates a new chunk from seconds (legacy conversion).
    ///
    /// This is useful for migrating from older systems that store time as seconds.
    /// The frame counts are calculated by rounding `seconds * fps`.
    #[allow(clippy::too_many_arguments)]
    pub fn from_seconds(
        file_id: i32,
        uuid: String,
        chunk_index: u32,
        chunk_type: ChunkType,
        rule: ChunkRule,
        start_time: f64,
        end_time: f64,
        fps: f64,
        content: serde_json::Value,
    ) -> Self {
        let start_frame = (start_time * fps).round() as i64;
        let end_frame = (end_time * fps).round() as i64;
        Self::new(
            file_id,
            uuid,
            chunk_index,
            chunk_type,
            rule,
            start_frame,
            end_frame,
            fps,
            content,
        )
    }

    /// Returns the start time as a `FrameTime`.
    pub fn start_time(&self) -> FrameTime {
        FrameTime::from_frames(self.start_frame, self.fps)
    }

    /// Returns the end time as a `FrameTime`.
    pub fn end_time(&self) -> FrameTime {
        FrameTime::from_frames(self.end_frame, self.fps)
    }

    /// Returns the duration in frames.
    pub fn duration_frames(&self) -> i64 {
        self.end_frame - self.start_frame
    }

    /// Returns the duration in seconds.
    pub fn duration_seconds(&self) -> f64 {
        self.duration_frames() as f64 / self.fps
    }

    /// Formats the start time as "seconds.frame" (e.g., "123.04").
    pub fn format_start_sec_frame(&self) -> String {
        self.start_time().format_sec_frame()
    }

    /// Formats the end time as "seconds.frame" (e.g., "456.15").
    pub fn format_end_sec_frame(&self) -> String {
        self.end_time().format_sec_frame()
    }

    /// Formats the start time as "HH:MM:SS".
    pub fn format_start_hms(&self) -> String {
        self.start_time().format_hms()
    }

    /// Formats the end time as "HH:MM:SS".
    pub fn format_end_hms(&self) -> String {
        self.end_time().format_hms()
    }

    /// Formats the start time as "HH:MM:SS.FF".
    pub fn format_start_hms_frame(&self) -> String {
        self.start_time().format_hms_frame()
    }

    /// Formats the end time as "HH:MM:SS.FF".
    pub fn format_end_hms_frame(&self) -> String {
        self.end_time().format_hms_frame()
    }

    /// Returns a tuple of (start_seconds, end_seconds) for compatibility.
    ///
    /// This is provided for backward compatibility during migration.
    /// Prefer using `start_time()` and `end_time()` methods.
    pub fn time_range_seconds(&self) -> (f64, f64) {
        (self.start_time().seconds(), self.end_time().seconds())
    }

    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
        self.metadata = Some(metadata);
        self
    }

    pub fn with_vector_id(mut self, vector_id: String) -> Self {
        self.vector_id = Some(vector_id);
        self
    }

    pub fn with_text_content(mut self, text: String) -> Self {
        self.text_content = Some(text);
        self
    }

    pub fn with_frame_count(mut self, count: i32) -> Self {
        self.frame_count = count;
        self
    }

    pub fn with_pre_chunk_ids(mut self, ids: Vec<i32>) -> Self {
        self.pre_chunk_ids = ids;
        self
    }

    pub fn with_parent_chunk_id(mut self, parent_id: String) -> Self {
        self.parent_chunk_id = Some(parent_id);
        self
    }

    pub fn with_child_chunk_ids(mut self, child_ids: Vec<String>) -> Self {
        self.child_chunk_ids = child_ids;
        self
    }

    pub fn is_parent_chunk(&self) -> bool {
        !self.child_chunk_ids.is_empty()
    }

    pub fn is_child_chunk(&self) -> bool {
        self.parent_chunk_id.is_some()
    }

    /// 創建視覺分片
    pub fn new_visual(
        file_id: i32,
        uuid: String,
        chunk_index: u32,
        start_frame: i64,
        end_frame: i64,
        fps: f64,
        visual_content: VisualChunkContent,
    ) -> Self {
        let content = serde_json::to_value(&visual_content)
            .unwrap_or_else(|_| serde_json::json!({"error": "Failed to serialize visual content"}));

        Self::new(
            file_id,
            uuid,
            chunk_index,
            ChunkType::Visual,
            ChunkRule::Rule2,
            start_frame,
            end_frame,
            fps,
            content,
        )
    }

    /// 從 YOLO 結果創建視覺分片
    pub fn from_yolo_result(
        file_id: i32,
        uuid: String,
        chunk_index: u32,
        start_frame: i64,
        end_frame: i64,
        fps: f64,
        yolo_frames: Vec<crate::core::processor::yolo::YoloFrame>,
    ) -> Self {
        use crate::core::processor::yolo::YoloFrame;
        use std::collections::HashMap;

        // 分析物件統計
        let mut object_counts = HashMap::new();
        let mut keyframe_objects = Vec::new();
        let mut all_objects = Vec::new();

        for frame in &yolo_frames {
            let mut frame_objects = Vec::new();

            for obj in &frame.objects {
                // 更新物件統計
                *object_counts.entry(obj.class_name.clone()).or_insert(0) += 1;

                // 創建檢測到的物件
                let detected_obj = DetectedObject {
                    class_name: obj.class_name.clone(),
                    class_id: obj.class_id,
                    confidence: obj.confidence,
                    bbox: Some(BoundingBox {
                        x: obj.x,
                        y: obj.y,
                        width: obj.width,
                        height: obj.height,
                    }),
                    occurrence: 1,
                };

                frame_objects.push(detected_obj.clone());
                all_objects.push(detected_obj);
            }

            if !frame_objects.is_empty() {
                keyframe_objects.push(KeyframeObjects {
                    timestamp: frame.timestamp,
                    frame_number: frame.frame,
                    objects: frame_objects,
                });
            }
        }

        // 創建主要物件標籤
        let primary_objects = object_counts
            .iter()
            .filter(|(_, &count)| count >= 3) // 出現至少3次的物件
            .map(|(name, _)| name.clone())
            .collect::<Vec<_>>()
            .join(", ");

        // 創建物件統計 JSON
        let object_stats =
            serde_json::to_value(&object_counts).unwrap_or_else(|_| serde_json::json!({}));

        // 創建視覺內容
        let visual_content = VisualChunkContent {
            primary_objects: if primary_objects.is_empty() {
                "no objects detected".to_string()
            } else {
                primary_objects
            },
            object_stats,
            keyframe_objects,
            object_frequency: serde_json::to_value(&object_counts)
                .unwrap_or_else(|_| serde_json::json!({})),
            visual_summary: None, // 可選，後續可添加 LLM 生成的摘要
        };

        Self::new_visual(
            file_id,
            uuid,
            chunk_index,
            start_frame,
            end_frame,
            fps,
            visual_content,
        )
    }
}

impl VisualChunkContent {
    /// Calculate similarity between two YOLO frames based on object composition
    pub fn frame_similarity(
        frame1: &crate::core::processor::yolo::YoloFrame,
        frame2: &crate::core::processor::yolo::YoloFrame,
    ) -> f32 {
        if frame1.objects.is_empty() && frame2.objects.is_empty() {
            return 1.0; // Both empty frames are perfectly similar
        }

        if frame1.objects.is_empty() || frame2.objects.is_empty() {
            return 0.0; // One empty, one non-empty are dissimilar
        }

        // Create sets of object class names
        let set1: std::collections::HashSet<String> = frame1
            .objects
            .iter()
            .map(|o| o.class_name.clone())
            .collect();
        let set2: std::collections::HashSet<String> = frame2
            .objects
            .iter()
            .map(|o| o.class_name.clone())
            .collect();

        // Calculate Jaccard similarity
        let intersection: Vec<_> = set1.intersection(&set2).collect();
        let union: Vec<_> = set1.union(&set2).collect();

        if union.is_empty() {
            0.0
        } else {
            intersection.len() as f32 / union.len() as f32
        }
    }

    /// Get a summary of the visual chunk
    pub fn summary(&self) -> String {
        let duration = self.end_time - self.start_time;
        let frame_count = self.keyframe_objects.len();

        format!(
            "Visual chunk from {:.1}s to {:.1}s (duration: {:.1}s, {} frames). Objects: {} total, {} unique. Dominant objects: {}",
            self.start_time,
            self.end_time,
            duration,
            frame_count,
            self.metadata.object_count,
            self.metadata.unique_classes.len(),
            if self.dominant_objects.is_empty() {
                "none".to_string()
            } else {
                self.dominant_objects.join(", ")
            }
        )
    }

    /// Check if this chunk contains a specific object class
    pub fn contains_object(&self, class_name: &str) -> bool {
        self.keyframe_objects
            .iter()
            .any(|ko| ko.objects.iter().any(|obj| obj.class_name == class_name))
    }

    /// Get all objects with confidence above threshold
    pub fn high_confidence_objects(&self, threshold: f32) -> Vec<&DetectedObject> {
        self.keyframe_objects
            .iter()
            .flat_map(|ko| ko.objects.iter())
            .filter(|obj| obj.confidence >= threshold)
            .collect()
    }
}
