//! TLS posture coverage for the Rust client.
//!
//! tonic 0.13.1's `ClientTlsConfig` exposes no hook for a custom rustls
//! `ServerCertVerifier` (the verifier is built internally inside the
//! crate-private `TlsConnector`), so the Rust client cannot implement the
//! "accept any server certificate" lenient default the other clients use.
//! Rust is therefore the documented **pin-only exception**: TLS without a
//! pinned CA is rejected up front with a clear, actionable error, and
//! supplying a CA file is the supported path. These tests pin that contract.

use std::time::Duration;

use zb_mom_ww_mxgateway_client::{ClientOptions, Error, GalaxyClient, GatewayClient};

/// Drive `connect` to its error without requiring `GatewayClient: Debug`
/// (the success arm is dropped explicitly so `unwrap_err` is unnecessary).
async fn connect_err(options: ClientOptions) -> Error {
    match GatewayClient::connect(options).await {
        Ok(_client) => panic!("connect unexpectedly succeeded against a dead TLS address"),
        Err(error) => error,
    }
}

#[tokio::test]
async fn tls_without_ca_is_rejected_with_actionable_error_by_default() {
    let options = ClientOptions::new("https://127.0.0.1:1")
        .with_plaintext(false)
        .with_connect_timeout(Duration::from_millis(200));

    let error = connect_err(options).await;

    let Error::InvalidEndpoint { detail, .. } = error else {
        panic!("expected InvalidEndpoint, got {error:?}");
    };
    // The message must point the caller at the supported remedy (pin a CA)
    // and name the opt-in escape hatch.
    assert!(
        detail.contains("ca_file") || detail.contains("CA"),
        "error should instruct the user to pass a CA file: {detail}"
    );
    assert!(
        detail.contains("require_certificate_validation"),
        "error should mention the require_certificate_validation opt-in: {detail}"
    );
}

#[tokio::test]
async fn tls_with_require_certificate_validation_does_not_short_circuit() {
    // With strict verification opted in, the no-CA guard must not fire; the
    // connect attempt instead proceeds to the transport (and fails to reach
    // the dead address) rather than returning the "CA required" guard error.
    let options = ClientOptions::new("https://127.0.0.1:1")
        .with_plaintext(false)
        .with_require_certificate_validation(true)
        .with_connect_timeout(Duration::from_millis(200));

    let error = connect_err(options).await;

    assert!(
        !matches!(&error, Error::InvalidEndpoint { detail, .. }
            if detail.contains("require_certificate_validation")),
        "strict verification must bypass the no-CA guard, got {error:?}"
    );
}

#[tokio::test]
async fn tls_with_ca_file_is_permitted_and_proceeds_past_the_guard() {
    // Pinning a CA is the supported TLS path: the no-CA guard must not fire.
    // We hand it a readable PEM file; construction proceeds past the guard
    // and only fails later at the transport (dead address / handshake).
    let ca_path = std::env::temp_dir().join("mxgw-rust-tls-ca-fixture.pem");
    std::fs::write(&ca_path, SELF_SIGNED_CA_PEM).unwrap();

    let options = ClientOptions::new("https://127.0.0.1:1")
        .with_plaintext(false)
        .with_ca_file(&ca_path)
        .with_connect_timeout(Duration::from_millis(200));

    let error = connect_err(options).await;

    let _ = std::fs::remove_file(&ca_path);

    assert!(
        !matches!(&error, Error::InvalidEndpoint { detail, .. }
            if detail.contains("require_certificate_validation")),
        "pinning a CA must bypass the no-CA guard, got {error:?}"
    );
}

/// Drive `GalaxyClient::connect` to its error (mirrors `connect_err` above).
async fn galaxy_connect_err(options: ClientOptions) -> Error {
    match GalaxyClient::connect(options).await {
        Ok(_client) => {
            panic!("GalaxyClient::connect unexpectedly succeeded against a dead TLS address")
        }
        Err(error) => error,
    }
}

#[tokio::test]
async fn galaxy_tls_without_ca_is_rejected_with_actionable_error_by_default() {
    // GalaxyClient::connect must apply the same TLS guard as GatewayClient —
    // TLS without a pinned CA (and without require_certificate_validation)
    // returns a clear, actionable InvalidEndpoint error.
    let options = ClientOptions::new("https://127.0.0.1:1")
        .with_plaintext(false)
        .with_connect_timeout(Duration::from_millis(200));

    let error = galaxy_connect_err(options).await;

    let Error::InvalidEndpoint { detail, .. } = error else {
        panic!("expected InvalidEndpoint, got {error:?}");
    };
    assert!(
        detail.contains("ca_file") || detail.contains("CA"),
        "error should instruct the user to pass a CA file: {detail}"
    );
    assert!(
        detail.contains("require_certificate_validation"),
        "error should mention the require_certificate_validation opt-in: {detail}"
    );
}

/// A throwaway self-signed CA certificate (PEM). Only needs to parse as a
/// PEM trust root so the CA-pinning path is exercised past the guard.
const SELF_SIGNED_CA_PEM: &str = "-----BEGIN CERTIFICATE-----
MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw
DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow
EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d
7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B
5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr
BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1
NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l
Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc
6MF9+Yw1Yy0t
-----END CERTIFICATE-----
";
