//! Integration test for `rutster::serve_tls::serve_tls_with_nodelay`
//! (deploy slice C §5.4): the production TLS serve path binds, accepts a TLS
//! handshake (using a self-signed cert generated at test time), serves an
//! HTTP request, and stays up.
//!
//! TCP_NODELAY parity is implicit: `NodeLayAcceptor` sets it on every accepted
//! socket before the TLS handshake. A regression would surface instead in plan
//! A's sim-bench NODELAY assertion; this test only proves the TLS path is
//! functional.

use std::io;
use std::sync::Arc;
use std::time::Duration;

use axum_server::tls_rustls::RustlsConfig;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::{ClientConfig, DigitallySignedStruct, SignatureScheme};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;

/// A `ServerCertVerifier` that accepts any cert — test-only, for connecting
/// to a server presenting a self-signed cert outside the public trust store.
/// Do NOT use in production code paths (it would defeat TLS authentication).
#[derive(Debug)]
struct InsecureVerifier;

impl ServerCertVerifier for InsecureVerifier {
    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_SHA256,
            SignatureScheme::ECDSA_NISTP256_SHA256,
            SignatureScheme::ED25519,
            SignatureScheme::RSA_PSS_SHA256,
            SignatureScheme::RSA_PKCS1_SHA384,
            SignatureScheme::ECDSA_NISTP384_SHA384,
            SignatureScheme::RSA_PSS_SHA384,
            SignatureScheme::RSA_PKCS1_SHA512,
            SignatureScheme::RSA_PSS_SHA512,
            SignatureScheme::ECDSA_NISTP521_SHA512,
        ]
    }
}

fn client_config() -> Arc<ClientConfig> {
    Arc::new(
        ClientConfig::builder()
            .dangerous()
            .with_custom_certificate_verifier(Arc::new(InsecureVerifier))
            .with_no_client_auth(),
    )
}

fn write_self_signed_cert(
    cert_path: &std::path::Path,
    key_path: &std::path::Path,
    common_name: &str,
) {
    let rcgen::CertifiedKey { cert, signing_key } =
        rcgen::generate_simple_self_signed(vec![common_name.to_string()]).unwrap();
    std::fs::write(cert_path, cert.pem()).unwrap();
    std::fs::write(key_path, signing_key.serialize_pem()).unwrap();
}

/// Pick a currently-free IPv4 loopback address.
///
/// We bind a throw-away listener, read the ephemeral port, and drop it so the
/// server can bind the same address. The race window is tiny and acceptable
/// for a single-threaded test; a failure would surface as a bind error from
/// the server task.
async fn free_ipv4_addr() -> io::Result<std::net::SocketAddr> {
    let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
    let addr = listener.local_addr()?;
    drop(listener);
    Ok(addr)
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn serve_tls_binds_and_serves_healthz() {
    // rustls is compiled with both `aws-lc-rs` and `ring` features, so there
    // is no single automatic process-level CryptoProvider. Install one up
    // front; both the test client and axum-server's RustlsConfig need it.
    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-serve-tls-{}-{}",
        std::process::id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap()
            .as_nanos()
    ));
    std::fs::create_dir_all(&dir).unwrap();
    let cert_path = dir.join("cert.pem");
    let key_path = dir.join("key.pem");
    write_self_signed_cert(&cert_path, &key_path, "round-trip-test");

    let config = RustlsConfig::from_pem_file(&cert_path, &key_path)
        .await
        .unwrap();

    let app = rutster::routes::router(rutster::session_map::AppState::default());
    let addr = free_ipv4_addr().await.unwrap();

    let (stop_tx, stop_rx) = tokio::sync::oneshot::channel::<()>();
    let mut server = tokio::spawn(rutster::serve_tls::serve_tls_with_nodelay(
        addr,
        config,
        app,
        async {
            let _ = stop_rx.await;
        },
    ));

    // Give the server a moment to bind + enter the accept loop.
    tokio::time::sleep(Duration::from_millis(200)).await;

    // Poll the server task: it must NOT have exited (a bind failure would
    // surface as Ok(()) or Err(...)) — Pending means it's running.
    assert!(
        tokio::time::timeout(Duration::from_millis(50), &mut server)
            .await
            .is_err(),
        "server task unexpectedly exited (bind failure?)"
    );

    // Dial with a tokio-rustls client using the test-only verifier.
    let stream = TcpStream::connect(addr).await.unwrap();
    let connector = tokio_rustls::TlsConnector::from(client_config());
    let server_name = ServerName::try_from("round-trip-test").unwrap();
    let mut tls = connector.connect(server_name, stream).await.unwrap();

    tls.write_all(b"GET /healthz HTTP/1.1\r\nHost: round-trip-test\r\nConnection: close\r\n\r\n")
        .await
        .unwrap();

    let mut buf = [0u8; 4096];
    let n = tls.read(&mut buf).await.unwrap();
    let response = String::from_utf8_lossy(&buf[..n]);
    assert!(
        response.contains("200 OK"),
        "expected 200 OK from /healthz, got: {response}"
    );

    // The server should still be running after the request.
    assert!(
        tokio::time::timeout(Duration::from_millis(50), &mut server)
            .await
            .is_err(),
        "server task exited after serving the request"
    );

    let _ = stop_tx.send(());
    server.abort();
    let _ = std::fs::remove_dir_all(&dir);
}
