//! mTLS Authentication Module
//!
//! Provides mutual TLS authentication middleware for Actix-web.
//! Non-mTLS connections are silently dropped (no response).

use actix_web::{
    dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
    Error, HttpMessage,
};
#[allow(unused_imports)]
use chrono::{DateTime, Duration, Utc};
use futures_util::future::LocalBoxFuture;
use rustls::{
    server::{ServerConfig, WebPkiClientVerifier},
    RootCertStore,
};
use rustls_pemfile::{certs, private_key};
use std::{fs::File, io::BufReader, sync::Arc};
use tracing::{debug, info, warn};

/// Check for duplicate critical headers (VULN-006)
/// Returns true if duplicate headers are detected
fn has_duplicate_critical_headers(req: &ServiceRequest) -> bool {
    let critical_headers = ["content-type", "authorization", "host"];

    for header_name in critical_headers.iter() {
        // Count occurrences of this header
        let mut count = 0;
        for (name, _) in req.headers().iter() {
            if name.as_str().eq_ignore_ascii_case(header_name) {
                count += 1;
                if count > 1 {
                    warn!(
                        peer_addr = ?req.peer_addr(),
                        header = header_name,
                        "Duplicate critical header detected - rejecting request"
                    );
                    return true;
                }
            }
        }
    }
    false
}

/// mTLS Configuration
#[derive(Debug, Clone)]
pub struct MtlsConfig {
    pub ca_cert_path: String,
    pub server_cert_path: String,
    pub server_key_path: String,
    pub min_tls_version: String,
}

/// mTLS Middleware for Actix-web
pub struct MtlsMiddleware {
    config: Arc<MtlsConfig>,
    cert_store: Arc<RootCertStore>,
}

impl MtlsMiddleware {
    /// Create a new mTLS middleware
    pub fn new(config: MtlsConfig) -> Result<Self, MtlsError> {
        let cert_store = load_ca_certs(&config.ca_cert_path)?;

        Ok(Self {
            config: Arc::new(config),
            cert_store: Arc::new(cert_store),
        })
    }

    /// Build rustls server configuration with client certificate verification
    pub fn build_rustls_config(&self) -> Result<Arc<ServerConfig>, MtlsError> {
        let client_verifier = WebPkiClientVerifier::builder(self.cert_store.clone())
            .build()
            .map_err(|e| MtlsError::ClientVerifierError(e.to_string()))?;

        let server_cert = load_certs(&self.config.server_cert_path)?;
        let server_key = load_private_key(&self.config.server_key_path)?;

        let config = ServerConfig::builder()
            .with_client_cert_verifier(client_verifier)
            .with_single_cert(server_cert, server_key)
            .map_err(|e| MtlsError::ServerConfigError(e.to_string()))?;

        Ok(Arc::new(config))
    }
}

/// Load CA certificates from PEM file
fn load_ca_certs(path: &str) -> Result<RootCertStore, MtlsError> {
    let mut cert_store = RootCertStore::empty();

    let cert_file = File::open(path)
        .map_err(|e| MtlsError::IoError(format!("Failed to open CA cert {}: {}", path, e)))?;
    let mut reader = BufReader::new(cert_file);

    let certs = certs(&mut reader)
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| MtlsError::ParseError(format!("Failed to parse CA certs: {}", e)))?;

    for cert in certs {
        cert_store
            .add(cert)
            .map_err(|e| MtlsError::StoreError(format!("Failed to add CA cert to store: {}", e)))?;
    }

    info!("Loaded CA certificates from {}", path);
    Ok(cert_store)
}

/// Load server certificates from PEM file
fn load_certs(path: &str) -> Result<Vec<rustls::pki_types::CertificateDer<'static>>, MtlsError> {
    let cert_file = File::open(path)
        .map_err(|e| MtlsError::IoError(format!("Failed to open cert {}: {}", path, e)))?;
    let mut reader = BufReader::new(cert_file);

    let certs = certs(&mut reader)
        .collect::<Result<Vec<_>, _>>()
        .map_err(|e| MtlsError::ParseError(format!("Failed to parse server certs: {}", e)))?;

    Ok(certs)
}

/// Load private key from PEM file
fn load_private_key(path: &str) -> Result<rustls::pki_types::PrivateKeyDer<'static>, MtlsError> {
    let key_file = File::open(path)
        .map_err(|e| MtlsError::IoError(format!("Failed to open key {}: {}", path, e)))?;
    let mut reader = BufReader::new(key_file);

    let key = private_key(&mut reader)
        .map_err(|e| MtlsError::ParseError(format!("Failed to parse private key: {}", e)))?
        .ok_or_else(|| MtlsError::ParseError("No private key found in file".to_string()))?;

    Ok(key)
}

/// mTLS Error types
#[derive(Debug, thiserror::Error)]
pub enum MtlsError {
    #[error("IO error: {0}")]
    IoError(String),
    #[error("Parse error: {0}")]
    ParseError(String),
    #[error("Certificate store error: {0}")]
    StoreError(String),
    #[error("Client verifier error: {0}")]
    ClientVerifierError(String),
    #[error("Server config error: {0}")]
    ServerConfigError(String),
    #[error("Certificate validation error: {0}")]
    ValidationError(String),
}

impl<S, B> Transform<S, ServiceRequest> for MtlsMiddleware
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type InitError = ();
    type Transform = MtlsMiddlewareService<S>;
    type Future = futures_util::future::Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        futures_util::future::ok(MtlsMiddlewareService {
            service,
            config: self.config.clone(),
            cert_store: self.cert_store.clone(),
        })
    }
}

pub struct MtlsMiddlewareService<S> {
    service: S,
    #[allow(dead_code)]
    config: Arc<MtlsConfig>,
    cert_store: Arc<RootCertStore>,
}

impl<S, B> Service<ServiceRequest> for MtlsMiddlewareService<S>
where
    S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
    S::Future: 'static,
    B: 'static,
{
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;

    forward_ready!(service);

    fn call(&self, req: ServiceRequest) -> Self::Future {
        let cert_store = self.cert_store.clone();
        let peer_addr = req.peer_addr();

        // VULN-006: Check for duplicate critical headers before processing
        if has_duplicate_critical_headers(&req) {
            warn!(
                peer_addr = ?peer_addr,
                "Duplicate critical headers detected - rejecting request (VULN-006)"
            );
            return Box::pin(async move {
                Err(actix_web::error::ErrorBadRequest(
                    "Duplicate critical headers not allowed",
                ))
            });
        }

        // Check for client certificate in request extensions
        // In a proper mTLS setup with Actix-web + rustls, the certificate
        // would be extracted from the TLS connection before reaching this middleware
        let has_client_cert = req.extensions().get::<ClientCertInfo>().is_some();

        if !has_client_cert {
            // No client certificate provided - silent drop
            warn!(
                peer_addr = ?peer_addr,
                "No client certificate provided - dropping connection (mTLS required)"
            );
            // Return error immediately without calling service
            return Box::pin(async move {
                Err(actix_web::error::ErrorBadRequest(
                    "Client certificate required",
                ))
            });
        }

        // Certificate present - validate it
        let cert_info = req.extensions().get::<ClientCertInfo>().cloned();

        if let Some(info) = cert_info {
            // Validate certificate against CA store
            match validate_client_certificate(&info, &cert_store) {
                Ok(_) => {
                    info!(
                        subject = %info.subject,
                        issuer = %info.issuer,
                        peer_addr = ?peer_addr,
                        "mTLS client certificate validated successfully"
                    );
                }
                Err(e) => {
                    warn!(
                        error = %e,
                        peer_addr = ?peer_addr,
                        "mTLS client certificate validation failed - dropping connection"
                    );
                    return Box::pin(async move {
                        Err(actix_web::error::ErrorBadRequest(
                            "Certificate validation failed",
                        ))
                    });
                }
            }
        } else {
            warn!(
                peer_addr = ?peer_addr,
                "No client certificate provided - dropping connection (mTLS required)"
            );
            return Box::pin(async move {
                Err(actix_web::error::ErrorBadRequest(
                    "Client certificate required",
                ))
            });
        }

        debug!("mTLS authentication passed for request");

        // All checks passed - call the service
        let fut = self.service.call(req);
        Box::pin(fut)
    }
}

/// Certificate information extracted from client certificate
#[derive(Debug, Clone)]
pub struct ClientCertInfo {
    pub subject: String,
    pub issuer: String,
    pub serial: String,
    pub not_before: DateTime<Utc>,
    pub not_after: DateTime<Utc>,
}

/// Validate client certificate against CA store
fn validate_client_certificate(
    cert_info: &ClientCertInfo,
    _cert_store: &RootCertStore,
) -> Result<(), MtlsError> {
    // Check certificate validity period
    let now = Utc::now();

    if now < cert_info.not_before {
        return Err(MtlsError::ValidationError(
            "Certificate is not yet valid".to_string(),
        ));
    }

    if now > cert_info.not_after {
        return Err(MtlsError::ValidationError(
            "Certificate has expired".to_string(),
        ));
    }

    // In production, would verify certificate chain against CA store
    // For now, we trust certificates that were extracted from the TLS connection

    Ok(())
}

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

    #[test]
    fn test_mtls_config_creation() {
        let config = MtlsConfig {
            ca_cert_path: "/etc/linux_patch_api/certs/ca.pem".to_string(),
            server_cert_path: "/etc/linux_patch_api/certs/server.pem".to_string(),
            server_key_path: "/etc/linux_patch_api/certs/server.key".to_string(),
            min_tls_version: "1.3".to_string(),
        };

        assert_eq!(config.ca_cert_path, "/etc/linux_patch_api/certs/ca.pem");
        assert_eq!(config.min_tls_version, "1.3");
    }

    #[test]
    fn test_client_cert_info() {
        let info = ClientCertInfo {
            subject: "CN=test-client".to_string(),
            issuer: "CN=Test CA".to_string(),
            serial: "12345".to_string(),
            not_before: Utc::now() - Duration::days(1),
            not_after: Utc::now() + Duration::days(365),
        };

        assert!(info.subject.contains("CN="));
        assert!(info.issuer.contains("CN="));

        // Test validation with valid cert
        let cert_store = RootCertStore::empty();
        assert!(validate_client_certificate(&info, &cert_store).is_ok());
    }

    #[test]
    fn test_client_cert_expired() {
        let info = ClientCertInfo {
            subject: "CN=expired-client".to_string(),
            issuer: "CN=Test CA".to_string(),
            serial: "12345".to_string(),
            not_before: Utc::now() - Duration::days(365),
            not_after: Utc::now() - Duration::days(1),
        };

        let cert_store = RootCertStore::empty();
        let result = validate_client_certificate(&info, &cert_store);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("expired"));
    }
}
