use std::path::Path;
use std::sync::Arc;

use anyhow::Context as _;
use rumqttc::TlsConfiguration;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified};
use rustls::{ClientConfig, DigitallySignedStruct, KeyLogFile, SignatureScheme};
use rustls_pki_types::pem::PemObject as _;
use rustls_pki_types::{CertificateDer, PrivateKeyDer, ServerName, UnixTime};
use rustls_platform_verifier::{BuilderVerifierExt as _, Verifier};

#[derive(Debug)]
struct NoVerifier;
impl rustls::client::danger::ServerCertVerifier for NoVerifier {
    fn verify_server_cert(
        &self,
        _end_entity: &CertificateDer<'_>,
        _intermediates: &[CertificateDer<'_>],
        _server_name: &ServerName<'_>,
        _ocsp_response: &[u8],
        _now: UnixTime,
    ) -> Result<ServerCertVerified, rustls::Error> {
        Ok(ServerCertVerified::assertion())
    }

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

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

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

pub fn create_tls_configuration(
    insecure: bool,
    ca_cert: Option<&Path>,
    client_cert: Option<&Path>,
    client_private_key: Option<&Path>,
) -> anyhow::Result<TlsConfiguration> {
    let conf = if let Some(ca_cert) = ca_cert {
        let builder = ClientConfig::builder();
        let verifier = Verifier::new_with_extra_roots(
            read_certificate_file(ca_cert).context("while reading ca-cert")?,
            builder.crypto_provider().clone(),
        )
        .context("while adding CA certificates")?;
        builder
            .dangerous()
            .with_custom_certificate_verifier(Arc::new(verifier))
    } else {
        ClientConfig::builder()
            .with_platform_verifier()
            .context("while reading platform verifier")?
    };

    let mut conf = match (client_cert, client_private_key) {
        (Some(client_cert), Some(client_private_key)) => conf
            .with_client_auth_cert(
                read_certificate_file(client_cert).context("while reading client-cert")?,
                PrivateKeyDer::from_pem_file(client_private_key)
                    .context("while reading client-private-key")?,
            )
            .context("while setting client auth cert")?,
        (None, None) => conf.with_no_client_auth(),
        _ => unreachable!("requires both cert and key which should be ensured by clap"),
    };
    conf.key_log = Arc::new(KeyLogFile::new());

    if insecure {
        let mut danger = conf.dangerous();
        danger.set_certificate_verifier(Arc::new(NoVerifier {}));
    }

    Ok(TlsConfiguration::Rustls(Arc::new(conf)))
}

fn read_certificate_file(
    file: &Path,
) -> Result<Vec<CertificateDer<'static>>, rustls_pki_types::pem::Error> {
    CertificateDer::pem_file_iter(file)?.collect()
}
