// Security coverage for relay TLS/mTLS fail-closed policy.
//
// Scope: exercise local TLS policy helpers and preserve installer/runtime
// markers that make production relay access client-authenticated.
// Targets: `server/src/security.rs`, `client/src/relay_transport.rs`, and
// install-time PKI handling.
// Why: HID, clipboard, mic, and camera RPCs are too powerful to rely on
// application-level good behavior; production trust must be enforced at the
// transport boundary.

use lesavka_client::relay_transport::{endpoint, enforce_transport_policy};
use lesavka_server::security::server_tls_config;
use serial_test::serial;
use temp_env::with_vars;
use tempfile::tempdir;

const SERVER_INSTALL: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/install/server.sh"
));
const CLIENT_INSTALL: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/install/client.sh"
));
const SERVER_SECURITY: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/server/src/security.rs"
));
const CLIENT_TRANSPORT: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/client/src/relay_transport.rs"
));
const SERVER_ENTRYPOINT: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/server/src/main/entrypoint.rs"
));

#[test]
#[serial]
fn production_tls_requires_server_identity_files() {
    with_vars(
        [
            ("LESAVKA_REQUIRE_TLS", Some("1")),
            ("LESAVKA_TLS_CERT", Some("/tmp/lesavka-missing-server.crt")),
            ("LESAVKA_TLS_KEY", Some("/tmp/lesavka-missing-server.key")),
            ("LESAVKA_TLS_CLIENT_CA", Some("/tmp/lesavka-missing-ca.crt")),
        ],
        || {
            let err = server_tls_config().expect_err("required TLS must fail closed");
            assert!(err.to_string().contains("LESAVKA_REQUIRE_TLS=1"));
        },
    );
}

#[test]
#[serial]
fn server_tls_config_accepts_identity_and_keeps_client_ca_binding_explicit() {
    let dir = tempdir().expect("tls dir");
    let cert = dir.path().join("server.crt");
    let key = dir.path().join("server.key");
    let ca = dir.path().join("ca.crt");
    std::fs::write(&cert, b"not a real cert").expect("write cert");
    std::fs::write(&key, b"not a real key").expect("write key");
    std::fs::write(&ca, b"not a real ca").expect("write ca");

    with_vars(
        [
            ("LESAVKA_REQUIRE_TLS", Some("1")),
            ("LESAVKA_TLS_CERT", Some(cert.to_string_lossy().as_ref())),
            ("LESAVKA_TLS_KEY", Some(key.to_string_lossy().as_ref())),
            ("LESAVKA_TLS_CLIENT_CA", Some(ca.to_string_lossy().as_ref())),
            ("LESAVKA_TLS_CLIENT_AUTH_OPTIONAL", Some("0")),
        ],
        || {
            assert!(
                server_tls_config()
                    .expect("identity and CA should build TLS config")
                    .is_some()
            );
        },
    );

    assert!(SERVER_SECURITY.contains("client_ca_root"));
    assert!(SERVER_SECURITY.contains("LESAVKA_TLS_CLIENT_AUTH_OPTIONAL"));
}

#[test]
#[serial]
fn client_https_endpoint_fails_fast_without_enrollment() {
    let dir = tempdir().expect("empty home");
    with_vars(
        [
            ("HOME", Some(dir.path().to_string_lossy().as_ref())),
            ("LESAVKA_TLS_CA", None::<&str>),
            ("LESAVKA_TLS_CLIENT_CERT", None::<&str>),
            ("LESAVKA_TLS_CLIENT_KEY", None::<&str>),
            ("LESAVKA_ALLOW_INSECURE", None::<&str>),
        ],
        || {
            let err = endpoint("https://38.28.125.112:50051")
                .expect_err("missing client cert must fail before connecting");
            let rendered = format!("{err:#}");
            assert!(rendered.contains("TLS enrollment is missing"));
            assert!(rendered.contains("client.key"));
        },
    );
}

#[test]
#[serial]
fn client_https_endpoint_rejects_malformed_enrollment_material() {
    let dir = tempdir().expect("pki home");
    let pki = dir.path().join(".config/lesavka/pki");
    std::fs::create_dir_all(&pki).expect("create pki");
    std::fs::write(pki.join("ca.crt"), b"wrong ca").expect("write ca");
    std::fs::write(pki.join("client.crt"), b"malformed cert").expect("write cert");
    std::fs::write(pki.join("client.key"), b"malformed key").expect("write key");

    with_vars(
        [
            ("HOME", Some(dir.path().to_string_lossy().as_ref())),
            ("LESAVKA_TLS_CA", None::<&str>),
            ("LESAVKA_TLS_CLIENT_CERT", None::<&str>),
            ("LESAVKA_TLS_CLIENT_KEY", None::<&str>),
        ],
        || {
            let err = endpoint("https://lesavka-server:50051")
                .expect_err("malformed cert/key material must fail");
            assert!(format!("{err:#}").contains("configuring relay TLS"));
        },
    );
}

#[test]
#[serial]
fn public_plaintext_transport_requires_deliberate_lab_override() {
    with_vars([("LESAVKA_ALLOW_INSECURE", None::<&str>)], || {
        let err = enforce_transport_policy("http://38.28.125.112:50051")
            .expect_err("public plaintext relay should be rejected");
        assert!(
            err.to_string()
                .contains("refusing insecure relay transport")
        );
    });

    with_vars([("LESAVKA_ALLOW_INSECURE", Some("1"))], || {
        enforce_transport_policy("http://38.28.125.112:50051")
            .expect("explicit lab override should allow plaintext");
    });
}

#[test]
fn install_and_runtime_paths_keep_mtls_client_identity_first_class() {
    for marker in [
        "LESAVKA_REQUIRE_TLS:-1",
        "LESAVKA_TLS_CLIENT_CA",
        "openssl genrsa -out \"$LESAVKA_TLS_DIR/client.key\"",
        "lesavka-client-pki.tar.gz",
        "chmod 0600 \"$LESAVKA_TLS_DIR/\"*.key",
    ] {
        assert!(
            SERVER_INSTALL.contains(marker),
            "server installer should preserve mTLS marker {marker}"
        );
    }

    for marker in [
        "LESAVKA_CLIENT_PKI_BUNDLE",
        "ca.crt",
        "client.crt",
        "client.key",
        "install -m 0600",
    ] {
        assert!(
            CLIENT_INSTALL.contains(marker),
            "client installer should preserve enrollment marker {marker}"
        );
    }

    assert!(SERVER_ENTRYPOINT.contains("security::server_tls_config()?"));
    assert!(SERVER_ENTRYPOINT.contains(".tls_config(tls)?"));
    assert!(CLIENT_TRANSPORT.contains("tls.identity(Identity::from_pem(cert, key))"));
    assert!(CLIENT_TRANSPORT.contains("tls.ca_certificate(Certificate::from_pem(ca))"));
}
