use rustls::pki_types::CertificateDer;
use snafu::Snafu;

#[derive(Debug)]
pub struct DangerousServerCertVerifier;

impl rustls::client::danger::ServerCertVerifier for DangerousServerCertVerifier {
    fn verify_server_cert(
        &self,
        _: &rustls::pki_types::CertificateDer<'_>,
        _: &[rustls::pki_types::CertificateDer<'_>],
        _: &rustls::pki_types::ServerName<'_>,
        _: &[u8],
        _: rustls::pki_types::UnixTime,
    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
        Ok(rustls::client::danger::ServerCertVerified::assertion())
    }

    fn verify_tls12_signature(
        &self,
        _: &[u8],
        _: &rustls::pki_types::CertificateDer<'_>,
        _: &rustls::DigitallySignedStruct,
    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
    }

    fn verify_tls13_signature(
        &self,
        _: &[u8],
        _: &rustls::pki_types::CertificateDer<'_>,
        _: &rustls::DigitallySignedStruct,
    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
    }

    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
        vec![
            rustls::SignatureScheme::RSA_PKCS1_SHA1,
            rustls::SignatureScheme::ECDSA_SHA1_Legacy,
            rustls::SignatureScheme::RSA_PKCS1_SHA256,
            rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
            rustls::SignatureScheme::RSA_PKCS1_SHA384,
            rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
            rustls::SignatureScheme::RSA_PKCS1_SHA512,
            rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
            rustls::SignatureScheme::RSA_PSS_SHA256,
            rustls::SignatureScheme::RSA_PSS_SHA384,
            rustls::SignatureScheme::RSA_PSS_SHA512,
            rustls::SignatureScheme::ED25519,
            rustls::SignatureScheme::ED448,
        ]
    }
}

#[derive(Debug, Snafu)]
#[snafu(module)]
pub enum InvalidIdentity {
    #[snafu(transparent)]
    Tls { source: rustls::Error },
    #[snafu(display("certificate for identity cannot be parsed"))]
    InvalidCertificate {
        source: x509_parser::nom::Err<x509_parser::error::X509Error>,
    },
    #[snafu(display("SAN extensions in certificate are invalid"))]
    InvalidSAN {
        source: x509_parser::error::X509Error,
    },
    #[snafu(display("certificate for identity is missing SAN extensions"))]
    MissingSAN,
    #[snafu(display("identity name not found in certificate SAN"))]
    NameNotFound,
}

pub fn verify_certificate_for_name(
    certificate: &CertificateDer<'static>,
    client_name: &str,
) -> Result<(), InvalidIdentity> {
    use x509_parser::prelude::*;

    let cert = match x509_parser::parse_x509_certificate(certificate) {
        Ok((_remain, cert)) => cert,
        Err(source) => return Err(InvalidIdentity::InvalidCertificate { source }),
    };
    let san = match cert.subject_alternative_name() {
        Ok(Some(san)) => san,
        Ok(None) => return Err(InvalidIdentity::MissingSAN),
        Err(source) => return Err(InvalidIdentity::InvalidSAN { source }),
    };

    if san.value.general_names.iter().any(|name| match name {
        GeneralName::DNSName(name) => *name == client_name,
        _ => false,
    }) {
        return Ok(());
    }

    Err(InvalidIdentity::NameNotFound)
}
