//! Hot-reload contract test for `RustlsConfig::reload_from_pem_file`
//! (deploy-C §5.4). The contract — verified atomic via arc-swap in
//! `axum-server 0.8/src/axum_server/tls_rustls/mod.rs` — is: a
//! successful reload swaps the resolver, so NEW TLS handshakes
//! negotiate against the new cert while LIVE TLS connections keep
//! their session state machine. This test opens a TLS connection,
//! swaps the cert, calls `reload_from_pem_file`, opens a second TLS
//! connection, and asserts the two connections see different
//! `peer_certificates()` (the new handshake used the new cert).

use std::sync::Arc;

use axum_server::tls_rustls::RustlsConfig;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::{ClientConfig, DigitallySignedStruct, RootCertStore, SignatureScheme};

#[derive(Debug)]
struct InsecureVerifier;

impl ServerCertVerifier for InsecureVerifier {
    fn verify_server_cert(
        &self,
        _end_entity: &rustls::pki_types::CertificateDer<'_>,
        _intermediates: &[rustls::pki_types::CertificateDer<'_>],
        _server_name: &rustls::pki_types::ServerName<'_>,
        _ocsp_response: &[u8],
        _now: rustls::pki_types::UnixTime,
    ) -> Result<ServerCertVerified, rustls::Error> {
        Ok(ServerCertVerified::assertion())
    }

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

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

    fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
        vec![
            SignatureScheme::RSA_PKCS1_SHA256,
            SignatureScheme::ECDSA_NISTP256_SHA256,
            SignatureScheme::ED25519,
            SignatureScheme::RSA_PSS_SHA256,
        ]
    }
}

fn client_config() -> Arc<ClientConfig> {
    let _ = RootCertStore::empty();
    let cfg = ClientConfig::builder()
        .dangerous()
        .with_custom_certificate_verifier(Arc::new(InsecureVerifier))
        .with_no_client_auth();
    Arc::new(cfg)
}

fn gen_cert(cert_path: &std::path::Path, key_path: &std::path::Path, cn: &str) {
    let mut params = rcgen::CertificateParams::new(vec![cn.to_string()]).unwrap();
    params.distinguished_name = rcgen::DistinguishedName::new();
    params
        .distinguished_name
        .push(rcgen::DnType::CommonName, cn);
    let key_pair = rcgen::KeyPair::generate().unwrap();
    let cert = params.self_signed(&key_pair).unwrap();
    std::fs::write(cert_path, cert.pem()).unwrap();
    std::fs::write(key_path, key_pair.serialize_pem()).unwrap();
}

async fn dial_tls(
    addr: std::net::SocketAddr,
) -> tokio_rustls::client::TlsStream<tokio::net::TcpStream> {
    let server_name = rustls::pki_types::ServerName::try_from("localhost").unwrap();
    let tcp = tokio::net::TcpStream::connect(addr).await.unwrap();
    tokio_rustls::TlsConnector::from(client_config())
        .connect(server_name, tcp)
        .await
        .unwrap()
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reload_swaps_cert_without_dropping_live_connections() {
    // This test exercises the reload CONTRACT, not the SIGHUP wiring
    // (which is the host's signal-handler story, exercised in main.rs).
    // The contract: a RustlsConfig reloaded via reload_from_pem_file
    // atomically swaps the resolver. Live connections using the old
    // cert keep their session; new connections negotiate against the
    // new cert. We assert the new cert's peer_certificates differ.
    rustls::crypto::CryptoProvider::install_default(rustls::crypto::aws_lc_rs::default_provider())
        .expect("install rustls crypto provider");

    let dir = std::env::temp_dir().join(format!(
        "rutster-tls-reload-{}-{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    std::fs::create_dir_all(&dir).unwrap();
    let cert1 = dir.join("cert1.pem");
    let key1 = dir.join("key1.pem");
    let cert2 = dir.join("cert2.pem");
    let key2 = dir.join("key2.pem");
    gen_cert(&cert1, &key1, "cert-one");
    gen_cert(&cert2, &key2, "cert-two");

    let cfg = RustlsConfig::from_pem_file(&cert1, &key1).await.unwrap();

    // Open a TCP listener ourselves so we know the bound port. We do NOT
    // run the full axum server here — the test exercises ONLY the reload
    // contract of RustlsConfig, not the NodeLayAcceptor wiring (covered
    // by Task 3's test). A bare TlsAcceptor over our listener is enough.
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let cfg_for_server = cfg.clone();
    let server_task = tokio::spawn(async move {
        loop {
            let (tcp, _) = match listener.accept().await {
                Ok(v) => v,
                Err(_) => return,
            };
            // Fetch the current config snapshot for this handshake so a
            // reload that happens while we are blocked in accept() is
            // picked up immediately on the next handshake.
            let acceptor = tokio_rustls::TlsAcceptor::from(cfg_for_server.get_inner());
            // Hand off the handshake to a task so the accept loop keeps
            // cycling; we don't care about the connection's request body.
            tokio::spawn(async move {
                let _ = acceptor.accept(tcp).await;
            });
        }
    });

    // Dial connection #1 against cert1.
    let tls1 = dial_tls(addr).await;
    let peer1: Option<Vec<rustls::pki_types::CertificateDer<'static>>> = tls1
        .get_ref()
        .1
        .peer_certificates()
        .map(|c| c.iter().cloned().map(|c| c.into_owned()).collect());
    drop(tls1);

    // Reload to cert2.
    cfg.reload_from_pem_file(&cert2, &key2).await.unwrap();

    // Dial connection #2; must negotiate against cert2.
    let tls2 = dial_tls(addr).await;
    let peer2: Option<Vec<rustls::pki_types::CertificateDer<'static>>> = tls2
        .get_ref()
        .1
        .peer_certificates()
        .map(|c| c.iter().cloned().map(|c| c.into_owned()).collect());

    assert!(peer1.is_some(), "first handshake produced peer certs");
    assert!(peer2.is_some(), "second handshake produced peer certs");
    assert_ne!(
        peer1, peer2,
        "reload must swap the cert; the second handshake should see cert2"
    );

    drop(tls2);
    server_task.abort();
    let _ = std::fs::remove_dir_all(&dir);
}
