use std::fs;

use rcgen::generate_simple_self_signed;
use runewarp::{
    LogLevel, ServerCertificateSettings, initialize_manual_server_certificate, load_server_settings,
};
use tempfile::tempdir;

#[test]
fn server_settings_accept_exact_match_tunnels_and_default_logs_to_true() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "Tunnel.Example.Test."
cert-dir = "server-cert"

[[server.tunnels]]
public-hostnames = ["App.Example.Test.", "api.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let settings = load_server_settings(&tempdir.path().join("config.toml")).unwrap();

    assert_eq!(settings.hostname, "tunnel.example.test");
    assert_eq!(settings.log_level, LogLevel::Info);
    assert_eq!(
        settings.tunnels[0].public_hostnames,
        vec!["app.example.test", "api.example.test"]
    );
}

#[test]
fn server_settings_accept_flat_cert_dir_and_default_to_manual_mode() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "Tunnel.Example.Test."
cert-dir = "server-cert"

[[server.tunnels]]
public-hostnames = ["App.Example.Test.", "api.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let settings = load_server_settings(&tempdir.path().join("config.toml")).unwrap();

    assert_eq!(settings.hostname, "tunnel.example.test");
    assert_eq!(settings.log_level, LogLevel::Info);
    assert_eq!(
        settings.certificate,
        ServerCertificateSettings::Manual {
            directory: tempdir.path().join("server-cert"),
        }
    );
    assert_eq!(settings.public_bind_address, "0.0.0.0:443".parse().unwrap());
    assert_eq!(
        settings.tunnel_connection_bind_address,
        "0.0.0.0:443".parse().unwrap()
    );
    assert_eq!(
        settings.tunnels[0].public_hostnames,
        vec!["app.example.test", "api.example.test"]
    );
}

#[test]
fn server_settings_accept_explicit_listener_bind_addresses() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
public-bind-address = "127.0.0.1:8443"
tunnel-bind-address = "127.0.0.1:9443"

[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let settings = load_server_settings(&tempdir.path().join("config.toml")).unwrap();

    assert_eq!(
        settings.public_bind_address,
        "127.0.0.1:8443".parse().unwrap()
    );
    assert_eq!(
        settings.tunnel_connection_bind_address,
        "127.0.0.1:9443".parse().unwrap()
    );
}

#[test]
fn server_settings_accept_top_level_log_level() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
log-level = "debug"

[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let settings = load_server_settings(&tempdir.path().join("config.toml")).unwrap();

    assert_eq!(settings.log_level, LogLevel::Debug);
}

#[test]
fn server_settings_reject_legacy_server_logs_as_an_unknown_field() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
logs = false

[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();
    let message = error.to_string();

    assert!(!message.contains("failed to parse [server]"));
    assert!(message.contains("unknown field `logs`"));
}

#[test]
fn server_settings_reject_non_literal_listener_bind_addresses() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"
public-bind-address = "public.example.test:443"
tunnel-bind-address = "tunnel.example.test:443"

[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();
    let message = error.to_string();

    assert!(message.contains("server.public-bind-address is invalid"));
    assert!(message.contains("server.tunnel-bind-address is invalid"));
    assert!(!message.contains("failed to parse [server]"));
}

#[test]
fn server_settings_report_all_selected_mode_errors_together() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]

[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "not-lowercase-hex"

[[server.tunnels]]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();
    let message = error.to_string();

    assert!(message.contains("server.hostname is required"));
    assert!(message.contains("server.cert-dir directory not found"));
    assert!(message.contains("server.tunnels[].client-identity is invalid"));
    assert!(message.contains("server.tunnels[].public-hostnames is required"));
}

#[test]
fn server_settings_reject_acme_and_cert_dir_together() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::create_dir(tempdir.path().join("acme-state")).unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[server.acme]
email = "admin@example.test"
state-dir = "acme-state"

[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();

    assert!(
        error
            .to_string()
            .contains("[server.acme] and server.cert-dir are mutually exclusive")
    );
}

#[test]
fn server_settings_reject_invalid_manual_tls_material_during_validation() {
    let tempdir = tempdir().unwrap();
    let mismatched_key =
        generate_simple_self_signed(vec!["other.example.test".to_owned()]).unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("server-cert/server.key"),
        mismatched_key.signing_key.serialize_pem(),
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();

    assert!(error.to_string().contains("server TLS material is invalid"));
}

#[test]
fn server_settings_require_the_manual_server_ca_certificate() {
    let tempdir = tempdir().unwrap();
    let server_cert = generate_simple_self_signed(vec!["tunnel.example.test".to_owned()]).unwrap();
    fs::create_dir(tempdir.path().join("server-cert")).unwrap();
    fs::write(
        tempdir.path().join("server-cert/server.crt"),
        server_cert.cert.pem(),
    )
    .unwrap();
    fs::write(
        tempdir.path().join("server-cert/server.key"),
        server_cert.signing_key.serialize_pem(),
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();

    assert!(error.to_string().contains("server-ca.crt"));
}

#[test]
fn server_settings_reject_manual_tls_material_for_the_wrong_server_hostname() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "other.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();

    assert!(error.to_string().contains("server TLS material is invalid"));
}

#[test]
fn server_settings_reject_the_legacy_flat_server_surface() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-file = "server.crt"
key-file = "server.key"

[[server.tunnels]]
client-public-key-fingerprint = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();
    let message = error.to_string();

    assert!(!message.contains("failed to parse [server]"));
    assert!(message.contains("unknown field `cert-file`"));
    assert!(message.contains("unknown field `key-file`"));
    assert!(message.contains("unknown field `client-public-key-fingerprint`"));
    assert!(message.contains("server.cert-dir directory not found"));
    assert!(message.contains("server.tunnels[].client-identity is required"));
}

#[test]
fn server_settings_require_the_acme_state_directory_to_exist() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"

[server.acme]
email = "admin@example.test"
state-dir = "missing-acme-state"

[[server.tunnels]]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();

    assert!(
        error
            .to_string()
            .contains("server.acme.state-dir directory not found")
    );
}

#[test]
fn server_settings_reject_duplicate_public_hostnames_and_server_hostname_reuse() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
public-hostnames = ["App.Example.Test."]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"

[[server.tunnels]]
public-hostnames = ["app.example.test", "tunnel.example.test"]
client-identity = "111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();
    let message = error.to_string();

    assert!(message.contains(
        "server.tunnels[].public-hostnames must be unique after normalization: app.example.test"
    ));
    assert!(message.contains(
        "server.tunnels[].public-hostnames must not include server.hostname `tunnel.example.test`"
    ));
}

#[test]
fn server_settings_report_duplicate_hostnames_even_when_another_hostname_is_invalid() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
public-hostnames = ["App.Example.Test."]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"

[[server.tunnels]]
public-hostnames = ["app.example.test", "*.bad.example.test"]
client-identity = "111122223333444455556666777788889999aaaabbbbccccddddeeeeffff0000"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();
    let message = error.to_string();

    assert!(message.contains(
        "server.tunnels[].public-hostnames contains invalid hostname `*.bad.example.test`"
    ));
    assert!(message.contains(
        "server.tunnels[].public-hostnames must be unique after normalization: app.example.test"
    ));
}

#[test]
fn server_settings_reject_empty_public_hostname_lists() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
public-hostnames = []
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();

    assert!(
        error
            .to_string()
            .contains("server.tunnels[].public-hostnames must not be empty")
    );
}

#[test]
fn server_settings_reject_duplicate_client_identities() {
    let tempdir = tempdir().unwrap();
    initialize_manual_server_certificate(
        tempdir.path().join("server-cert").as_path(),
        "tunnel.example.test",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("config.toml"),
        r#"
[server]
hostname = "tunnel.example.test"
cert-dir = "server-cert"

[[server.tunnels]]
public-hostnames = ["app.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"

[[server.tunnels]]
public-hostnames = ["api.example.test"]
client-identity = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff"
"#,
    )
    .unwrap();

    let error = load_server_settings(&tempdir.path().join("config.toml")).unwrap_err();

    assert!(
        error
            .to_string()
            .contains("server.tunnels[].client-identity must be unique")
    );
}
