use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct Meta {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub provider: Option<ProviderMeta>,

    /// Arbitrary extra fields
    #[serde(flatten)]
    pub extra: HashMap<String, Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProviderMeta {
    /// Provider name (e.g., "opencode", "anthropic")
    pub name: String,

    /// Original provider-specific IDs
    #[serde(skip_serializing_if = "Option::is_none")]
    pub original_ids: Option<HashMap<String, String>>,

    /// Minimal raw payload excerpts for debugging (optional)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub raw_excerpt: Option<Value>,
}

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

    #[test]
    fn test_meta_default() {
        let meta = Meta::default();
        assert_eq!(meta.provider, None);
        assert!(meta.extra.is_empty());
    }

    #[test]
    fn test_meta_with_provider() {
        let meta = Meta {
            provider: Some(ProviderMeta {
                name: "opencode".to_string(),
                original_ids: Some(HashMap::from([
                    ("session_id".to_string(), "abc123".to_string()),
                ])),
                raw_excerpt: None,
            }),
            extra: HashMap::new(),
        };
        let json = serde_json::to_string(&meta).unwrap();
        assert!(json.contains(r#""name":"opencode"#));
    }

    #[test]
    fn test_skip_serializing_if_none() {
        let meta = Meta::default();
        let json = serde_json::to_string(&meta).unwrap();
        // Should be empty object since provider is None
        assert_eq!(json, "{}");
    }

    #[test]
    fn test_roundtrip() {
        let meta = Meta {
            provider: Some(ProviderMeta {
                name: "test".to_string(),
                original_ids: None,
                raw_excerpt: None,
            }),
            extra: HashMap::new(),
        };
        let json = serde_json::to_string(&meta).unwrap();
        let deserialized: Meta = serde_json::from_str(&json).unwrap();
        assert_eq!(meta, deserialized);
    }

    #[test]
    fn test_meta_preserves_claude_code_tool_response() {
        use serde_json::json;

        // Test that complex nested _meta data is preserved (T013 requirement)
        let meta_json = json!({
            "claudeCode": {
                "toolResponse": {
                    "mode": "content",
                    "numFiles": 0,
                    "filenames": [],
                    "content": "some grep output here",
                    "numLines": 58,
                    "appliedLimit": 100
                },
                "toolName": "Grep"
            }
        });

        // Deserialize into Meta
        let meta: Meta = serde_json::from_value(meta_json.clone()).unwrap();

        // Verify claudeCode is in extra
        assert!(meta.extra.contains_key("claudeCode"));

        // Serialize back to JSON
        let serialized = serde_json::to_value(&meta).unwrap();

        // Verify all fields are preserved
        assert_eq!(serialized["claudeCode"]["toolName"], "Grep");
        assert!(serialized["claudeCode"]["toolResponse"].is_object());
        assert_eq!(serialized["claudeCode"]["toolResponse"]["mode"], "content");
        assert_eq!(serialized["claudeCode"]["toolResponse"]["numFiles"], 0);
        assert_eq!(serialized["claudeCode"]["toolResponse"]["numLines"], 58);
        assert_eq!(serialized["claudeCode"]["toolResponse"]["appliedLimit"], 100);
        assert_eq!(
            serialized["claudeCode"]["toolResponse"]["content"],
            "some grep output here"
        );
    }

    #[test]
    fn test_meta_round_trip_preservation() {
        use serde_json::json;

        // Test incoming → serialize → deserialize → serialize preserves all fields (T013)
        let original_meta_json = json!({
            "claudeCode": {
                "toolResponse": {
                    "mode": "content",
                    "numFiles": 3,
                    "filenames": ["file1.rs", "file2.rs", "file3.rs"],
                    "content": "match results",
                    "numLines": 42,
                    "appliedLimit": 100,
                    "customField": "should be preserved"
                },
                "toolName": "Grep",
                "additionalField": "also preserved"
            },
            "provider": {
                "name": "anthropic",
                "original_ids": {
                    "session_id": "sess_123"
                }
            },
            "customTopLevel": "preserved too"
        });

        // First round trip
        let meta1: Meta = serde_json::from_value(original_meta_json.clone()).unwrap();
        let json1 = serde_json::to_value(&meta1).unwrap();

        // Second round trip
        let meta2: Meta = serde_json::from_value(json1.clone()).unwrap();
        let json2 = serde_json::to_value(&meta2).unwrap();

        // Verify both serializations are identical (stable)
        assert_eq!(json1, json2);

        // Verify all nested fields preserved
        assert_eq!(json2["claudeCode"]["toolResponse"]["customField"], "should be preserved");
        assert_eq!(json2["claudeCode"]["additionalField"], "also preserved");
        assert_eq!(json2["customTopLevel"], "preserved too");
        assert_eq!(json2["provider"]["name"], "anthropic");
    }

    #[test]
    fn test_meta_extra_fields_with_flatten() {
        use serde_json::json;

        // Test that #[serde(flatten)] correctly captures arbitrary fields
        let json = json!({
            "provider": {
                "name": "test_provider"
            },
            "arbitraryField1": "value1",
            "arbitraryField2": {
                "nested": "structure"
            },
            "arbitraryField3": [1, 2, 3]
        });

        let meta: Meta = serde_json::from_value(json.clone()).unwrap();

        // Verify provider is parsed correctly
        assert!(meta.provider.is_some());
        assert_eq!(meta.provider.as_ref().unwrap().name, "test_provider");

        // Verify extra fields are captured
        assert_eq!(meta.extra.len(), 3);
        assert!(meta.extra.contains_key("arbitraryField1"));
        assert!(meta.extra.contains_key("arbitraryField2"));
        assert!(meta.extra.contains_key("arbitraryField3"));

        // Verify round-trip preserves all fields
        let serialized = serde_json::to_value(&meta).unwrap();
        assert_eq!(serialized["arbitraryField1"], "value1");
        assert_eq!(serialized["arbitraryField2"]["nested"], "structure");
        assert_eq!(serialized["arbitraryField3"], json!([1, 2, 3]));
    }

    #[test]
    fn test_meta_no_data_loss_on_unknown_fields() {
        use serde_json::json;

        // Simulate receiving _meta from Claude with fields we don't know about yet
        let future_meta = json!({
            "claudeCode": {
                "toolName": "FutureTool",
                "futureFeature1": "some value",
                "futureFeature2": {
                    "deeplyNested": {
                        "data": [1, 2, 3]
                    }
                }
            },
            "unknownTopLevel": "should not be lost"
        });

        let meta: Meta = serde_json::from_value(future_meta.clone()).unwrap();
        let serialized = serde_json::to_value(&meta).unwrap();

        // Verify NO data loss - all unknown fields preserved
        assert_eq!(serialized["claudeCode"]["toolName"], "FutureTool");
        assert_eq!(serialized["claudeCode"]["futureFeature1"], "some value");
        assert_eq!(
            serialized["claudeCode"]["futureFeature2"]["deeplyNested"]["data"],
            json!([1, 2, 3])
        );
        assert_eq!(serialized["unknownTopLevel"], "should not be lost");
    }
}
