use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, BTreeSet};

use meerkat_contracts::capability::MobpackCapabilityRequirement;

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MobpackManifest {
    pub mobpack: MobpackSection,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub requires: Option<RequiresSection>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub models: BTreeMap<String, String>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub profiles: BTreeMap<String, ProfileSection>,
    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
    pub surfaces: BTreeSet<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MobpackSection {
    pub name: String,
    pub version: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequiresSection {
    #[serde(default)]
    pub capabilities: Vec<String>,
}

impl RequiresSection {
    pub fn typed_capabilities(
        &self,
    ) -> impl Iterator<Item = MobpackCapabilityRequirement<'_>> + '_ {
        self.capabilities
            .iter()
            .map(|capability| MobpackCapabilityRequirement::parse(capability))
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProfileSection {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub model: Option<String>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub skills: Vec<String>,
}

#[cfg(test)]
#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
mod tests {
    use super::*;
    use meerkat_contracts::capability::{
        CapabilityId, HostProcessCapabilityId, MobpackCapabilityId, MobpackCapabilityRequirement,
    };

    #[test]
    fn test_manifest_toml_roundtrip() {
        let mut models = BTreeMap::new();
        models.insert("planner".to_string(), "gpt-5".to_string());
        models.insert("reviewer".to_string(), "gpt-5-mini".to_string());

        let mut profiles = BTreeMap::new();
        profiles.insert(
            "lead".to_string(),
            ProfileSection {
                model: Some("planner".to_string()),
                skills: vec!["skills/lead.md".to_string()],
            },
        );
        profiles.insert(
            "reviewer".to_string(),
            ProfileSection {
                model: Some("reviewer".to_string()),
                skills: vec!["skills/review.md".to_string()],
            },
        );

        let manifest = MobpackManifest {
            mobpack: MobpackSection {
                name: "code-review".to_string(),
                version: "1.0.0".to_string(),
                description: Some("Review assistant".to_string()),
            },
            requires: Some(RequiresSection {
                capabilities: vec!["comms".to_string(), "shell".to_string()],
            }),
            models,
            profiles,
            surfaces: BTreeSet::from(["cli".to_string(), "rpc".to_string()]),
        };

        let encoded = toml::to_string(&manifest).unwrap();
        let decoded: MobpackManifest = toml::from_str(&encoded).unwrap();
        assert_eq!(decoded, manifest);
    }

    #[test]
    fn test_requires_section_preserves_strings_and_exposes_typed_capabilities() {
        let manifest: MobpackManifest = toml::from_str(
            r#"
[mobpack]
name = "browser-test"
version = "1.0.0"

[requires]
capabilities = ["comms", "shell", "mcp_stdio", "vendor.custom"]
"#,
        )
        .unwrap();
        let requires = manifest.requires.unwrap();

        assert_eq!(
            requires.capabilities,
            vec![
                "comms".to_string(),
                "shell".to_string(),
                "mcp_stdio".to_string(),
                "vendor.custom".to_string(),
            ]
        );

        let typed = requires
            .typed_capabilities()
            .map(MobpackCapabilityRequirement::id)
            .collect::<Vec<_>>();
        assert_eq!(
            typed,
            vec![
                MobpackCapabilityId::Known(CapabilityId::Comms),
                MobpackCapabilityId::Known(CapabilityId::Shell),
                MobpackCapabilityId::HostProcess(HostProcessCapabilityId::McpStdio),
                MobpackCapabilityId::Unknown,
            ]
        );
    }
}
