//! mTLS (Mutual TLS) authentication
//!
//! Provides certificate-based authentication for service-to-service communication.

use std::collections::HashMap;

use iam_types::{Error, IamError, PrincipalKind, PrincipalRef, Result};

/// Certificate information extracted from mTLS connection
#[derive(Debug, Clone)]
pub struct CertificateInfo {
    /// Common Name (CN) from the certificate subject
    pub common_name: String,
    /// Organization (O) from the certificate subject
    pub organization: Option<String>,
    /// Organizational Unit (OU) from the certificate subject
    pub organizational_unit: Option<String>,
    /// Serial number of the certificate
    pub serial_number: String,
    /// Certificate fingerprint (SHA-256)
    pub fingerprint: String,
    /// Not before timestamp (Unix seconds)
    pub not_before: u64,
    /// Not after timestamp (Unix seconds)
    pub not_after: u64,
    /// Subject Alternative Names (SANs)
    pub sans: Vec<SubjectAltName>,
}

/// Subject Alternative Name types
#[derive(Debug, Clone)]
pub enum SubjectAltName {
    /// DNS name
    Dns(String),
    /// URI
    Uri(String),
    /// IP address
    Ip(String),
    /// Email
    Email(String),
}

/// Configuration for mTLS verifier
#[derive(Debug, Clone)]
pub struct MtlsVerifierConfig {
    /// Mapping from CN patterns to principal references
    /// Pattern can use * as wildcard
    pub cn_mappings: HashMap<String, PrincipalMapping>,
    /// Required organization
    pub required_org: Option<String>,
    /// Required organizational unit
    pub required_ou: Option<String>,
    /// Whether to validate certificate expiration
    pub validate_expiration: bool,
}

/// Mapping configuration for a CN pattern
#[derive(Debug, Clone)]
pub struct PrincipalMapping {
    /// Principal kind to assign
    pub kind: PrincipalKind,
    /// ID template (can use {cn}, {ou}, {o} placeholders)
    pub id_template: String,
    /// Optional node ID extraction from CN
    pub node_id_from_cn: bool,
}

impl Default for MtlsVerifierConfig {
    fn default() -> Self {
        Self {
            cn_mappings: HashMap::new(),
            required_org: None,
            required_ou: None,
            validate_expiration: true,
        }
    }
}

impl MtlsVerifierConfig {
    /// Create a new config
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a CN to principal mapping
    pub fn add_mapping(mut self, cn_pattern: impl Into<String>, mapping: PrincipalMapping) -> Self {
        self.cn_mappings.insert(cn_pattern.into(), mapping);
        self
    }

    /// Set required organization
    pub fn with_required_org(mut self, org: impl Into<String>) -> Self {
        self.required_org = Some(org.into());
        self
    }

    /// Set required OU
    pub fn with_required_ou(mut self, ou: impl Into<String>) -> Self {
        self.required_ou = Some(ou.into());
        self
    }
}

/// mTLS certificate verifier
pub struct MtlsVerifier {
    config: MtlsVerifierConfig,
}

impl MtlsVerifier {
    /// Create a new mTLS verifier
    pub fn new(config: MtlsVerifierConfig) -> Self {
        Self { config }
    }

    /// Create with default service account mapping
    pub fn with_default_sa_mapping() -> Self {
        let config = MtlsVerifierConfig::new()
            .add_mapping(
                "*.service.internal",
                PrincipalMapping {
                    kind: PrincipalKind::ServiceAccount,
                    id_template: "{cn}".into(),
                    node_id_from_cn: false,
                },
            )
            .add_mapping(
                "node-*.compute.internal",
                PrincipalMapping {
                    kind: PrincipalKind::ServiceAccount,
                    id_template: "compute-agent".into(),
                    node_id_from_cn: true,
                },
            );

        Self::new(config)
    }

    /// Verify a certificate and return the authenticated principal
    pub fn verify(&self, cert_info: &CertificateInfo) -> Result<MtlsAuthResult> {
        // 1. Validate expiration
        if self.config.validate_expiration {
            let now = std::time::SystemTime::now()
                .duration_since(std::time::UNIX_EPOCH)
                .unwrap()
                .as_secs();

            if now < cert_info.not_before {
                return Err(Error::Iam(IamError::InvalidToken(
                    "Certificate not yet valid".into(),
                )));
            }

            if now > cert_info.not_after {
                return Err(Error::Iam(IamError::InvalidToken(
                    "Certificate expired".into(),
                )));
            }
        }

        // 2. Validate organization if required
        if let Some(required_org) = &self.config.required_org {
            match &cert_info.organization {
                Some(org) if org == required_org => {}
                Some(org) => {
                    return Err(Error::Iam(IamError::AuthnFailed(format!(
                        "Invalid organization: expected {}, got {}",
                        required_org, org
                    ))));
                }
                None => {
                    return Err(Error::Iam(IamError::AuthnFailed(
                        "Certificate missing organization".into(),
                    )));
                }
            }
        }

        // 3. Validate OU if required
        if let Some(required_ou) = &self.config.required_ou {
            match &cert_info.organizational_unit {
                Some(ou) if ou == required_ou => {}
                Some(ou) => {
                    return Err(Error::Iam(IamError::AuthnFailed(format!(
                        "Invalid organizational unit: expected {}, got {}",
                        required_ou, ou
                    ))));
                }
                None => {
                    return Err(Error::Iam(IamError::AuthnFailed(
                        "Certificate missing organizational unit".into(),
                    )));
                }
            }
        }

        // 4. Find matching CN pattern
        let (_pattern, mapping) = self
            .find_matching_pattern(&cert_info.common_name)
            .ok_or_else(|| {
                Error::Iam(IamError::AuthnFailed(format!(
                    "No mapping for CN: {}",
                    cert_info.common_name
                )))
            })?;

        // 5. Build principal reference
        let principal_id = self.expand_template(&mapping.id_template, cert_info);

        let principal_ref = PrincipalRef::new(mapping.kind.clone(), principal_id);

        // 6. Extract node ID if configured
        let node_id = if mapping.node_id_from_cn {
            Some(self.extract_node_id(&cert_info.common_name))
        } else {
            None
        };

        Ok(MtlsAuthResult {
            principal_ref,
            node_id,
            certificate_fingerprint: cert_info.fingerprint.clone(),
        })
    }

    fn find_matching_pattern(&self, cn: &str) -> Option<(&String, &PrincipalMapping)> {
        for (pattern, mapping) in &self.config.cn_mappings {
            if self.matches_pattern(pattern, cn) {
                return Some((pattern, mapping));
            }
        }
        None
    }

    fn matches_pattern(&self, pattern: &str, value: &str) -> bool {
        if pattern == "*" {
            return true;
        }

        if !pattern.contains('*') {
            return pattern == value;
        }

        // Simple glob matching
        let parts: Vec<&str> = pattern.split('*').collect();
        if parts.len() == 2 {
            let (prefix, suffix) = (parts[0], parts[1]);
            return value.starts_with(prefix) && value.ends_with(suffix);
        }

        // For more complex patterns, do exact match
        pattern == value
    }

    fn expand_template(&self, template: &str, cert_info: &CertificateInfo) -> String {
        let mut result = template.to_string();
        result = result.replace("{cn}", &cert_info.common_name);
        if let Some(o) = &cert_info.organization {
            result = result.replace("{o}", o);
        }
        if let Some(ou) = &cert_info.organizational_unit {
            result = result.replace("{ou}", ou);
        }
        result
    }

    fn extract_node_id(&self, cn: &str) -> String {
        // Extract node ID from CN like "node-abc123.compute.internal"
        cn.split('.').next().unwrap_or(cn).to_string()
    }
}

/// Result of mTLS authentication
#[derive(Debug, Clone)]
pub struct MtlsAuthResult {
    /// Authenticated principal reference
    pub principal_ref: PrincipalRef,
    /// Node ID (if applicable)
    pub node_id: Option<String>,
    /// Certificate fingerprint
    pub certificate_fingerprint: String,
}

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

    fn test_cert_info() -> CertificateInfo {
        CertificateInfo {
            common_name: "compute-agent.service.internal".into(),
            organization: Some("cloud-platform".into()),
            organizational_unit: Some("compute".into()),
            serial_number: "123456".into(),
            fingerprint: "sha256:abc123".into(),
            not_before: 0,
            not_after: u64::MAX,
            sans: vec![],
        }
    }

    #[test]
    fn test_mtls_verification() {
        let config = MtlsVerifierConfig::new()
            .add_mapping(
                "*.service.internal",
                PrincipalMapping {
                    kind: PrincipalKind::ServiceAccount,
                    id_template: "{cn}".into(),
                    node_id_from_cn: false,
                },
            )
            .with_required_org("cloud-platform");

        let verifier = MtlsVerifier::new(config);
        let cert = test_cert_info();

        let result = verifier.verify(&cert).unwrap();
        assert_eq!(result.principal_ref.kind, PrincipalKind::ServiceAccount);
        assert_eq!(result.principal_ref.id, "compute-agent.service.internal");
    }

    #[test]
    fn test_node_id_extraction() {
        let config = MtlsVerifierConfig::new().add_mapping(
            "node-*.compute.internal",
            PrincipalMapping {
                kind: PrincipalKind::ServiceAccount,
                id_template: "compute-agent".into(),
                node_id_from_cn: true,
            },
        );

        let verifier = MtlsVerifier::new(config);

        let mut cert = test_cert_info();
        cert.common_name = "node-abc123.compute.internal".into();
        cert.organization = None;

        let result = verifier.verify(&cert).unwrap();
        assert_eq!(result.node_id, Some("node-abc123".into()));
    }

    #[test]
    fn test_pattern_matching() {
        let verifier = MtlsVerifier::new(MtlsVerifierConfig::default());

        assert!(verifier.matches_pattern("*.example.com", "foo.example.com"));
        assert!(verifier.matches_pattern("*.example.com", "bar.example.com"));
        assert!(!verifier.matches_pattern("*.example.com", "foo.other.com"));
        assert!(verifier.matches_pattern("exact-match", "exact-match"));
        assert!(!verifier.matches_pattern("exact-match", "other"));
        assert!(verifier.matches_pattern("*", "anything"));
    }
}
