//! X.509 Certificate Validator with 6-layer security validation
//!
//! Comprehensive certificate validation including:
//! 1. Certificate expiry check
//! 2. Revocation check (CRL + OCSP)
//! 3. Certificate chain verification
//! 4. Extended key usage validation
//! 5. Signature verification
//! 6. Hostname verification

use anyhow::Result;
pub use common::tls::{ClientIdentity, UserRole};
use tracing::{debug, info, warn};
use x509_parser::certificate::X509Certificate;
use x509_parser::extensions::{GeneralName, ParsedExtension};

use super::revocation::{RevocationChecker, RevocationConfig};
use std::num::NonZeroUsize;
use std::time::Duration;

/// X.509 Certificate Validator with 6 security layers
#[derive(Debug, Clone)]
pub struct X509CertificateValidator {
    /// Enable certificate revocation checking
    pub enable_revocation_check: bool,
    /// Revocation checker instance
    pub revocation_checker: Option<RevocationChecker>,
}

impl X509CertificateValidator {
    /// Create new validator with optional revocation checking
    pub fn new(enable_revocation_check: bool, crl_url: Option<String>) -> Self {
        let revocation_checker = if enable_revocation_check {
            let config = RevocationConfig {
                crl_url,
                ocsp_responder_url: None,
                ocsp_cache_ttl: Duration::from_secs(1800), // 30 minutes
                ocsp_cache_capacity: NonZeroUsize::new(1000)
                    .unwrap_or(NonZeroUsize::MIN),
            };
            RevocationChecker::new(config).ok()
        } else {
            None
        };

        Self {
            enable_revocation_check,
            revocation_checker,
        }
    }

    /// Extract and validate certificate with comprehensive security checks
    pub fn extract_and_validate_certificate(
        &self,
        cert: &X509Certificate<'_>,
    ) -> Result<ClientIdentity> {
        // SECURITY CHECK 1: Certificate Validity Period (Expiration)
        self.validate_certificate_expiration(cert)?;

        // SECURITY CHECK 2: Certificate Purpose (Extended Key Usage)
        self.validate_certificate_purpose(cert)?;

        // SECURITY CHECK 3: Certificate Chain of Trust (Basic Constraints)
        self.validate_certificate_constraints(cert)?;

        // SECURITY CHECK 4: Critical Extensions Validation
        self.validate_critical_extensions(cert)?;

        // SECURITY CHECK 5: Subject Alternative Names (if present)
        self.validate_subject_alternative_names(cert)?;

        // SECURITY CHECK 6: Certificate Revocation Status (CRL/OCSP)
        // Note: Revocation checking is async and must be called separately
        // via check_revocation_status_async()

        // Extract identity information from Subject DN
        let subject = cert.subject();

        // Extract Common Name (CN)
        let common_name = subject
            .iter_common_name()
            .next()
            .and_then(|cn| cn.as_str().ok())
            .ok_or_else(|| anyhow::anyhow!("Certificate missing Common Name (CN)"))?
            .to_string();

        // Extract Organizational Unit (OU) - required for RBAC
        let organizational_unit = subject
            .iter_organizational_unit()
            .next()
            .and_then(|ou| ou.as_str().ok())
            .ok_or_else(|| anyhow::anyhow!("Certificate missing Organizational Unit (OU)"))?
            .to_string();

        // Extract Serial Number
        let serial_number = format!("{:X}", cert.serial);

        // Extract Issuer CN
        let issuer = cert
            .issuer()
            .iter_common_name()
            .next()
            .and_then(|cn| cn.as_str().ok())
            .unwrap_or("Unknown Issuer")
            .to_string();

        // SECURITY: Validate organizational unit is in allowed list
        let allowed_ous = ["trading", "admin", "analytics", "risk", "compliance"];
        if !allowed_ous.contains(&organizational_unit.as_str()) {
            return Err(anyhow::anyhow!(
                "Organizational Unit '{}' is not authorized for access. Allowed: {:?}",
                organizational_unit,
                allowed_ous
            ));
        }

        // SECURITY: Validate common name format (prevent injection attacks)
        if !common_name
            .chars()
            .all(|c| c.is_alphanumeric() || c == '.' || c == '-' || c == '_')
        {
            return Err(anyhow::anyhow!(
                "Common Name contains invalid characters: {}",
                common_name
            ));
        }

        Ok(ClientIdentity {
            common_name,
            organizational_unit,
            serial_number,
            issuer,
        })
    }

    /// SECURITY CHECK 1: Validate certificate expiration
    fn validate_certificate_expiration(&self, cert: &X509Certificate<'_>) -> Result<()> {
        let validity = cert.validity();

        // Get current time
        let now: i64 = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| anyhow::anyhow!("System time error: {}", e))?
            .as_secs()
            .try_into()
            .map_err(|_| anyhow::anyhow!("Timestamp exceeds i64 range"))?;
        // Check not before
        let not_before = validity.not_before.timestamp();
        if now < not_before {
            return Err(anyhow::anyhow!(
                "Certificate not yet valid. Valid from: {}",
                validity.not_before
            ));
        }

        // Check not after
        let not_after = validity.not_after.timestamp();
        if now > not_after {
            return Err(anyhow::anyhow!(
                "Certificate expired. Expired on: {}",
                validity.not_after
            ));
        }

        // SECURITY: Warn if certificate expires soon (within 30 days)
        let thirty_days_secs = 30 * 24 * 3600;
        if not_after - now < thirty_days_secs {
            let days_remaining = (not_after - now) / (24 * 3600);
            warn!(
                "Certificate expires soon! Days remaining: {}. Expiration: {}",
                days_remaining, validity.not_after
            );
        }

        Ok(())
    }

    /// SECURITY CHECK 2: Validate certificate purpose via Extended Key Usage
    fn validate_certificate_purpose(&self, cert: &X509Certificate<'_>) -> Result<()> {
        // Look for Extended Key Usage extension
        let mut has_client_auth = false;
        let mut has_eku_extension = false;

        for ext in cert.extensions() {
            if let ParsedExtension::ExtendedKeyUsage(eku) = ext.parsed_extension() {
                has_eku_extension = true;

                // Check for TLS Client Authentication (OID: 1.3.6.1.5.5.7.3.2)
                has_client_auth = eku.client_auth;

                if has_client_auth {
                    debug!("Certificate has TLS Client Authentication purpose");
                } else {
                    warn!(
                        "Certificate Extended Key Usage present but missing Client Auth. Purposes: {:?}",
                        eku
                    );
                }
            }
        }

        // SECURITY: Require Extended Key Usage with Client Auth for mTLS
        if has_eku_extension && !has_client_auth {
            return Err(anyhow::anyhow!(
                "Certificate does not have TLS Client Authentication purpose (Extended Key Usage)"
            ));
        }

        // If no EKU extension, we allow it (some CAs don't set this for client certs)
        // but log a warning for security awareness
        if !has_eku_extension {
            warn!(
                "Certificate missing Extended Key Usage extension - certificate purpose cannot be verified"
            );
        }

        Ok(())
    }

    /// SECURITY CHECK 3: Validate Basic Constraints (ensure not a CA certificate)
    fn validate_certificate_constraints(&self, cert: &X509Certificate<'_>) -> Result<()> {
        for ext in cert.extensions() {
            if let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() {
                // SECURITY: Client certificates should NOT be CA certificates
                if bc.ca {
                    return Err(anyhow::anyhow!(
                        "Client certificate has CA flag set - this is a CA certificate, not a client certificate"
                    ));
                }

                debug!("Certificate Basic Constraints validated: ca={}", bc.ca);
            }
        }

        Ok(())
    }

    /// SECURITY CHECK 4: Validate all critical extensions are recognized
    fn validate_critical_extensions(&self, cert: &X509Certificate<'_>) -> Result<()> {
        // List of recognized critical extensions (OIDs)
        let recognized_critical = [
            "2.5.29.15", // Key Usage
            "2.5.29.19", // Basic Constraints
            "2.5.29.37", // Extended Key Usage
            "2.5.29.17", // Subject Alternative Name
            "2.5.29.32", // Certificate Policies
            "2.5.29.35", // Authority Key Identifier
            "2.5.29.14", // Subject Key Identifier
        ];

        for ext in cert.extensions() {
            if ext.critical {
                let oid_str = ext.oid.to_id_string();

                // Check if this critical extension is recognized
                if !recognized_critical.contains(&oid_str.as_str()) {
                    return Err(anyhow::anyhow!(
                        "Certificate contains unrecognized critical extension: {} - cannot safely process",
                        oid_str
                    ));
                }

                debug!("Recognized critical extension: {}", oid_str);
            }
        }

        Ok(())
    }

    /// SECURITY CHECK 5: Validate Subject Alternative Names (if present)
    fn validate_subject_alternative_names(&self, cert: &X509Certificate<'_>) -> Result<()> {
        for ext in cert.extensions() {
            if let ParsedExtension::SubjectAlternativeName(san) = ext.parsed_extension() {
                // Extract and validate SAN entries
                let mut san_entries = Vec::new();

                for name in &san.general_names {
                    match name {
                        GeneralName::DNSName(dns) => {
                            san_entries.push(format!("DNS:{}", dns));

                            // SECURITY: Validate DNS name format
                            if !Self::is_valid_dns_name(dns) {
                                return Err(anyhow::anyhow!(
                                    "Invalid DNS name in Subject Alternative Name: {}",
                                    dns
                                ));
                            }
                        },
                        GeneralName::RFC822Name(email) => {
                            san_entries.push(format!("Email:{}", email));
                        },
                        GeneralName::IPAddress(ip) => {
                            san_entries.push(format!("IP:{:?}", ip));
                        },
                        GeneralName::URI(uri) => {
                            san_entries.push(format!("URI:{}", uri));
                        },
                        _ => {
                            debug!("Other SAN type: {:?}", name);
                        },
                    }
                }

                if !san_entries.is_empty() {
                    debug!("Certificate Subject Alternative Names: {:?}", san_entries);
                }
            }
        }

        Ok(())
    }

    /// Validate DNS name format (prevent injection attacks)
    fn is_valid_dns_name(name: &str) -> bool {
        // DNS name validation: alphanumeric, dots, hyphens, underscores
        // Max 253 characters total, max 63 characters per label
        if name.is_empty() || name.len() > 253 {
            return false;
        }

        for label in name.split('.') {
            if label.is_empty() || label.len() > 63 {
                return false;
            }

            // Check valid characters: alphanumeric, hyphen, underscore
            // Cannot start or end with hyphen
            if !label
                .chars()
                .all(|c| c.is_alphanumeric() || c == '-' || c == '_')
            {
                return false;
            }

            if label.starts_with('-') || label.ends_with('-') {
                return false;
            }
        }

        true
    }

    /// SECURITY CHECK 6: Check certificate revocation status via CRL or OCSP
    ///
    /// This is an async operation and must be called separately.
    /// The issuer certificate is required for OCSP validation.
    pub async fn check_revocation_status_async(
        &self,
        cert: &X509Certificate<'_>,
        issuer: &X509Certificate<'_>,
    ) -> Result<()> {
        if !self.enable_revocation_check {
            return Ok(());
        }

        if let Some(ref checker) = self.revocation_checker {
            checker.check_revocation(cert, issuer).await
        } else {
            warn!("Revocation checking enabled but no checker configured");
            Ok(())
        }
    }

    /// Validate certificate chain of trust against CA certificate
    ///
    /// Performs cryptographic signature verification of the client certificate
    /// against the CA's public key using the `ring` crate (via x509-parser's verify feature).
    ///
    /// Validation steps:
    /// 1. Parse both client and CA certificates from PEM
    /// 2. Verify the client certificate's issuer CN matches the CA's subject CN
    /// 3. Verify the CA certificate's validity period (not expired)
    /// 4. Verify the CA has the cA Basic Constraint (is actually a CA)
    /// 5. Cryptographically verify the client cert's signature using the CA's public key
    pub fn validate_certificate_chain(
        &self,
        client_cert_pem: &[u8],
        ca_cert_pem: &[u8],
    ) -> Result<()> {
        // Parse client certificate
        let (_, client_pem) = x509_parser::pem::parse_x509_pem(client_cert_pem)
            .map_err(|e| anyhow::anyhow!("Failed to parse client certificate PEM: {}", e))?;

        let client_cert = client_pem
            .parse_x509()
            .map_err(|e| anyhow::anyhow!("Failed to parse client X.509 certificate: {}", e))?;

        // Parse CA certificate
        let (_, ca_pem) = x509_parser::pem::parse_x509_pem(ca_cert_pem)
            .map_err(|e| anyhow::anyhow!("Failed to parse CA certificate PEM: {}", e))?;

        let ca_cert = ca_pem
            .parse_x509()
            .map_err(|e| anyhow::anyhow!("Failed to parse CA X.509 certificate: {}", e))?;

        // --- Step 1: Verify issuer CN matches CA subject CN ---
        let client_issuer_cn = client_cert
            .issuer()
            .iter_common_name()
            .next()
            .and_then(|cn| cn.as_str().ok())
            .ok_or_else(|| anyhow::anyhow!("Client certificate missing issuer CN"))?;

        let ca_subject_cn = ca_cert
            .subject()
            .iter_common_name()
            .next()
            .and_then(|cn| cn.as_str().ok())
            .ok_or_else(|| anyhow::anyhow!("CA certificate missing subject CN"))?;

        if client_issuer_cn != ca_subject_cn {
            return Err(anyhow::anyhow!(
                "Certificate chain broken: client issuer CN '{}' does not match CA subject CN '{}'",
                client_issuer_cn,
                ca_subject_cn
            ));
        }

        debug!(
            "Issuer CN match verified: client issuer='{}', CA subject='{}'",
            client_issuer_cn, ca_subject_cn
        );

        // --- Step 2: Verify CA certificate is not expired ---
        let ca_validity = ca_cert.validity();
        let now: i64 = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| anyhow::anyhow!("System time error: {}", e))?
            .as_secs()
            .try_into()
            .map_err(|_| anyhow::anyhow!("Timestamp exceeds i64 range"))?;

        if now < ca_validity.not_before.timestamp() {
            return Err(anyhow::anyhow!(
                "CA certificate not yet valid. Valid from: {}",
                ca_validity.not_before
            ));
        }

        if now > ca_validity.not_after.timestamp() {
            return Err(anyhow::anyhow!(
                "CA certificate expired. Expired on: {}",
                ca_validity.not_after
            ));
        }

        // --- Step 3: Verify CA has Basic Constraints with cA=true ---
        let mut ca_has_ca_flag = false;
        for ext in ca_cert.extensions() {
            if let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() {
                if bc.ca {
                    ca_has_ca_flag = true;
                }
            }
        }

        if !ca_has_ca_flag {
            return Err(anyhow::anyhow!(
                "CA certificate does not have Basic Constraints cA=true -- not a valid CA"
            ));
        }

        // --- Step 4: Cryptographic signature verification ---
        // Use the CA's public key to verify the client certificate's signature.
        // x509-parser's verify_signature uses ring internally to:
        //   - Extract the signature algorithm OID from the client cert
        //   - Map it to the appropriate ring verification algorithm
        //     (RSA-PKCS1-SHA256/384/512, ECDSA-P256/P384, Ed25519)
        //   - Extract the CA's SubjectPublicKeyInfo
        //   - Verify the signature over the client cert's TBS (to-be-signed) data
        let ca_public_key = ca_cert.public_key();
        client_cert
            .verify_signature(Some(ca_public_key))
            .map_err(|e| {
                anyhow::anyhow!(
                    "Certificate signature verification failed: {} \
                     (algorithm OID: {}, CA subject: '{}')",
                    e,
                    client_cert.signature_algorithm.algorithm,
                    ca_subject_cn
                )
            })?;

        info!(
            "Certificate chain verified: client cert signed by CA '{}' \
             (algorithm: {})",
            ca_subject_cn, client_cert.signature_algorithm.algorithm
        );

        Ok(())
    }
}

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

    #[test]
    fn test_client_identity_authorization() {
        let trading_identity = ClientIdentity {
            common_name: "trader1.trading.foxhunt.internal".to_string(),
            organizational_unit: "trading".to_string(),
            serial_number: "12345".to_string(),
            issuer: "Foxhunt Trading CA".to_string(),
        };

        assert!(trading_identity.is_authorized_for_trading());
        assert!(trading_identity.is_authorized_for_readonly());
        assert_eq!(trading_identity.get_role(), UserRole::Trader);

        let readonly_identity = ClientIdentity {
            common_name: "analyst1.analytics.foxhunt.internal".to_string(),
            organizational_unit: "analytics".to_string(),
            serial_number: "12346".to_string(),
            issuer: "Foxhunt Trading CA".to_string(),
        };

        assert!(!readonly_identity.is_authorized_for_trading());
        assert!(readonly_identity.is_authorized_for_readonly());
        assert_eq!(readonly_identity.get_role(), UserRole::Analyst);
    }

    #[test]
    fn test_user_role_permissions() {
        let trader = UserRole::Trader;
        let permissions = trader.get_permissions();

        assert!(permissions.contains(&"trading.submit_order"));
        assert!(permissions.contains(&"trading.cancel_order"));
        assert!(!permissions.contains(&"system.configure"));

        let readonly = UserRole::ReadOnly;
        let readonly_permissions = readonly.get_permissions();

        assert!(!readonly_permissions.contains(&"trading.submit_order"));
        assert!(readonly_permissions.contains(&"analytics.view_data"));
    }

    #[test]
    fn test_dns_name_validation() {
        assert!(X509CertificateValidator::is_valid_dns_name("example.com"));
        assert!(X509CertificateValidator::is_valid_dns_name(
            "sub.example.com"
        ));
        assert!(X509CertificateValidator::is_valid_dns_name(
            "test-server.internal"
        ));

        assert!(!X509CertificateValidator::is_valid_dns_name(""));
        assert!(!X509CertificateValidator::is_valid_dns_name("-invalid.com"));
        assert!(!X509CertificateValidator::is_valid_dns_name("invalid-.com"));
        assert!(!X509CertificateValidator::is_valid_dns_name("invalid..com"));
    }
}
