use serde::de::IntoDeserializer;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use url::Url;

/// The type of service endpoint included in a CTS-issued JWT.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ServiceType {
    ZeroKms,
    Secrets,
}

impl ServiceType {
    /// Return the lowercase string representation (matching the serde serialization).
    pub fn as_str(&self) -> &'static str {
        match self {
            ServiceType::ZeroKms => "zerokms",
            ServiceType::Secrets => "secrets",
        }
    }
}

/// A map of service types to their endpoint URLs.
///
/// Included in CTS-issued JWTs so clients can discover service endpoints
/// without guessing URL schemes from the `aud` claim.
///
/// Unknown service types are skipped during deserialization with a warning,
/// so that adding new services doesn't break existing clients.
#[derive(Debug, Clone, Default, PartialEq, Serialize)]
#[serde(transparent)]
pub struct Services(BTreeMap<ServiceType, Url>);

impl<'de> Deserialize<'de> for Services {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let raw: BTreeMap<String, Url> = BTreeMap::deserialize(deserializer)?;
        let mut services = BTreeMap::new();
        for (key, url) in raw {
            match ServiceType::deserialize(
                <&str as IntoDeserializer<serde::de::value::Error>>::into_deserializer(
                    key.as_str(),
                ),
            ) {
                Ok(service_type) => {
                    services.insert(service_type, url);
                }
                Err(_) => {
                    tracing::warn!(service_type = %key, "Unknown service type in token; ignoring");
                }
            }
        }
        Ok(Services(services))
    }
}

impl Services {
    pub fn new() -> Self {
        Self(BTreeMap::new())
    }

    pub fn insert(&mut self, service_type: ServiceType, url: Url) {
        self.0.insert(service_type, url);
    }

    pub fn get(&self, service_type: ServiceType) -> Option<&Url> {
        self.0.get(&service_type)
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    pub fn iter(&self) -> impl Iterator<Item = (&ServiceType, &Url)> {
        self.0.iter()
    }
}

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

    #[test]
    fn test_services_roundtrip() {
        let mut services = Services::new();
        services.insert(
            ServiceType::ZeroKms,
            Url::parse("https://us-east-1.aws.viturhosted.net/").unwrap(),
        );

        let json = serde_json::to_string(&services).unwrap();
        assert_eq!(
            json,
            r#"{"zerokms":"https://us-east-1.aws.viturhosted.net/"}"#
        );

        let deserialized: Services = serde_json::from_str(&json).unwrap();
        assert_eq!(
            deserialized.get(ServiceType::ZeroKms).map(|u| u.as_str()),
            Some("https://us-east-1.aws.viturhosted.net/")
        );
    }

    #[test]
    fn test_empty_services_not_serialized_as_null() {
        let services = Services::new();
        let json = serde_json::to_string(&services).unwrap();
        assert_eq!(json, "{}");
    }

    #[test]
    fn test_default_is_empty() {
        let services = Services::default();
        assert!(services.is_empty());
    }

    #[test]
    fn test_missing_key_returns_none() {
        let services = Services::new();
        assert_eq!(services.get(ServiceType::ZeroKms), None);
    }

    #[test]
    fn test_service_type_as_str() {
        assert_eq!(ServiceType::ZeroKms.as_str(), "zerokms");
        assert_eq!(ServiceType::Secrets.as_str(), "secrets");
    }

    #[test]
    fn test_services_roundtrip_with_multiple_services() {
        let mut services = Services::new();
        services.insert(
            ServiceType::Secrets,
            Url::parse("https://dashboard.cipherstash.com/").unwrap(),
        );
        services.insert(
            ServiceType::ZeroKms,
            Url::parse("https://us-east-1.aws.viturhosted.net/").unwrap(),
        );

        let json = serde_json::to_string(&services).unwrap();
        let deserialized: Services = serde_json::from_str(&json).unwrap();

        assert_eq!(
            deserialized.get(ServiceType::Secrets).map(|u| u.as_str()),
            Some("https://dashboard.cipherstash.com/"),
            "secrets endpoint should roundtrip through serde"
        );
        assert_eq!(
            deserialized.get(ServiceType::ZeroKms).map(|u| u.as_str()),
            Some("https://us-east-1.aws.viturhosted.net/"),
            "zerokms endpoint should roundtrip through serde"
        );
    }

    #[test]
    fn test_iter_populated_services() {
        let mut services = Services::new();
        let url = Url::parse("https://zerokms.example.com/").unwrap();
        services.insert(ServiceType::ZeroKms, url.clone());

        let entries: Vec<_> = services.iter().collect();
        assert_eq!(entries.len(), 1);
        assert_eq!(entries[0], (&ServiceType::ZeroKms, &url));
    }

    #[test]
    fn test_iter_empty_services() {
        let services = Services::new();
        assert_eq!(services.iter().count(), 0);
    }

    #[test]
    fn test_unknown_service_type_is_skipped() {
        let json = r#"{"zerokms":"https://us-east-1.aws.viturhosted.net/","newservice":"https://new.example.com/"}"#;
        let services: Services = serde_json::from_str(json).unwrap();

        assert_eq!(
            services.get(ServiceType::ZeroKms).map(|u| u.as_str()),
            Some("https://us-east-1.aws.viturhosted.net/"),
            "known service should be preserved"
        );
        assert!(
            services.iter().all(|(k, _)| *k != ServiceType::Secrets),
            "unknown service should not appear as a known variant"
        );
        assert_eq!(
            services.iter().count(),
            1,
            "only the known service should be present"
        );
    }

    #[test]
    fn test_all_unknown_services_produces_empty() {
        let json = r#"{"foo":"https://foo.example.com/"}"#;
        let services: Services = serde_json::from_str(json).unwrap();
        assert!(services.is_empty());
    }
}
