// TLS integration tests for Phase 16.2 (TCP), Phase 16.3 (HTTP), and Phase 16.4 (mTLS).

mod common;

use std::time::Duration;

use bytes::Bytes;
use fafb_q::codec::Frame;
use fafb_q::types::{NamespaceId, TopicName};

use common::{
    TEST_KEY, TEST_NS, TlsTestClient, make_test_tls_connector, start_test_tls_tcp_server,
};

// -- Phase 16.2: TLS on TCP tests (existing) --

/// Full round-trip over TLS: connect → auth → publish → subscribe → receive.
#[tokio::test]
async fn tls_connect_auth_publish_subscribe() {
    let server = start_test_tls_tcp_server().await;
    let connector = make_test_tls_connector(&server.certs.root_cert_der);

    // Publisher
    let mut publisher =
        TlsTestClient::connect_and_auth_tls(&server.addr, &connector, TEST_NS, TEST_KEY).await;

    // Subscriber
    let mut subscriber =
        TlsTestClient::connect_and_auth_tls(&server.addr, &connector, TEST_NS, TEST_KEY).await;

    let topic = TopicName("tls-topic".into());

    // Subscribe
    subscriber
        .send(Frame::Subscribe {
            topic: topic.clone(),
            group: None,
        })
        .await;
    let ack = subscriber.recv().await;
    assert!(
        matches!(ack, Frame::SubscribeAck { .. }),
        "expected SubscribeAck, got: {ack:?}"
    );

    // Publish
    publisher
        .send(Frame::Publish {
            topic: topic.clone(),
            payload: Bytes::from_static(b"hello over TLS"),
            headers: vec![],
            ttl_ms: 0,
        })
        .await;
    let pub_ack = publisher.recv().await;
    assert!(
        matches!(pub_ack, Frame::PublishAck { .. }),
        "expected PublishAck, got: {pub_ack:?}"
    );

    // Receive the delivered message
    let delivered = subscriber
        .recv_timeout(Duration::from_secs(5))
        .await
        .expect("expected Deliver frame");
    match delivered {
        Frame::Deliver {
            topic: t, payload, ..
        } => {
            assert_eq!(t, topic);
            assert_eq!(payload.as_ref(), b"hello over TLS");
        }
        other => panic!("expected Deliver, got: {other:?}"),
    }

    server.shutdown();
}

/// Plaintext client connecting to a TLS port — handshake fails, connection drops.
#[tokio::test]
async fn plaintext_to_tls_port_rejected() {
    let server = start_test_tls_tcp_server().await;

    // Connect with a plaintext TestClient (no TLS)
    let mut client = common::TestClient::connect(&server.addr).await;

    // Try to send a Connect frame — the server expects a TLS handshake first,
    // so the framed write or subsequent read should fail.
    client
        .send(Frame::Connect {
            version: 1,
            namespace: NamespaceId(TEST_NS.into()),
            api_key: fafb_q::types::ApiKey(TEST_KEY.into()),
            capabilities: 0,
        })
        .await;

    // The server will see garbage (not a TLS ClientHello) and fail the handshake.
    // The client should get no valid response — either EOF or error.
    let result = client.recv_timeout(Duration::from_secs(2)).await;
    assert!(
        result.is_none(),
        "plaintext client should not receive a valid frame from TLS port"
    );

    server.shutdown();
}

/// TLS handshake timeout — a slow client that never completes the handshake.
#[tokio::test]
async fn tls_handshake_timeout() {
    // Start server with a very short handshake timeout (1s).
    // Only override handshake_timeout_secs — cert_file/key_file are overwritten
    // by start_test_tls_tcp_server_with_config with freshly generated certs.
    let tls_config = fafb_q::config::TlsConfig {
        enabled: true,
        handshake_timeout_secs: 1,
        min_version: "1.2".to_owned(),
        ..Default::default()
    };
    let server = common::start_test_tls_tcp_server_with_config(tls_config).await;

    // Connect a raw TCP socket but never send a TLS ClientHello.
    let mut stream = tokio::net::TcpStream::connect(&server.addr).await.unwrap();

    // Wait for the connection to be closed by the server due to handshake timeout.
    // Use a bounded read with overall timeout instead of sleeping — the server
    // should drop the stream within ~1s, we give it up to 5s.
    let mut buf = [0u8; 1];
    let result = tokio::time::timeout(
        Duration::from_secs(5),
        tokio::io::AsyncReadExt::read(&mut stream, &mut buf),
    )
    .await;

    match result {
        Ok(Ok(0)) => {}  // EOF — expected: server closed the connection
        Ok(Err(_)) => {} // IO error — also acceptable
        Err(_) => panic!("server did not close connection within 5s (handshake timeout was 1s)"),
        Ok(Ok(n)) => panic!("expected EOF or error, got {n} bytes"),
    }

    // Verify the server is still healthy by connecting a proper TLS client
    let connector = make_test_tls_connector(&server.certs.root_cert_der);
    let mut client =
        TlsTestClient::connect_and_auth_tls(&server.addr, &connector, TEST_NS, TEST_KEY).await;
    client
        .send(Frame::Publish {
            topic: TopicName("timeout-test".into()),
            payload: Bytes::from_static(b"still alive"),
            headers: vec![],
            ttl_ms: 0,
        })
        .await;
    let ack = client.recv().await;
    assert!(
        matches!(ack, Frame::PublishAck { .. }),
        "server should still be healthy after timeout, got: {ack:?}"
    );

    server.shutdown();
}

/// Multiple TLS clients can connect and operate concurrently.
#[tokio::test]
async fn tls_multiple_concurrent_clients() {
    let server = start_test_tls_tcp_server().await;
    let connector = make_test_tls_connector(&server.certs.root_cert_der);

    let topic = TopicName("concurrent-tls".into());

    // Connect 3 clients
    let mut clients = Vec::new();
    for _ in 0..3 {
        let c =
            TlsTestClient::connect_and_auth_tls(&server.addr, &connector, TEST_NS, TEST_KEY).await;
        clients.push(c);
    }

    // Each client publishes a message
    for (i, client) in clients.iter_mut().enumerate() {
        client
            .send(Frame::Publish {
                topic: topic.clone(),
                payload: Bytes::from(format!("msg-{i}")),
                headers: vec![],
                ttl_ms: 0,
            })
            .await;
        let ack = client.recv().await;
        assert!(
            matches!(ack, Frame::PublishAck { .. }),
            "client {i}: expected PublishAck, got: {ack:?}"
        );
    }

    server.shutdown();
}

// -- Phase 16.3: TLS on HTTP (HTTPS) tests --

/// HTTPS publish endpoint — publish a message over HTTPS and verify it's stored.
#[tokio::test]
async fn https_publish() {
    let server = common::start_test_https_server().await;
    let client = common::make_test_https_client(&server.certs.root_cert_der);

    let resp = client
        .post(format!(
            "{}/v1/{}/https-topic/publish",
            server.base_url, TEST_NS
        ))
        .header("Authorization", format!("Bearer {TEST_KEY}"))
        .body(b"hello over HTTPS".to_vec())
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 200, "publish over HTTPS should succeed");

    server.shutdown();
}

/// HTTPS health endpoints — /health/live, /health/ready, /health.
#[tokio::test]
async fn https_health_endpoints() {
    let server = common::start_test_https_server().await;
    let client = common::make_test_https_client(&server.certs.root_cert_der);

    // /health/live
    let resp = client
        .get(format!("{}/health/live", server.base_url))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "/health/live should return 200");

    // /health/ready
    let resp = client
        .get(format!("{}/health/ready", server.base_url))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "/health/ready should return 200");

    // /health (alias for /health/ready)
    let resp = client
        .get(format!("{}/health", server.base_url))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "/health should return 200");

    server.shutdown();
}

/// HTTPS fetch endpoint — publish then fetch over HTTPS.
#[tokio::test]
async fn https_fetch() {
    let server = common::start_test_https_server().await;
    let client = common::make_test_https_client(&server.certs.root_cert_der);

    // Publish a message first
    let resp = client
        .post(format!(
            "{}/v1/{}/https-fetch-topic/publish",
            server.base_url, TEST_NS
        ))
        .header("Authorization", format!("Bearer {TEST_KEY}"))
        .body(b"fetch me over HTTPS".to_vec())
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);

    // Fetch it
    let resp = client
        .get(format!(
            "{}/v1/{}/https-fetch-topic/fetch?offset=0&max_count=10",
            server.base_url, TEST_NS
        ))
        .header("Authorization", format!("Bearer {TEST_KEY}"))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "fetch over HTTPS should succeed");

    let body: serde_json::Value = resp.json().await.unwrap();
    assert!(
        body.get("messages").is_some(),
        "fetch response should contain messages"
    );

    server.shutdown();
}

/// HTTPS admin endpoints — /admin/drain/status requires admin key.
#[tokio::test]
async fn https_admin_endpoints() {
    let admin_key = "test-admin-key-at-least-24-chars";
    let server = common::start_test_https_admin_server(admin_key).await;
    let client = common::make_test_https_client(&server.certs.root_cert_der);

    // Without admin key — should return 403
    let resp = client
        .get(format!("{}/admin/drain/status", server.base_url))
        .send()
        .await
        .unwrap();
    assert_eq!(
        resp.status(),
        403,
        "admin endpoint without key should return 403"
    );

    // With admin key — should return 200
    let resp = client
        .get(format!("{}/admin/drain/status", server.base_url))
        .header("Authorization", format!("Bearer {admin_key}"))
        .send()
        .await
        .unwrap();
    assert_eq!(
        resp.status(),
        200,
        "admin endpoint with key should return 200"
    );

    server.shutdown();
}

/// Plaintext client connecting to an HTTPS port — handshake fails, request fails.
#[tokio::test]
async fn https_plaintext_rejected() {
    let server = common::start_test_https_server().await;

    // Try to make an HTTP (not HTTPS) request to the HTTPS port.
    // Extract the port from the base_url (https://127.0.0.1:PORT).
    let port = server
        .base_url
        .rsplit(':')
        .next()
        .unwrap()
        .parse::<u16>()
        .unwrap();
    let plaintext_url = format!("http://127.0.0.1:{port}/health/live");

    let client = reqwest::Client::new();
    let result = client
        .get(&plaintext_url)
        .timeout(Duration::from_secs(3))
        .send()
        .await;

    // The server expects a TLS handshake, so a plaintext request should fail
    // (connection reset, EOF, or timeout).
    assert!(
        result.is_err(),
        "plaintext request to HTTPS port should fail, got: {result:?}"
    );

    // Verify the server is still healthy with a proper HTTPS client.
    let tls_client = common::make_test_https_client(&server.certs.root_cert_der);
    let resp = tls_client
        .get(format!("{}/health/live", server.base_url))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200, "server should still be healthy");

    server.shutdown();
}

/// HTTPS handshake timeout — a slow client that never completes the handshake.
#[tokio::test]
async fn https_handshake_timeout() {
    // Start HTTPS server with a very short handshake timeout (1s).
    let tls_config = fafb_q::config::TlsConfig {
        enabled: true,
        handshake_timeout_secs: 1,
        min_version: "1.2".to_owned(),
        ..Default::default()
    };
    let server = common::start_test_https_server_with_tls_config(tls_config).await;

    // Connect a raw TCP socket but never send a TLS ClientHello.
    let port = server
        .base_url
        .rsplit(':')
        .next()
        .unwrap()
        .parse::<u16>()
        .unwrap();
    let mut stream = tokio::net::TcpStream::connect(format!("127.0.0.1:{port}"))
        .await
        .unwrap();

    // The handshake task should time out after ~1s. Since handshakes are
    // concurrent, the listener itself stays alive. We verify by checking
    // that a proper TLS client can still connect afterward.

    // Wait a bit for the timeout to fire.
    tokio::time::sleep(Duration::from_secs(2)).await;

    // The slow connection should be dropped (EOF or error on read).
    let mut buf = [0u8; 1];
    let result = tokio::time::timeout(
        Duration::from_secs(2),
        tokio::io::AsyncReadExt::read(&mut stream, &mut buf),
    )
    .await;
    match result {
        Ok(Ok(0)) | Ok(Err(_)) => {} // EOF or error — expected
        Err(_) => {
            // Timeout reading — also acceptable since the server may just
            // drop the socket without sending anything.
        }
        Ok(Ok(n)) => panic!("expected EOF or error, got {n} bytes"),
    }

    // Verify the server is still healthy — this confirms concurrent
    // handshakes work (the slow client didn't block the listener).
    let tls_client = common::make_test_https_client(&server.certs.root_cert_der);
    let resp = tls_client
        .get(format!("{}/health/live", server.base_url))
        .send()
        .await
        .unwrap();
    assert_eq!(
        resp.status(),
        200,
        "server should still be healthy after handshake timeout"
    );

    server.shutdown();
}

// -- Phase 16.4: mTLS (Mutual TLS) tests --

/// mTLS TCP: valid client cert signed by trusted CA → accepted, full round-trip works.
#[tokio::test]
async fn mtls_tcp_valid_client_cert_accepted() {
    let (server, mtls_certs) = common::start_test_mtls_tcp_server().await;

    let connector = common::make_test_mtls_connector(
        &mtls_certs.server_ca_der,
        &mtls_certs.client_cert_pem,
        &mtls_certs.client_key_pem,
    );

    let mut client =
        TlsTestClient::connect_and_auth_tls(&server.addr, &connector, TEST_NS, TEST_KEY).await;

    // Publish a message
    let topic = TopicName("mtls-topic".into());
    client
        .send(Frame::Publish {
            topic: topic.clone(),
            payload: Bytes::from_static(b"hello mTLS"),
            headers: vec![],
            ttl_ms: 0,
        })
        .await;
    let ack = client.recv().await;
    assert!(
        matches!(ack, Frame::PublishAck { .. }),
        "expected PublishAck, got: {ack:?}"
    );

    server.shutdown();
}

/// mTLS TCP: client with no cert → connection fails (handshake or first I/O).
#[tokio::test]
async fn mtls_tcp_no_client_cert_rejected() {
    let (server, mtls_certs) = common::start_test_mtls_tcp_server().await;

    // Connector that trusts the server but presents NO client certificate.
    let connector = common::make_test_no_client_cert_connector(&mtls_certs.server_ca_der);

    let stream = tokio::net::TcpStream::connect(&server.addr).await.unwrap();
    let server_name = rustls::pki_types::ServerName::try_from("localhost").unwrap();

    // The TLS handshake may appear to succeed from the client side (the alert
    // is buffered), but the first actual I/O operation will fail because the
    // server rejected the connection due to missing client cert.
    match connector.connect(server_name, stream).await {
        Err(_) => {
            // Handshake failed immediately — expected for some TLS implementations.
        }
        Ok(tls_stream) => {
            // Handshake appeared to succeed — try to use the connection.
            // The server-side rejection will surface as a read/write error.
            let mut framed = tokio_util::codec::Framed::new(
                tls_stream,
                fafb_q::codec::MessageCodec::new(1_048_576),
            );
            use futures::SinkExt;
            let send_result = framed
                .send(Frame::Connect {
                    version: 1,
                    namespace: NamespaceId(TEST_NS.into()),
                    api_key: fafb_q::types::ApiKey(TEST_KEY.into()),
                    capabilities: 0,
                })
                .await;

            if send_result.is_ok() {
                // Send appeared to succeed — try to read the response.
                use futures::StreamExt;
                let recv_result = tokio::time::timeout(Duration::from_secs(3), framed.next()).await;

                match recv_result {
                    Ok(Some(Ok(Frame::ConnectAck { success: true, .. }))) => {
                        panic!("mTLS server should not accept a client without a cert");
                    }
                    _ => {
                        // Connection error, timeout, or EOF — expected.
                    }
                }
            }
            // Send failed — expected (server closed the connection).
        }
    }

    // Server should still be healthy — connect with valid client cert.
    let valid_connector = common::make_test_mtls_connector(
        &mtls_certs.server_ca_der,
        &mtls_certs.client_cert_pem,
        &mtls_certs.client_key_pem,
    );
    let mut client =
        TlsTestClient::connect_and_auth_tls(&server.addr, &valid_connector, TEST_NS, TEST_KEY)
            .await;
    client
        .send(Frame::Publish {
            topic: TopicName("mtls-recovery".into()),
            payload: Bytes::from_static(b"still alive"),
            headers: vec![],
            ttl_ms: 0,
        })
        .await;
    let ack = client.recv().await;
    assert!(
        matches!(ack, Frame::PublishAck { .. }),
        "server should still be healthy after rejected handshake, got: {ack:?}"
    );

    server.shutdown();
}

/// mTLS TCP: client cert signed by untrusted CA → connection fails.
#[tokio::test]
async fn mtls_tcp_untrusted_ca_rejected() {
    let (server, mtls_certs) = common::start_test_mtls_tcp_server().await;

    // Connector with untrusted client cert (signed by a different CA).
    let connector = common::make_test_mtls_connector(
        &mtls_certs.server_ca_der,
        &mtls_certs.untrusted_client_cert_pem,
        &mtls_certs.untrusted_client_key_pem,
    );

    let stream = tokio::net::TcpStream::connect(&server.addr).await.unwrap();
    let server_name = rustls::pki_types::ServerName::try_from("localhost").unwrap();

    // Similar to the no-cert case: the client-side handshake may appear to
    // succeed, but the server will reject the untrusted cert. The error
    // surfaces on the first read/write.
    match connector.connect(server_name, stream).await {
        Err(_) => {
            // Immediate handshake failure — expected.
        }
        Ok(tls_stream) => {
            let mut framed = tokio_util::codec::Framed::new(
                tls_stream,
                fafb_q::codec::MessageCodec::new(1_048_576),
            );
            use futures::SinkExt;
            let send_result = framed
                .send(Frame::Connect {
                    version: 1,
                    namespace: NamespaceId(TEST_NS.into()),
                    api_key: fafb_q::types::ApiKey(TEST_KEY.into()),
                    capabilities: 0,
                })
                .await;

            if send_result.is_ok() {
                use futures::StreamExt;
                let recv_result = tokio::time::timeout(Duration::from_secs(3), framed.next()).await;

                match recv_result {
                    Ok(Some(Ok(Frame::ConnectAck { success: true, .. }))) => {
                        panic!("mTLS server should not accept a client with untrusted cert");
                    }
                    _ => {
                        // Connection error, timeout, or EOF — expected.
                    }
                }
            }
        }
    }

    server.shutdown();
}

/// mTLS HTTPS: valid client cert → publish succeeds.
#[tokio::test]
async fn mtls_https_valid_client_cert_accepted() {
    let (server, mtls_certs) = common::start_test_mtls_https_server().await;

    let client = common::make_test_mtls_https_client(&mtls_certs);

    let resp = client
        .post(format!(
            "{}/v1/{}/mtls-https-topic/publish",
            server.base_url, TEST_NS
        ))
        .header("Authorization", format!("Bearer {TEST_KEY}"))
        .body(b"hello mTLS HTTPS".to_vec())
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 200, "publish over mTLS HTTPS should succeed");

    server.shutdown();
}

/// mTLS HTTPS: no client cert → handshake rejected (connection error).
#[tokio::test]
async fn mtls_https_no_client_cert_rejected() {
    let (server, mtls_certs) = common::start_test_mtls_https_server().await;

    let client = common::make_test_no_client_cert_https_client(&mtls_certs);

    let result = client
        .get(format!("{}/health/live", server.base_url))
        .timeout(Duration::from_secs(5))
        .send()
        .await;

    // The server requires a client cert, so the handshake should fail.
    assert!(
        result.is_err(),
        "HTTPS request without client cert should fail when mTLS is enabled, got: {result:?}"
    );

    server.shutdown();
}

/// HTTPS graceful shutdown — verify server stops accepting after shutdown.
#[tokio::test]
async fn https_graceful_shutdown() {
    let server = common::start_test_https_server().await;
    let client = common::make_test_https_client(&server.certs.root_cert_der);

    // Verify server is alive
    let resp = client
        .get(format!("{}/health/live", server.base_url))
        .send()
        .await
        .unwrap();
    assert_eq!(resp.status(), 200);

    // Trigger shutdown
    let base_url = server.base_url.clone();
    server.shutdown();

    // Brief wait for shutdown to propagate
    tokio::time::sleep(Duration::from_millis(500)).await;

    // Connection should now fail
    let result = client
        .get(format!("{base_url}/health/live"))
        .timeout(Duration::from_secs(2))
        .send()
        .await;

    // Either connection refused or timeout — both are acceptable
    assert!(
        result.is_err() || result.as_ref().is_ok_and(|r| r.status().is_server_error()),
        "server should be unreachable after shutdown, got: {result:?}"
    );
}

// -- Phase 16.5: Certificate Hot Reload tests --

use common::{make_test_https_client, start_test_https_reload_server};

const ADMIN_KEY: &str = "test-admin-key-that-is-long-enough-for-validation";

/// Swap cert file on disk → call TlsProvider::reload() directly → verify
/// the provider picks up the new cert with a different expiry.
#[tokio::test]
async fn tls_reload_detects_changed_cert_file() {
    let server = start_test_https_reload_server(ADMIN_KEY).await;
    let old_expiry = server.tls_provider.cert_expiry_epoch();

    // Generate a new cert with different validity and overwrite the temp files.
    // We need to wait at least 1s so the filesystem mtime changes.
    tokio::time::sleep(Duration::from_secs(1)).await;

    let mut params = rcgen::CertificateParams::new(vec!["localhost".to_owned()]).unwrap();
    params
        .subject_alt_names
        .push(rcgen::SanType::IpAddress(std::net::IpAddr::V4(
            std::net::Ipv4Addr::LOCALHOST,
        )));
    // Set a different validity to distinguish from the original cert.
    params.not_after = rcgen::date_time_ymd(2040, 1, 1);
    let new_kp = rcgen::KeyPair::generate().unwrap();
    let new_cert = params.self_signed(&new_kp).unwrap();

    std::fs::write(server.certs.cert_file.path(), new_cert.pem()).unwrap();
    std::fs::write(server.certs.key_file.path(), new_kp.serialize_pem()).unwrap();

    let changed = server.tls_provider.reload().unwrap();
    assert!(changed, "reload should detect changed files");

    let new_expiry = server.tls_provider.cert_expiry_epoch();
    assert_ne!(old_expiry, new_expiry, "expiry should change after reload");
    assert!(
        server.tls_provider.cert_ttl_secs() > 0,
        "cert should still be valid"
    );
}

/// Reload with corrupt cert file → verify old cert continues serving,
/// existing connections still work.
#[tokio::test]
async fn tls_reload_fails_gracefully_with_corrupt_cert() {
    let server = start_test_https_reload_server(ADMIN_KEY).await;
    let client = make_test_https_client(&server.certs.root_cert_der);
    let old_expiry = server.tls_provider.cert_expiry_epoch();

    // Verify server is working before the bad reload.
    let resp = client
        .get(format!("{}/health/live", server.base_url))
        .send()
        .await
        .expect("health check should succeed");
    assert_eq!(resp.status(), 200);

    // Corrupt the cert file and attempt reload.
    tokio::time::sleep(Duration::from_secs(1)).await;
    std::fs::write(server.certs.cert_file.path(), b"not a valid cert").unwrap();

    let result = server.tls_provider.reload();
    assert!(result.is_err(), "reload should fail with corrupt cert");

    // Old cert continues serving — expiry unchanged.
    assert_eq!(server.tls_provider.cert_expiry_epoch(), old_expiry);

    // Server still responds on existing acceptor (new connections use old cert).
    let resp = client
        .get(format!("{}/health/live", server.base_url))
        .send()
        .await
        .expect("server should still work with old cert");
    assert_eq!(resp.status(), 200);
}

/// POST /admin/tls/reload triggers reload and returns per-provider results.
#[tokio::test]
async fn tls_admin_reload_endpoint() {
    let server = start_test_https_reload_server(ADMIN_KEY).await;
    let client = make_test_https_client(&server.certs.root_cert_der);

    // Call reload — certs haven't changed, should return "unchanged" per-provider.
    let resp = client
        .post(format!("{}/admin/tls/reload", server.base_url))
        .header("Authorization", format!("Bearer {ADMIN_KEY}"))
        .send()
        .await
        .expect("admin reload request");
    assert_eq!(resp.status(), 200);
    let body: serde_json::Value = resp.json().await.unwrap();
    let providers = body["providers"].as_array().expect("providers array");
    assert_eq!(providers.len(), 1);
    assert_eq!(providers[0]["status"], "unchanged");
    assert_eq!(providers[0]["listener"], "shared");
    assert!(providers[0]["cert_expires"].is_string());

    // Now swap the cert and call reload again.
    tokio::time::sleep(Duration::from_secs(1)).await;
    let mut params = rcgen::CertificateParams::new(vec!["localhost".to_owned()]).unwrap();
    params
        .subject_alt_names
        .push(rcgen::SanType::IpAddress(std::net::IpAddr::V4(
            std::net::Ipv4Addr::LOCALHOST,
        )));
    params.not_after = rcgen::date_time_ymd(2040, 1, 1);
    let new_kp = rcgen::KeyPair::generate().unwrap();
    let new_cert = params.self_signed(&new_kp).unwrap();
    std::fs::write(server.certs.cert_file.path(), new_cert.pem()).unwrap();
    std::fs::write(server.certs.key_file.path(), new_kp.serialize_pem()).unwrap();

    let resp = client
        .post(format!("{}/admin/tls/reload", server.base_url))
        .header("Authorization", format!("Bearer {ADMIN_KEY}"))
        .send()
        .await
        .expect("admin reload request after swap");
    assert_eq!(resp.status(), 200);
    let body: serde_json::Value = resp.json().await.unwrap();
    let providers = body["providers"].as_array().unwrap();
    assert_eq!(providers[0]["status"], "success");
    assert!(providers[0]["cert_expires"].is_string());
}

/// POST /admin/tls/reload without auth returns 403.
#[tokio::test]
async fn tls_admin_reload_requires_auth() {
    let server = start_test_https_reload_server(ADMIN_KEY).await;
    let client = make_test_https_client(&server.certs.root_cert_der);

    let resp = client
        .post(format!("{}/admin/tls/reload", server.base_url))
        .send()
        .await
        .expect("admin reload without auth");
    assert_eq!(resp.status(), 403);
}

/// GET /admin/tls/status returns cert details without triggering reload.
#[tokio::test]
async fn tls_admin_status_endpoint() {
    let server = start_test_https_reload_server(ADMIN_KEY).await;
    let client = make_test_https_client(&server.certs.root_cert_der);

    let resp = client
        .get(format!("{}/admin/tls/status", server.base_url))
        .header("Authorization", format!("Bearer {ADMIN_KEY}"))
        .send()
        .await
        .expect("admin tls status request");
    assert_eq!(resp.status(), 200);
    let body: serde_json::Value = resp.json().await.unwrap();
    let providers = body["providers"].as_array().expect("providers array");
    assert_eq!(providers.len(), 1);
    let p = &providers[0];
    assert_eq!(p["listener"], "shared");
    assert!(p["cert_expires"].is_string());
    assert!(p["cert_ttl_secs"].as_u64().unwrap() > 0);
    assert!(p["cert_fingerprint_sha256"].as_str().unwrap().contains(':'));
    assert_eq!(p["client_auth"], false);
    assert_eq!(p["min_version"], "1.2");
    assert!(p["cert_file"].is_string());
}

/// GET /admin/tls/status without auth returns 403.
#[tokio::test]
async fn tls_admin_status_requires_auth() {
    let server = start_test_https_reload_server(ADMIN_KEY).await;
    let client = make_test_https_client(&server.certs.root_cert_der);

    let resp = client
        .get(format!("{}/admin/tls/status", server.base_url))
        .send()
        .await
        .expect("admin tls status without auth");
    assert_eq!(resp.status(), 403);
}
