use super::*;
use std::fs;
use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};

fn write_temp_config(contents: &str) -> PathBuf {
    let nonce = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("system time must be after unix epoch")
        .as_nanos();
    let path = std::env::temp_dir().join(format!("telemt-load-mask-shape-security-{nonce}.toml"));
    fs::write(&path, contents).expect("temp config write must succeed");
    path
}

fn remove_temp_config(path: &PathBuf) {
    let _ = fs::remove_file(path);
}

#[test]
fn load_rejects_zero_mask_shape_bucket_floor_bytes() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_bucket_floor_bytes = 0
mask_shape_bucket_cap_bytes = 4096
"#,
    );

    let err =
        ProxyConfig::load(&path).expect_err("zero mask_shape_bucket_floor_bytes must be rejected");
    let msg = err.to_string();
    assert!(
        msg.contains("censorship.mask_shape_bucket_floor_bytes must be > 0"),
        "error must explain floor>0 invariant, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_rejects_mask_shape_bucket_cap_less_than_floor() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_bucket_floor_bytes = 1024
mask_shape_bucket_cap_bytes = 512
"#,
    );

    let err =
        ProxyConfig::load(&path).expect_err("mask_shape_bucket_cap_bytes < floor must be rejected");
    let msg = err.to_string();
    assert!(
        msg.contains(
            "censorship.mask_shape_bucket_cap_bytes must be >= censorship.mask_shape_bucket_floor_bytes"
        ),
        "error must explain cap>=floor invariant, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_accepts_mask_shape_bucket_cap_equal_to_floor() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_hardening = true
mask_shape_bucket_floor_bytes = 1024
mask_shape_bucket_cap_bytes = 1024
"#,
    );

    let cfg = ProxyConfig::load(&path).expect("equal cap and floor must be accepted");
    assert!(cfg.censorship.mask_shape_hardening);
    assert_eq!(cfg.censorship.mask_shape_bucket_floor_bytes, 1024);
    assert_eq!(cfg.censorship.mask_shape_bucket_cap_bytes, 1024);

    remove_temp_config(&path);
}

#[test]
fn load_rejects_above_cap_blur_when_shape_hardening_disabled() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_hardening = false
mask_shape_above_cap_blur = true
mask_shape_above_cap_blur_max_bytes = 64
"#,
    );

    let err =
        ProxyConfig::load(&path).expect_err("above-cap blur must require shape hardening enabled");
    let msg = err.to_string();
    assert!(
        msg.contains(
            "censorship.mask_shape_above_cap_blur requires censorship.mask_shape_hardening = true"
        ),
        "error must explain blur prerequisite, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_rejects_above_cap_blur_with_zero_max_bytes() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_hardening = true
mask_shape_above_cap_blur = true
mask_shape_above_cap_blur_max_bytes = 0
"#,
    );

    let err =
        ProxyConfig::load(&path).expect_err("above-cap blur max bytes must be > 0 when enabled");
    let msg = err.to_string();
    assert!(
        msg.contains("censorship.mask_shape_above_cap_blur_max_bytes must be > 0 when censorship.mask_shape_above_cap_blur is enabled"),
        "error must explain blur max bytes invariant, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_rejects_timing_normalization_floor_zero_when_enabled() {
    let path = write_temp_config(
        r#"
[censorship]
mask_timing_normalization_enabled = true
mask_timing_normalization_floor_ms = 0
mask_timing_normalization_ceiling_ms = 200
"#,
    );

    let err =
        ProxyConfig::load(&path).expect_err("timing normalization floor must be > 0 when enabled");
    let msg = err.to_string();
    assert!(
        msg.contains("censorship.mask_timing_normalization_floor_ms must be > 0 when censorship.mask_timing_normalization_enabled is true"),
        "error must explain timing floor invariant, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_rejects_timing_normalization_ceiling_below_floor() {
    let path = write_temp_config(
        r#"
[censorship]
mask_timing_normalization_enabled = true
mask_timing_normalization_floor_ms = 220
mask_timing_normalization_ceiling_ms = 200
"#,
    );

    let err = ProxyConfig::load(&path).expect_err("timing normalization ceiling must be >= floor");
    let msg = err.to_string();
    assert!(
        msg.contains("censorship.mask_timing_normalization_ceiling_ms must be >= censorship.mask_timing_normalization_floor_ms"),
        "error must explain timing ceiling/floor invariant, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_accepts_valid_timing_normalization_and_above_cap_blur_config() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_hardening = true
mask_shape_above_cap_blur = true
mask_shape_above_cap_blur_max_bytes = 128
mask_timing_normalization_enabled = true
mask_timing_normalization_floor_ms = 150
mask_timing_normalization_ceiling_ms = 240
"#,
    );

    let cfg = ProxyConfig::load(&path)
        .expect("valid blur and timing normalization settings must be accepted");
    assert!(cfg.censorship.mask_shape_hardening);
    assert!(cfg.censorship.mask_shape_above_cap_blur);
    assert_eq!(cfg.censorship.mask_shape_above_cap_blur_max_bytes, 128);
    assert!(cfg.censorship.mask_timing_normalization_enabled);
    assert_eq!(cfg.censorship.mask_timing_normalization_floor_ms, 150);
    assert_eq!(cfg.censorship.mask_timing_normalization_ceiling_ms, 240);

    remove_temp_config(&path);
}

#[test]
fn load_rejects_aggressive_shape_mode_when_shape_hardening_disabled() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_hardening = false
mask_shape_hardening_aggressive_mode = true
"#,
    );

    let err = ProxyConfig::load(&path)
        .expect_err("aggressive shape hardening mode must require shape hardening enabled");
    let msg = err.to_string();
    assert!(
        msg.contains("censorship.mask_shape_hardening_aggressive_mode requires censorship.mask_shape_hardening = true"),
        "error must explain aggressive-mode prerequisite, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_accepts_aggressive_shape_mode_when_shape_hardening_enabled() {
    let path = write_temp_config(
        r#"
[censorship]
mask_shape_hardening = true
mask_shape_hardening_aggressive_mode = true
mask_shape_above_cap_blur = true
mask_shape_above_cap_blur_max_bytes = 8
"#,
    );

    let cfg = ProxyConfig::load(&path)
        .expect("aggressive shape hardening mode should be accepted when prerequisites are met");
    assert!(cfg.censorship.mask_shape_hardening);
    assert!(cfg.censorship.mask_shape_hardening_aggressive_mode);
    assert!(cfg.censorship.mask_shape_above_cap_blur);

    remove_temp_config(&path);
}

#[test]
fn load_rejects_zero_mask_relay_max_bytes() {
    let path = write_temp_config(
        r#"
[censorship]
mask_relay_max_bytes = 0
"#,
    );

    let err = ProxyConfig::load(&path).expect_err("mask_relay_max_bytes must be > 0");
    let msg = err.to_string();
    assert!(
        msg.contains("censorship.mask_relay_max_bytes must be > 0"),
        "error must explain non-zero relay cap invariant, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_rejects_mask_relay_max_bytes_above_upper_bound() {
    let path = write_temp_config(
        r#"
[censorship]
mask_relay_max_bytes = 67108865
"#,
    );

    let err =
        ProxyConfig::load(&path).expect_err("mask_relay_max_bytes above hard cap must be rejected");
    let msg = err.to_string();
    assert!(
        msg.contains("censorship.mask_relay_max_bytes must be <= 67108864"),
        "error must explain relay cap upper bound invariant, got: {msg}"
    );

    remove_temp_config(&path);
}

#[test]
fn load_accepts_valid_mask_relay_max_bytes() {
    let path = write_temp_config(
        r#"
[censorship]
mask_relay_max_bytes = 8388608
"#,
    );

    let cfg = ProxyConfig::load(&path).expect("valid mask_relay_max_bytes must be accepted");
    assert_eq!(cfg.censorship.mask_relay_max_bytes, 8_388_608);

    remove_temp_config(&path);
}
