//! Integration test for the deploy-epoch-fix T3 fixes:
//!
//! 1. **rustls single-provider gate** — dropping `"ring"` from the direct
//!    `rustls` + `tokio-rustls` feature lists (root Cargo.toml) leaves
//!    `aws-lc-rs` as rustls's sole compiled CryptoProvider. rustls 0.23's
//!    `CryptoProvider::from_crate_features()` picks the process default
//!    via `#[cfg(all(feature = "aws_lc_rs", not(feature = "ring"), ...))]`
//!    — when both features were on, NEITHER cfg block matched and
//!    `RustlsConfig::from_pem_file` panicked with "Could not
//!    automatically determine the process-level CryptoProvider".
//!
//!    This test reproduces the production code path: it calls
//!    `RustlsConfig::from_pem_file` WITHOUT an explicit
//!    `CryptoProvider::install_default(...)` first (the existing
//!    `serve_tls_nodelay.rs` test installs one up front — that's the
//!    workaround, not the fix). After T3, the single-feature gate makes
//!    the explicit install unnecessary.
//!
//! 2. **TLS bind moved into the plaintext match arm** — `main.rs` used
//!    to bind `addr` unconditionally before branching to TLS or
//!    plaintext. The TLS arm's `serve_tls_with_nodelay(addr, ...)`
//!    binds `addr` itself, so the unconditional bind stole the port
//!    first → EADDRINUSE the moment TLS env was set. After T3, the
//!    bind lives inside the `(None, None)` plaintext arm only. This
//!    test exercises the TLS arm directly to confirm it binds cleanly
//!    (no competing listener). `main.rs` itself isn't invoked — the
//!    test fires `serve_tls_with_nodelay` against an ephemeral port and
//!    proves the same code path the TLS arm uses binds without
//!    EADDRINUSE.
//!
//! Test must FAIL before the T3 fix (provider panic + EADDRINUSE) and
//! PASS after. See `.omo/plans/deploy-epoch-fix.md` T3.

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() -> std::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 tls_boot_without_explicit_crypto_provider_install() {
    // THE T3 ASSERTION: do NOT call rustls::crypto::CryptoProvider::install_default(...)
    // before RustlsConfig::from_pem_file. The serve_tls_nodelay.rs test does
    // install_default as a workaround; this test exercises the production code
    // path, which has no install_default call in main.rs. After T3's fix
    // (drop "ring" feature → single-provider cfg gate → aws-lc-rs auto-installed),
    // from_pem_file succeeds without the explicit install. Before T3 it would
    // panic here with "Could not automatically determine the process-level
    // CryptoProvider".

    let dir = std::env::temp_dir().join(format!(
        "rutster-tls-boot-{}-{}",
        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, "tls-boot-test");

    // THE T3 ASSERTION (crypto provider): must not panic. Before the fix
    // (both `ring` + `aws-lc-rs` features on rustls), this call panics
    // inside axum-server's RustlsConfig::from_pem_file when it tries to
    // resolve the process-level CryptoProvider.
    let config = RustlsConfig::from_pem_file(&cert_path, &key_path)
        .await
        .expect("RustlsConfig::from_pem_file must not panic with single rustls provider");

    // THE T3 ASSERTION (listener bind): the TLS arm is the sole binder of
    // `addr` now — the unconditional `TcpListener::bind(addr)` was moved
    // into the plaintext match arm in main.rs. This test exercises the
    // same `serve_tls_with_nodelay(addr, ...)` path the TLS arm uses.
    // Before the fix, if anything (the test infra, or — in production —
    // the moved-out unconditional bind) had already bound addr, the
    // server task would exit with EADDRINUSE. After the fix, the bind
    // succeeds because nothing competes for the port.
    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? EADDRINUSE?)"
    );

    // Dial with a tokio-rustls client using the test-only verifier + assert
    // an https GET /healthz returns 200.
    let stream = TcpStream::connect(addr).await.unwrap();
    let connector = tokio_rustls::TlsConnector::from(client_config());
    let server_name = ServerName::try_from("tls-boot-test").unwrap();
    let mut tls = connector.connect(server_name, stream).await.unwrap();

    tls.write_all(b"GET /healthz HTTP/1.1\r\nHost: tls-boot-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 https /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);
}
