use super::*;
use crate::crypto::sha256_hmac;
use crate::tls_front::emulator::build_emulated_server_hello;
use crate::tls_front::types::{
    CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsProfileSource,
};
use std::time::SystemTime;

/// Build a TLS-handshake-like buffer that contains a valid HMAC digest
/// for the given `secret` and `timestamp`.
///
/// Layout (bytes):
///   [0..TLS_DIGEST_POS]         : fixed filler (0x42)
///   [TLS_DIGEST_POS..+32]       : digest = HMAC XOR [0..0 || timestamp_le]
///   [TLS_DIGEST_POS+32]         : session_id_len = 32
///   [TLS_DIGEST_POS+33..+65]    : session_id filler (0x42)
fn make_valid_tls_handshake_with_session_id(
    secret: &[u8],
    timestamp: u32,
    session_id: &[u8],
) -> Vec<u8> {
    let session_id_len = session_id.len();
    assert!(session_id_len <= u8::MAX as usize);
    let len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 + session_id_len;
    let mut handshake = vec![0x42u8; len];

    handshake[TLS_DIGEST_POS + TLS_DIGEST_LEN] = session_id_len as u8;
    let sid_start = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1;
    handshake[sid_start..sid_start + session_id_len].copy_from_slice(session_id);
    // Zero the digest slot before computing HMAC (mirrors what validate does).
    handshake[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);

    let computed = sha256_hmac(secret, &handshake);

    // digest = HMAC such that XOR with stored digest yields [0..0, timestamp_le].
    // bytes 0-27 of digest == computed[0..28]  -> xored[..28] == 0
    // bytes 28-31 of digest == computed[28..32] XOR timestamp_le
    let mut digest = computed;
    let ts = timestamp.to_le_bytes();
    for i in 0..4 {
        digest[28 + i] ^= ts[i];
    }

    handshake[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].copy_from_slice(&digest);
    handshake
}

fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec<u8> {
    make_valid_tls_handshake_with_session_id(secret, timestamp, &[0x42; 32])
}

// ------------------------------------------------------------------
// Happy-path sanity
// ------------------------------------------------------------------

#[test]
fn valid_handshake_with_correct_secret_accepted() {
    let secret = b"correct_horse_battery_staple_32b";
    // timestamp = 0 triggers is_boot_time path, accepted without wall-clock check.
    let handshake = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("alice".to_string(), secret.to_vec())];
    let result = validate_tls_handshake(&handshake, &secrets, true);
    assert!(result.is_some(), "Valid handshake must be accepted");
    assert_eq!(result.unwrap().user, "alice");
}

#[test]
fn deterministic_external_vector_validates_without_helper() {
    // Deterministic vector generated by an external Python stdlib HMAC script,
    // not by this test module helper. This catches mirrored helper mistakes.
    let secret = hex::decode("00112233445566778899aabbccddeeff").unwrap();
    let handshake = hex::decode(
        "4242424242424242424242a93225d1d6b46260bc9ce0cc48c7487d2b1ca5afa7ae9fc6609d9e60a3ca842b204242424242424242424242424242424242424242424242424242424242424242",
    )
    .unwrap();

    let secrets = vec![("vector_user".to_string(), secret)];
    let result = validate_tls_handshake(&handshake, &secrets, true).unwrap();

    assert_eq!(result.user, "vector_user");
    assert_eq!(result.timestamp, 0x01020304);
}

#[test]
fn valid_handshake_timestamp_extracted_correctly() {
    let secret = b"ts_extraction_test";
    let ts: u32 = 0xDEAD_BEEF;
    let handshake = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let result = validate_tls_handshake(&handshake, &secrets, true);
    assert!(result.is_some());
    assert_eq!(result.unwrap().timestamp, ts);
}

// ------------------------------------------------------------------
// HMAC bit-flip rejection - adversarial HMAC forgery attempts
// ------------------------------------------------------------------

/// Flip every single bit across the 28-byte HMAC check window one at a
/// time.  Each flip must cause rejection.  This is the primary guard
/// against a censor gradually narrowing down a valid HMAC via partial
/// matches (which would be exploitable with a variable-time comparison).
#[test]
fn hmac_single_bit_flip_anywhere_in_check_window_rejected() {
    let secret = b"flip_test_secret";
    let base = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    // First ensure the unmodified handshake is accepted.
    assert!(
        validate_tls_handshake(&base, &secrets, true).is_some(),
        "Baseline handshake must be accepted before flip tests"
    );

    for byte_pos in 0..28usize {
        for bit in 0u8..8 {
            let mut h = base.clone();
            h[TLS_DIGEST_POS + byte_pos] ^= 1 << bit;
            assert!(
                validate_tls_handshake(&h, &secrets, true).is_none(),
                "Flip of bit {bit} in HMAC byte {byte_pos} must be rejected"
            );
        }
    }
}

/// XOR entire check window (bytes 0-27) with 0xFF - must still fail.
#[test]
fn hmac_full_window_corruption_rejected() {
    let secret = b"full_window_test";
    let mut h = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];
    for i in 0..28 {
        h[TLS_DIGEST_POS + i] ^= 0xFF;
    }
    assert!(validate_tls_handshake(&h, &secrets, true).is_none());
}

/// Byte 27 is the last byte in the checked window.  A non-constant-time
/// `all(|b| b == 0)` that short-circuits on byte 0 would never even reach
/// byte 27, making this an effective "did the fix actually run to the end"
/// sentinel: if this passes but the earlier byte-0 test fails, the check
/// window is not being evaluated end-to-end.
#[test]
fn hmac_last_byte_of_check_window_enforced() {
    let secret = b"last_byte_sentinel";
    let mut h = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];
    // Corrupt only byte 27.
    h[TLS_DIGEST_POS + 27] ^= 0x01;
    assert!(
        validate_tls_handshake(&h, &secrets, true).is_none(),
        "Corruption at byte 27 (end of HMAC window) must cause rejection"
    );
}

// ------------------------------------------------------------------
// User enumeration / multi-user ordering
// ------------------------------------------------------------------

#[test]
fn wrong_user_secret_rejected_even_with_valid_structure() {
    let secret_a = b"secret_alpha";
    let secret_b = b"secret_beta";
    let handshake = make_valid_tls_handshake(secret_b, 0);
    // Only user_a is configured.
    let secrets = vec![("user_a".to_string(), secret_a.to_vec())];
    assert!(
        validate_tls_handshake(&handshake, &secrets, true).is_none(),
        "Handshake for user_b must fail when only user_a is configured"
    );
}

#[test]
fn second_user_in_list_found_when_first_does_not_match() {
    let secret_a = b"secret_alpha";
    let secret_b = b"secret_beta";
    let handshake = make_valid_tls_handshake(secret_b, 0);
    let secrets = vec![
        ("user_a".to_string(), secret_a.to_vec()),
        ("user_b".to_string(), secret_b.to_vec()),
    ];
    let result = validate_tls_handshake(&handshake, &secrets, true);
    assert!(
        result.is_some(),
        "user_b must be found even though user_a comes first"
    );
    assert_eq!(result.unwrap().user, "user_b");
}

#[test]
fn duplicate_secret_keeps_first_user_identity() {
    // If multiple entries share the same secret, the selected identity must
    // stay stable and deterministic (first entry wins).
    let shared = b"same_secret_for_two_users";
    let handshake = make_valid_tls_handshake(shared, 0);
    let secrets = vec![
        ("first_user".to_string(), shared.to_vec()),
        ("second_user".to_string(), shared.to_vec()),
    ];

    let result = validate_tls_handshake(&handshake, &secrets, true);
    assert!(result.is_some());
    assert_eq!(result.unwrap().user, "first_user");
}

#[test]
fn no_user_matches_returns_none() {
    let secret_a = b"aaa";
    let secret_b = b"bbb";
    let secret_c = b"ccc";
    let handshake = make_valid_tls_handshake(b"unknown_secret", 0);
    let secrets = vec![
        ("a".to_string(), secret_a.to_vec()),
        ("b".to_string(), secret_b.to_vec()),
        ("c".to_string(), secret_c.to_vec()),
    ];
    assert!(validate_tls_handshake(&handshake, &secrets, true).is_none());
}

#[test]
fn empty_secrets_list_rejects_everything() {
    let secret = b"test";
    let handshake = make_valid_tls_handshake(secret, 0);
    let secrets: Vec<(String, Vec<u8>)> = Vec::new();
    assert!(validate_tls_handshake(&handshake, &secrets, true).is_none());
}

// ------------------------------------------------------------------
// Timestamp / time-skew boundary attacks
// ------------------------------------------------------------------

#[test]
fn timestamp_at_time_skew_boundaries_accepted() {
    let secret = b"skew_boundary_test_secret";
    let now: i64 = 1_700_000_000;
    let secrets = vec![("u".to_string(), secret.to_vec())];

    // time_diff = now - ts = TIME_SKEW_MIN = -1200
    // -> ts = now - TIME_SKEW_MIN = now + 1200  (20 min in the future).
    let ts_at_future_limit = (now - TIME_SKEW_MIN) as u32;
    let h = make_valid_tls_handshake(secret, ts_at_future_limit);
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, now).is_some(),
        "Timestamp at max-allowed future (time_diff = TIME_SKEW_MIN) must be accepted"
    );

    // time_diff = now - ts = TIME_SKEW_MAX = 600
    // -> ts = now - TIME_SKEW_MAX = now - 600  (10 min in the past).
    let ts_at_past_limit = (now - TIME_SKEW_MAX) as u32;
    let h = make_valid_tls_handshake(secret, ts_at_past_limit);
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, now).is_some(),
        "Timestamp at max-allowed past (time_diff = TIME_SKEW_MAX) must be accepted"
    );
}

#[test]
fn timestamp_one_second_outside_skew_window_rejected() {
    let secret = b"skew_outside_test_secret";
    let now: i64 = 1_700_000_000;
    let secrets = vec![("u".to_string(), secret.to_vec())];

    // time_diff = TIME_SKEW_MAX + 1 = 601  (one second too far in the past)
    // -> ts = now - (TIME_SKEW_MAX + 1) = now - 601
    let ts_too_past = (now - TIME_SKEW_MAX - 1) as u32;
    let h = make_valid_tls_handshake(secret, ts_too_past);
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, now).is_none(),
        "Timestamp one second too far in the past must be rejected"
    );

    // time_diff = TIME_SKEW_MIN - 1 = -1201  (one second too far in the future)
    // -> ts = now - (TIME_SKEW_MIN - 1) = now + 1201
    let ts_too_future = (now - TIME_SKEW_MIN + 1) as u32;
    let h = make_valid_tls_handshake(secret, ts_too_future);
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, now).is_none(),
        "Timestamp one second too far in the future must be rejected"
    );
}

#[test]
fn ignore_time_skew_accepts_far_future_timestamp() {
    let secret = b"ignore_skew_test";
    let now: i64 = 1_700_000_000;
    let secrets = vec![("u".to_string(), secret.to_vec())];

    // 1 hour in the future - outside TIME_SKEW_MAX but should pass with flag.
    let future_ts = (now + 3600) as u32;
    let h = make_valid_tls_handshake(secret, future_ts);
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, true, now).is_some(),
        "ignore_time_skew=true must override window rejection"
    );
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, now).is_none(),
        "ignore_time_skew=false must still reject far-future timestamp"
    );
}

#[test]
fn boot_time_timestamp_accepted_without_ignore_flag() {
    // Timestamps below the boot-time threshold are treated as client uptime,
    // not real wall-clock time.  The proxy allows them regardless of skew.
    let secret = b"boot_time_test";
    // Keep this safely below compatibility cap to assert bypass behavior.
    let boot_ts: u32 = BOOT_TIME_COMPAT_MAX_SECS.saturating_sub(1);
    let handshake = make_valid_tls_handshake(secret, boot_ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];
    assert!(
        validate_tls_handshake(&handshake, &secrets, false).is_some(),
        "Boot-time timestamp must be accepted even with ignore_time_skew=false"
    );
}

// ------------------------------------------------------------------
// Structural / length boundary attacks
// ------------------------------------------------------------------

#[test]
fn too_short_handshake_rejected_without_panic() {
    let secrets = vec![("u".to_string(), b"s".to_vec())];
    // Exactly one byte short of the minimum required length.
    let h = vec![0u8; TLS_DIGEST_POS + TLS_DIGEST_LEN];
    assert!(validate_tls_handshake(&h, &secrets, true).is_none());

    // Empty buffer.
    assert!(validate_tls_handshake(&[], &secrets, true).is_none());
}

#[test]
fn all_prefix_lengths_below_minimum_rejected_without_panic() {
    let min_len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1;
    let secrets = vec![("u".to_string(), b"s".to_vec())];

    for len in 0..min_len {
        let h = vec![0u8; len];
        assert!(
            validate_tls_handshake(&h, &secrets, true).is_none(),
            "prefix length {len} below minimum must be rejected"
        );
    }
}

#[test]
fn claimed_session_id_overflows_buffer_rejected() {
    let session_id_len: usize = 32;
    let min_len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 + session_id_len;
    let mut h = vec![0u8; min_len];
    // Claim session_id is 33 bytes - one more than the buffer holds.
    h[TLS_DIGEST_POS + TLS_DIGEST_LEN] = (session_id_len + 1) as u8;
    let secrets = vec![("u".to_string(), b"s".to_vec())];
    assert!(validate_tls_handshake(&h, &secrets, true).is_none());
}

#[test]
fn max_session_id_len_255_does_not_panic() {
    // session_id_len = 255 with a buffer that is far too small for it.
    let min_len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 + 32;
    let mut h = vec![0u8; min_len];
    h[TLS_DIGEST_POS + TLS_DIGEST_LEN] = 255;
    let secrets = vec![("u".to_string(), b"s".to_vec())];
    assert!(validate_tls_handshake(&h, &secrets, true).is_none());
}

#[test]
fn one_byte_session_id_validates_and_is_preserved() {
    let secret = b"sid_len_1_test";
    let handshake = make_valid_tls_handshake_with_session_id(secret, 0, &[0xAB]);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let result = validate_tls_handshake(&handshake, &secrets, true)
        .expect("one-byte session_id handshake must validate");
    assert_eq!(result.session_id, vec![0xAB]);
}

#[test]
fn max_session_id_len_255_with_valid_digest_is_rejected_by_rfc_cap() {
    let secret = b"sid_len_255_test";
    let session_id = vec![0xCCu8; 255];
    let handshake = make_valid_tls_handshake_with_session_id(secret, 0, &session_id);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    assert!(
        validate_tls_handshake(&handshake, &secrets, true).is_none(),
        "legacy_session_id length > 32 must be rejected even with valid digest"
    );
}

// ------------------------------------------------------------------
// Adversarial digest values
// ------------------------------------------------------------------

#[test]
fn all_zeros_digest_rejected() {
    // An all-zeros digest would only pass if HMAC(secret, msg) happens to
    // have its first 28 bytes all zero, which is computationally infeasible.
    let session_id_len: usize = 32;
    let min_len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 + session_id_len;
    let mut h = vec![0x42u8; min_len];
    h[TLS_DIGEST_POS + TLS_DIGEST_LEN] = session_id_len as u8;
    h[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);
    let secrets = vec![("u".to_string(), b"test_secret".to_vec())];
    assert!(validate_tls_handshake(&h, &secrets, true).is_none());
}

#[test]
fn all_ones_digest_rejected() {
    let session_id_len: usize = 32;
    let min_len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 + session_id_len;
    let mut h = vec![0x42u8; min_len];
    h[TLS_DIGEST_POS + TLS_DIGEST_LEN] = session_id_len as u8;
    h[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0xFF);
    let secrets = vec![("u".to_string(), b"test_secret".to_vec())];
    assert!(validate_tls_handshake(&h, &secrets, true).is_none());
}

/// Simulate a censor that sends 200 crafted packets with random digests.
/// Every single one must be rejected; no random digest should accidentally
/// pass (probability 2^{-224} per attempt; negligible for 200 trials).
#[test]
fn censor_probe_random_digests_all_rejected() {
    use crate::crypto::SecureRandom;
    let secret = b"production_like_secret_value_xyz";
    let session_id_len: usize = 32;
    let min_len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 + session_id_len;
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let rng = SecureRandom::new();

    for attempt in 0..200 {
        let mut h = vec![0x42u8; min_len];
        h[TLS_DIGEST_POS + TLS_DIGEST_LEN] = session_id_len as u8;
        let rand_digest = rng.bytes(TLS_DIGEST_LEN);
        h[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].copy_from_slice(&rand_digest);
        assert!(
            validate_tls_handshake(&h, &secrets, true).is_none(),
            "Random digest at attempt {attempt} must not match"
        );
    }
}

/// The check window is bytes 0-27 of the XOR result.  Bytes 28-31 encode
/// the timestamp and must NOT affect whether the HMAC portion validates -
/// only the timestamp range check uses them.  Build a valid handshake with
/// timestamp = 0 (boot-time), flip each of bytes 28-31 with ignore_time_skew
/// enabled, and verify the HMAC portion still passes (the timestamp changes
/// but the proxy still accepts the connection under ignore_time_skew).
#[test]
fn timestamp_bytes_28_31_do_not_affect_hmac_window() {
    let secret = b"window_boundary_test";
    let base = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    // Baseline must pass.
    assert!(validate_tls_handshake(&base, &secrets, true).is_some());

    // Flip each of the timestamp bytes; with ignore_time_skew the
    // modified timestamps (small absolute values) still pass boot-time check.
    for i in 28..32usize {
        let mut h = base.clone();
        h[TLS_DIGEST_POS + i] ^= 0xFF;
        // The new timestamp is non-zero but potentially still < boot threshold;
        // use ignore_time_skew=true so wallet test is HMAC-only.
        assert!(
            validate_tls_handshake(&h, &secrets, true).is_some(),
            "Flipping byte {i} (timestamp region) must not invalidate HMAC window"
        );
    }
}

// ------------------------------------------------------------------
// session_id preservation
// ------------------------------------------------------------------

#[test]
fn session_id_is_preserved_verbatim_in_validation_result() {
    // If session_id extraction is ever broken (wrong offset, wrong length,
    // off-by-one), this test will catch it before it silently corrupts the
    // ServerHello that echoes the session_id back to the client.
    let secret = b"session_id_preservation_test";
    let handshake = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let result = validate_tls_handshake(&handshake, &secrets, true).unwrap();

    let sid_len_pos = TLS_DIGEST_POS + TLS_DIGEST_LEN;
    let sid_len = handshake[sid_len_pos] as usize;
    let expected = &handshake[sid_len_pos + 1..sid_len_pos + 1 + sid_len];

    assert_eq!(
        result.session_id, expected,
        "session_id in TlsValidation must be the verbatim bytes from the handshake"
    );
}

// ------------------------------------------------------------------
// Clock decoupling - ignore_time_skew must not consult the system clock
// ------------------------------------------------------------------

/// When `ignore_time_skew = true`, a valid HMAC must be accepted even if
/// `now = 0` (the sentinel used when the clock is not needed).  A broken
/// system clock cannot silently deny service when the admin has explicitly
/// disabled timestamp checking.
#[test]
fn ignore_time_skew_accepts_valid_hmac_with_now_zero() {
    let secret = b"clock_decoupling_test";
    // Use a realistic Unix timestamp that would be far outside the window
    // if compared against now=0 (time_diff would be ~-1_700_000_000).
    let realistic_ts: u32 = 1_700_000_000;
    let h = make_valid_tls_handshake(secret, realistic_ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    assert!(
        validate_tls_handshake_at_time(&h, &secrets, true, 0).is_some(),
        "ignore_time_skew=true must accept a valid HMAC regardless of `now`"
    );

    // Confirm that the same handshake IS rejected when the window is enforced
    // and now=0 (time_diff very negative -> outside window).  This distinguishes
    // "clock decoupling" from "always accept".
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, 0).is_none(),
        "ignore_time_skew=false with now=0 must still reject out-of-window timestamps"
    );
}

/// An HMAC-invalid handshake must be rejected even when ignore_time_skew=true
/// and now=0.  Verifies that the clock-decoupling fix did not weaken HMAC
/// enforcement in the ignore_time_skew path.
#[test]
fn ignore_time_skew_with_now_zero_still_rejects_bad_hmac() {
    let secret = b"clock_no_backdoor_test";
    let mut h = make_valid_tls_handshake(secret, 1_700_000_000);
    let secrets = vec![("u".to_string(), secret.to_vec())];
    // Corrupt the HMAC check window.
    h[TLS_DIGEST_POS] ^= 0xFF;
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, true, 0).is_none(),
        "Broken HMAC must be rejected even with ignore_time_skew=true and now=0"
    );
}

#[test]
fn system_time_before_unix_epoch_is_rejected_without_panic() {
    let before_epoch = UNIX_EPOCH
        .checked_sub(std::time::Duration::from_secs(1))
        .expect("UNIX_EPOCH minus one second must be representable");
    assert!(system_time_to_unix_secs(before_epoch).is_none());
}

/// `i64::MAX` is 9_223_372_036_854_775_807 seconds (~292 billion years CE).
/// Any `SystemTime` whose duration since epoch exceeds `i64::MAX` seconds
/// must return `None` rather than silently wrapping to a large negative
/// timestamp that would corrupt every subsequent time-skew comparison.
#[test]
fn system_time_far_future_overflowing_i64_returns_none() {
    // i64::MAX + 1 seconds past epoch overflows i64 when cast naively with `as`.
    let overflow_secs = u64::try_from(i64::MAX).unwrap() + 1;
    if let Some(far_future) = UNIX_EPOCH.checked_add(std::time::Duration::from_secs(overflow_secs))
    {
        assert!(
            system_time_to_unix_secs(far_future).is_none(),
            "Seconds > i64::MAX must return None, not a wrapped negative timestamp"
        );
    }
    // If the platform cannot represent this SystemTime, the test is vacuously
    // satisfied: `checked_add` returning None means the platform already rejects it.
}

// ------------------------------------------------------------------
// Message canonicalization — HMAC covers every byte of the handshake
// ------------------------------------------------------------------

/// Every byte before TLS_DIGEST_POS is part of the HMAC input (because msg
/// = full handshake with only the digest slot zeroed).  An attacker cannot
/// replay a valid handshake with a modified ClientHello header while keeping
/// the stored digest; each such modification produces a different HMAC.
#[test]
fn pre_digest_bytes_are_hmac_covered() {
    // TLS_DIGEST_POS = 11, so 11 bytes precede the digest.
    let secret = b"pre_digest_coverage_test";
    let base = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    for byte_pos in 0..TLS_DIGEST_POS {
        let mut h = base.clone();
        h[byte_pos] ^= 0x01;
        assert!(
            validate_tls_handshake(&h, &secrets, true).is_none(),
            "Flip in pre-digest byte {byte_pos} must cause HMAC check failure"
        );
    }
}

/// session_id bytes follow the digest in the buffer and are also part of the
/// HMAC input.  Flipping any of them invalidates the stored digest, preventing
/// a censor from capturing a valid session_id and replaying it with a different
/// one while keeping the rest of the packet intact.
#[test]
fn session_id_bytes_are_hmac_covered() {
    let secret = b"session_id_coverage_test";
    let base = make_valid_tls_handshake(secret, 0); // session_id_len = 32
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let sid_start = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1;
    for byte_pos in sid_start..base.len() {
        let mut h = base.clone();
        h[byte_pos] ^= 0x01;
        assert!(
            validate_tls_handshake(&h, &secrets, true).is_none(),
            "Flip in session_id byte at offset {byte_pos} must cause HMAC check failure"
        );
    }
}

/// Appending even one byte to a valid handshake changes the HMAC input (msg
/// includes all bytes) and therefore invalidates the stored digest.  This
/// prevents a length-extension-style modification of the payload.
#[test]
fn appended_trailing_byte_causes_rejection() {
    let secret = b"trailing_byte_test";
    let mut h = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    assert!(
        validate_tls_handshake(&h, &secrets, true).is_some(),
        "baseline"
    );

    h.push(0x00);
    assert!(
        validate_tls_handshake(&h, &secrets, true).is_none(),
        "Appending a trailing byte to a valid handshake must invalidate the HMAC"
    );
}

// ------------------------------------------------------------------
// Zero-length session_id (structural edge case)
// ------------------------------------------------------------------

/// session_id_len = 0 is legal in the TLS spec.  The validator must accept a
/// valid handshake with an empty session_id and return an empty session_id
/// slice without panicking or accessing out-of-bounds memory.
#[test]
fn zero_length_session_id_accepted() {
    let secret = b"zero_sid_test";
    // Buffer: pre-digest | digest | session_id_len=0  (no session_id bytes follow)
    let len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1;
    let mut handshake = vec![0x42u8; len];
    handshake[TLS_DIGEST_POS + TLS_DIGEST_LEN] = 0; // session_id_len = 0
    handshake[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);

    let computed = sha256_hmac(secret, &handshake);
    // timestamp = 0 → ts XOR bytes are all zero → digest = computed unchanged.
    handshake[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].copy_from_slice(&computed);

    let secrets = vec![("u".to_string(), secret.to_vec())];
    let result = validate_tls_handshake(&handshake, &secrets, true);
    assert!(result.is_some(), "zero-length session_id must be accepted");
    assert!(
        result.unwrap().session_id.is_empty(),
        "session_id field must be empty when session_id_len = 0"
    );
}

// ------------------------------------------------------------------
// Boot-time threshold — exact boundary precision
// ------------------------------------------------------------------

/// timestamp = BOOT_TIME_COMPAT_MAX_SECS - 1 is the last value inside
/// the runtime boot-time compatibility window.
/// is_boot_time = true → skew check is skipped entirely → accepted even
/// when `now` is far from the timestamp.
#[test]
fn timestamp_one_below_boot_threshold_bypasses_skew_check() {
    let secret = b"boot_last_value_test";
    let ts: u32 = BOOT_TIME_COMPAT_MAX_SECS - 1;
    let h = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    // now = 0 → time_diff would be -86_399_999, way outside [-1200, 600].
    // Boot-time bypass must prevent the skew check from running.
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, 0).is_some(),
        "ts=BOOT_TIME_COMPAT_MAX_SECS-1 must bypass skew check regardless of now"
    );
}

/// timestamp = BOOT_TIME_COMPAT_MAX_SECS is the first value outside the
/// runtime boot-time compatibility window.
/// is_boot_time = false → skew check IS applied.  Two sub-cases confirm this:
/// once with now chosen so the skew passes (accepted) and once where it fails.
#[test]
fn timestamp_at_boot_threshold_triggers_skew_check() {
    let secret = b"boot_exact_value_test";
    let ts: u32 = BOOT_TIME_COMPAT_MAX_SECS;
    let h = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    // now = ts + 50 → time_diff = 50, within [-1200, 600] → accepted.
    let now_valid: i64 = ts as i64 + 50;
    assert!(
        validate_tls_handshake_at_time_with_boot_cap(
            &h,
            &secrets,
            false,
            now_valid,
            BOOT_TIME_COMPAT_MAX_SECS,
        )
        .is_some(),
        "ts=BOOT_TIME_COMPAT_MAX_SECS within skew window must be accepted via skew check"
    );

    // now = -1 → time_diff = -121 at the 120-second threshold, outside window
    // for TIME_SKEW_MIN=-120. If boot-time bypass were wrongly applied this
    // would pass.
    assert!(
        validate_tls_handshake_at_time_with_boot_cap(
            &h,
            &secrets,
            false,
            -1,
            BOOT_TIME_COMPAT_MAX_SECS,
        )
        .is_none(),
        "ts=BOOT_TIME_COMPAT_MAX_SECS far from now must be rejected — no boot-time bypass"
    );
}

#[test]
fn replay_window_cap_disables_boot_bypass_for_old_timestamps() {
    let secret = b"boot_cap_disabled_test";
    let ts: u32 = 900;
    let h = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let result = validate_tls_handshake_with_replay_window(&h, &secrets, false, 300);
    assert!(
        result.is_none(),
        "timestamp above replay-window cap must not use boot-time bypass"
    );
}

#[test]
fn replay_window_cap_still_allows_small_boot_timestamp() {
    let secret = b"boot_cap_enabled_test";
    let ts: u32 = BOOT_TIME_COMPAT_MAX_SECS.saturating_sub(1);
    let h = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let result = validate_tls_handshake_with_replay_window(&h, &secrets, false, 300);
    assert!(
        result.is_some(),
        "timestamp below replay-window cap must retain boot-time compatibility"
    );
}

#[test]
fn large_replay_window_is_hard_capped_for_boot_compatibility() {
    let secret = b"boot_cap_hard_limit_test";
    let ts: u32 = BOOT_TIME_COMPAT_MAX_SECS + 1;
    let h = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let result = validate_tls_handshake_with_replay_window(&h, &secrets, false, u64::MAX);
    assert!(
        result.is_none(),
        "very large replay window must not expand boot-time bypass beyond hard compatibility cap"
    );
}

#[test]
fn ignore_time_skew_explicitly_decouples_from_boot_time_cap() {
    let secret = b"ignore_skew_boot_cap_decouple_test";
    let ts: u32 = 1;
    let h = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let cap_zero = validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, true, 0, 0);
    let cap_nonzero = validate_tls_handshake_at_time_with_boot_cap(
        &h,
        &secrets,
        true,
        0,
        BOOT_TIME_COMPAT_MAX_SECS,
    );

    assert!(
        cap_zero.is_some(),
        "ignore_time_skew=true must accept valid HMAC"
    );
    assert!(
        cap_nonzero.is_some(),
        "ignore_time_skew path must not depend on boot-time cap"
    );

    let a = cap_zero.unwrap();
    let b = cap_nonzero.unwrap();
    assert_eq!(a.user, b.user);
    assert_eq!(a.timestamp, b.timestamp);
}

#[test]
fn adversarial_small_boot_timestamp_matrix_rejected_when_boot_cap_forced_zero() {
    let secret = b"boot_cap_zero_matrix_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let now: i64 = 1_700_000_000;

    for ts in 0u32..1024u32 {
        let h = make_valid_tls_handshake(secret, ts);
        let result = validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, false, now, 0);
        assert!(
            result.is_none(),
            "boot cap=0 must reject timestamp {ts} when skew checks are active"
        );
    }
}

#[test]
fn light_fuzz_boot_cap_zero_rejects_small_timestamp_space() {
    let secret = b"boot_cap_zero_fuzz_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let now: i64 = 1_700_000_000;
    let mut s: u64 = 0x9E37_79B9_7F4A_7C15;

    for _ in 0..4096 {
        s ^= s << 7;
        s ^= s >> 9;
        s ^= s << 8;
        let ts = (s as u32) % 2048;

        let h = make_valid_tls_handshake(secret, ts);
        let result = validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, false, now, 0);
        assert!(
            result.is_none(),
            "fuzzed boot-range timestamp {ts} must be rejected when cap=0"
        );
    }
}

#[test]
fn stress_boot_cap_zero_rejection_is_deterministic_under_high_iteration_count() {
    let secret = b"boot_cap_zero_stress_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let now: i64 = 1_700_000_000;

    for i in 0u32..20_000u32 {
        let ts = i % 4096;
        let h = make_valid_tls_handshake(secret, ts);
        let result = validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, false, now, 0);
        assert!(
            result.is_none(),
            "iteration {i}: timestamp {ts} must be rejected with cap=0"
        );
    }
}

#[test]
fn replay_window_one_allows_only_zero_timestamp_boot_bypass() {
    let secret = b"replay_window_one_boot_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let ts0 = make_valid_tls_handshake(secret, 0);
    let ts1 = make_valid_tls_handshake(secret, 1);

    assert!(
        validate_tls_handshake_with_replay_window(&ts0, &secrets, false, 1).is_some(),
        "replay_window=1 must allow timestamp 0 via boot-time compatibility"
    );
    assert!(
        validate_tls_handshake_with_replay_window(&ts1, &secrets, false, 1).is_none(),
        "replay_window=1 must reject timestamp 1 on normal wall-clock systems"
    );
}

#[test]
fn replay_window_two_allows_ts0_ts1_but_rejects_ts2() {
    let secret = b"replay_window_two_boot_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let ts0 = make_valid_tls_handshake(secret, 0);
    let ts1 = make_valid_tls_handshake(secret, 1);
    let ts2 = make_valid_tls_handshake(secret, 2);

    assert!(validate_tls_handshake_with_replay_window(&ts0, &secrets, false, 2).is_some());
    assert!(validate_tls_handshake_with_replay_window(&ts1, &secrets, false, 2).is_some());
    assert!(
        validate_tls_handshake_with_replay_window(&ts2, &secrets, false, 2).is_none(),
        "timestamp equal to replay-window cap must not use boot-time bypass"
    );
}

#[test]
fn adversarial_skew_boundary_matrix_accepts_only_inclusive_window_when_boot_disabled() {
    let secret = b"skew_boundary_matrix_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let now: i64 = 1_700_000_000;

    for offset in -1500i64..=1500i64 {
        let ts_i64 = now - offset;
        let ts = u32::try_from(ts_i64).expect("timestamp must fit u32 for test matrix");
        let h = make_valid_tls_handshake(secret, ts);
        let accepted =
            validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, false, now, 0).is_some();
        let expected = (TIME_SKEW_MIN..=TIME_SKEW_MAX).contains(&offset);
        assert_eq!(
            accepted, expected,
            "offset {offset} must match inclusive skew window when boot bypass is disabled"
        );
    }
}

#[test]
fn light_fuzz_skew_window_rejects_outside_range_when_boot_disabled() {
    let secret = b"skew_outside_fuzz_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let now: i64 = 1_700_000_000;
    let mut s: u64 = 0x0123_4567_89AB_CDEF;

    for _ in 0..4096 {
        s ^= s << 7;
        s ^= s >> 9;
        s ^= s << 8;

        let magnitude = 1300i64 + ((s % 2000u64) as i64);
        let sign = if (s & 1) == 0 { 1i64 } else { -1i64 };
        let offset = sign * magnitude;
        let ts_i64 = now - offset;
        let ts = u32::try_from(ts_i64).expect("timestamp must fit u32 for fuzz test");

        let h = make_valid_tls_handshake(secret, ts);
        let accepted =
            validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, false, now, 0).is_some();
        assert!(
            !accepted,
            "offset {offset} must be rejected outside strict skew window"
        );
    }
}

#[test]
fn stress_boot_disabled_validation_matches_time_diff_oracle() {
    let secret = b"boot_disabled_oracle_stress_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let now: i64 = 1_700_000_000;
    let mut s: u64 = 0xBADC_0FFE_EE11_2233;

    for _ in 0..25_000 {
        s ^= s << 7;
        s ^= s >> 9;
        s ^= s << 8;
        let ts = s as u32;
        let h = make_valid_tls_handshake(secret, ts);

        let accepted =
            validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, false, now, 0).is_some();
        let time_diff = now - i64::from(ts);
        let expected = (TIME_SKEW_MIN..=TIME_SKEW_MAX).contains(&time_diff);
        assert_eq!(
            accepted, expected,
            "boot-disabled validation must match pure time-diff oracle"
        );
    }
}

#[test]
fn integration_large_user_list_with_boot_disabled_finds_only_matching_user() {
    let now: i64 = 1_700_000_000;
    let target_secret = b"target_user_secret";
    let target_ts = (now - 1) as u32;
    let handshake = make_valid_tls_handshake(target_secret, target_ts);

    let mut secrets = Vec::new();
    for i in 0..512u32 {
        secrets.push((
            format!("noise-{i}"),
            format!("noise-secret-{i}").into_bytes(),
        ));
    }
    secrets.push(("target-user".to_string(), target_secret.to_vec()));

    let result = validate_tls_handshake_at_time_with_boot_cap(&handshake, &secrets, false, now, 0)
        .expect("matching user should validate within strict skew window");
    assert_eq!(result.user, "target-user");
}

#[test]
fn light_fuzz_ignore_time_skew_accepts_wide_timestamp_range_with_valid_hmac() {
    let secret = b"ignore_skew_fuzz_accept_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let mut s: u64 = 0xC0FF_EE11_2233_4455;

    for _ in 0..2048 {
        s ^= s << 7;
        s ^= s >> 9;
        s ^= s << 8;
        let ts = s as u32;

        let h = make_valid_tls_handshake(secret, ts);
        let result = validate_tls_handshake_with_replay_window(&h, &secrets, true, 60);
        assert!(
            result.is_some(),
            "ignore_time_skew=true must accept valid HMAC for arbitrary timestamp"
        );
    }
}

#[test]
fn light_fuzz_small_replay_window_rejects_far_timestamps_when_skew_enabled() {
    let secret = b"replay_window_reject_fuzz_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];

    for ts in 300u32..=1323u32 {
        let h = make_valid_tls_handshake(secret, ts);
        let result = validate_tls_handshake_at_time_with_boot_cap(&h, &secrets, false, 0, 300);
        assert!(
            result.is_none(),
            "with skew checks enabled and boot cap=300, timestamp >=300 at now=0 must be rejected"
        );
    }
}

// ------------------------------------------------------------------
// Extreme timestamp values
// ------------------------------------------------------------------

/// u32::MAX is a valid timestamp value.  When ignore_time_skew=true the HMAC
/// is the only gate, and a correctly constructed handshake must be accepted.
#[test]
fn u32_max_timestamp_accepted_with_ignore_time_skew() {
    let secret = b"u32_max_ts_accept_test";
    let h = make_valid_tls_handshake(secret, u32::MAX);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let result = validate_tls_handshake(&h, &secrets, true);
    assert!(
        result.is_some(),
        "u32::MAX timestamp must be accepted with ignore_time_skew=true"
    );
    assert_eq!(
        result.unwrap().timestamp,
        u32::MAX,
        "timestamp field must equal u32::MAX verbatim"
    );
}

/// u32::MAX > BOOT_TIME_MAX_SECS so the skew check runs.  With any realistic `now`
/// (~1.7 billion), time_diff = now - u32::MAX is deeply negative — far outside
/// [-1200, 600] — so the handshake must be rejected without overflow.
#[test]
fn u32_max_timestamp_rejected_by_skew_enforcement() {
    let secret = b"u32_max_ts_reject_test";
    let h = make_valid_tls_handshake(secret, u32::MAX);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let now: i64 = 1_700_000_000;
    assert!(
        validate_tls_handshake_at_time(&h, &secrets, false, now).is_none(),
        "u32::MAX timestamp must be rejected by skew check with realistic now"
    );
}

// ------------------------------------------------------------------
// Validation result field correctness
// ------------------------------------------------------------------

/// result.digest must be the verbatim bytes stored in the handshake buffer,
/// not the freshly recomputed HMAC.  Callers use this field directly when
/// constructing the ServerHello response digest.
#[test]
fn result_digest_field_is_verbatim_stored_digest() {
    let secret = b"digest_field_verbatim_test";
    let ts: u32 = 0xCAFE_BABE;
    let h = make_valid_tls_handshake(secret, ts);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let result = validate_tls_handshake(&h, &secrets, true).unwrap();

    let stored: [u8; TLS_DIGEST_LEN] = h[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN]
        .try_into()
        .unwrap();
    assert_eq!(
        result.digest, stored,
        "result.digest must equal the stored bytes, not the computed HMAC"
    );
}

// ------------------------------------------------------------------
// Secret length edge cases
// ------------------------------------------------------------------

/// HMAC-SHA256 pads or hashes keys of any length; a single-byte key must work.
#[test]
fn single_byte_secret_works() {
    let secret = b"x";
    let h = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("u".to_string(), secret.to_vec())];
    assert!(
        validate_tls_handshake(&h, &secrets, true).is_some(),
        "single-byte secret must produce a valid and verifiable HMAC"
    );
}

/// Keys longer than the HMAC block size (64 bytes for SHA-256) are hashed
/// before use.  A 256-byte key must work without truncation or panic.
#[test]
fn very_long_secret_256_bytes_works() {
    let secret = vec![0xABu8; 256];
    let h = make_valid_tls_handshake(&secret, 0);
    let secrets = vec![("u".to_string(), secret.clone())];
    assert!(
        validate_tls_handshake(&h, &secrets, true).is_some(),
        "256-byte secret must be accepted without truncation"
    );
}

// ------------------------------------------------------------------
// Determinism — same input must always produce same result
// ------------------------------------------------------------------

/// Calling validate twice on the same input must return identical results.
/// Non-determinism (e.g. from an accidentally global mutable state or a
/// shared nonce) would be a critical security defect in a proxy that rejects
/// censors by relying on stable authentication outcomes.
#[test]
fn validation_is_deterministic() {
    let secret = b"determinism_test_key";
    let h = make_valid_tls_handshake(secret, 42);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let r1 = validate_tls_handshake(&h, &secrets, true).unwrap();
    let r2 = validate_tls_handshake(&h, &secrets, true).unwrap();

    assert_eq!(r1.user, r2.user);
    assert_eq!(r1.session_id, r2.session_id);
    assert_eq!(r1.digest, r2.digest);
    assert_eq!(r1.timestamp, r2.timestamp);
}

// ------------------------------------------------------------------
// Multi-user: scan-all correctness guarantees
// ------------------------------------------------------------------

/// The matching logic must scan through the entire secrets list.  A user
/// at position 99 of 100 must be found; an implementation that stops early
/// on the first non-match would fail this test.
#[test]
fn last_user_in_large_list_is_found() {
    let target_secret = b"needle_in_haystack";
    let h = make_valid_tls_handshake(target_secret, 0);

    let mut secrets: Vec<(String, Vec<u8>)> = (0..99)
        .map(|i| (format!("decoy_{i}"), format!("wrong_{i}").into_bytes()))
        .collect();
    secrets.push(("needle".to_string(), target_secret.to_vec()));

    let result = validate_tls_handshake(&h, &secrets, true);
    assert!(result.is_some(), "100th user must be found");
    assert_eq!(result.unwrap().user, "needle");
}

/// When multiple users share the same secret the first occurrence must always
/// win.  The scan-all loop must not replace first_match with a later one.
#[test]
fn first_matching_user_wins_over_later_duplicate_secret() {
    let shared = b"duplicated_secret_key";
    let h = make_valid_tls_handshake(shared, 0);

    let secrets = vec![
        ("decoy_1".to_string(), b"wrong_1".to_vec()),
        ("winner".to_string(), shared.to_vec()), // first match
        ("decoy_2".to_string(), b"wrong_2".to_vec()),
        ("loser".to_string(), shared.to_vec()), // second match — must not win
        ("decoy_3".to_string(), b"wrong_3".to_vec()),
    ];

    let result = validate_tls_handshake(&h, &secrets, true);
    assert!(result.is_some());
    assert_eq!(
        result.unwrap().user,
        "winner",
        "first matching user must be returned even when a later entry also matches"
    );
}

// ------------------------------------------------------------------
// Legacy tls.rs tests moved here
// ------------------------------------------------------------------

#[test]
fn test_is_tls_handshake() {
    assert!(is_tls_handshake(&[0x16, 0x03, 0x01]));
    assert!(is_tls_handshake(&[0x16, 0x03, 0x03]));
    assert!(is_tls_handshake(&[0x16, 0x03, 0x01, 0x02, 0x00]));
    assert!(is_tls_handshake(&[0x16, 0x03, 0x03, 0x02, 0x00]));
    assert!(!is_tls_handshake(&[0x17, 0x03, 0x01]));
    assert!(!is_tls_handshake(&[0x16, 0x03, 0x02]));
    assert!(!is_tls_handshake(&[0x16, 0x03]));
}

#[test]
fn test_parse_tls_record_header() {
    let header = [0x16, 0x03, 0x01, 0x02, 0x00];
    let result = parse_tls_record_header(&header).unwrap();
    assert_eq!(result.0, TLS_RECORD_HANDSHAKE);
    assert_eq!(result.1, 512);

    let header = [0x17, 0x03, 0x03, 0x40, 0x00];
    let result = parse_tls_record_header(&header).unwrap();
    assert_eq!(result.0, TLS_RECORD_APPLICATION);
    assert_eq!(usize::from(result.1), MAX_TLS_PLAINTEXT_SIZE);
}

#[test]
fn parse_tls_record_header_rejects_invalid_versions() {
    let invalid = [
        [0x16, 0x03, 0x00, 0x00, 0x10],
        [0x16, 0x02, 0x00, 0x00, 0x10],
        [0x16, 0x03, 0x02, 0x00, 0x10],
        [0x16, 0x04, 0x00, 0x00, 0x10],
    ];
    for header in invalid {
        assert!(
            parse_tls_record_header(&header).is_none(),
            "invalid TLS record version {:?} must be rejected",
            [header[1], header[2]]
        );
    }
}

#[test]
fn test_gen_fake_x25519_key() {
    let rng = crate::crypto::SecureRandom::new();
    let key1 = gen_fake_x25519_key(&rng);
    let key2 = gen_fake_x25519_key(&rng);

    assert_eq!(key1.len(), 32);
    assert_eq!(key2.len(), 32);
    assert_ne!(key1, key2);
}

#[test]
fn test_fake_x25519_key_is_nonzero_and_varies() {
    let rng = crate::crypto::SecureRandom::new();
    let mut unique = std::collections::HashSet::new();
    let mut saw_non_zero = false;

    for _ in 0..64 {
        let key = gen_fake_x25519_key(&rng);
        if key != [0u8; 32] {
            saw_non_zero = true;
        }
        unique.insert(key);
    }

    assert!(
        saw_non_zero,
        "generated X25519 public keys must not collapse to all-zero output"
    );
    assert!(
        unique.len() > 1,
        "generated X25519 public keys must vary across invocations"
    );
}

#[test]
fn validate_tls_handshake_rejects_session_id_longer_than_rfc_cap() {
    let secret = b"session_id_cap_secret";
    let oversized_sid = vec![0x42u8; 33];
    let handshake = make_valid_tls_handshake_with_session_id(secret, 0, &oversized_sid);
    let secrets = vec![("u".to_string(), secret.to_vec())];

    assert!(
        validate_tls_handshake(&handshake, &secrets, true).is_none(),
        "legacy_session_id length > 32 must be rejected"
    );
}

fn server_hello_extension_types(record: &[u8]) -> Vec<u16> {
    if record.len() < 9 || record[0] != TLS_RECORD_HANDSHAKE || record[5] != 0x02 {
        return Vec::new();
    }

    let record_len = u16::from_be_bytes([record[3], record[4]]) as usize;
    if record.len() < 5 + record_len {
        return Vec::new();
    }

    let hs_len = u32::from_be_bytes([0, record[6], record[7], record[8]]) as usize;
    let hs_start = 5;
    let hs_end = hs_start + 4 + hs_len;
    if hs_end > record.len() {
        return Vec::new();
    }

    let mut pos = hs_start + 4 + 2 + 32;
    if pos >= hs_end {
        return Vec::new();
    }
    let sid_len = record[pos] as usize;
    pos += 1 + sid_len;
    if pos + 2 + 1 + 2 > hs_end {
        return Vec::new();
    }

    pos += 2 + 1;
    let ext_len = u16::from_be_bytes([record[pos], record[pos + 1]]) as usize;
    pos += 2;
    let ext_end = pos + ext_len;
    if ext_end > hs_end {
        return Vec::new();
    }

    let mut out = Vec::new();
    while pos + 4 <= ext_end {
        let etype = u16::from_be_bytes([record[pos], record[pos + 1]]);
        let elen = u16::from_be_bytes([record[pos + 2], record[pos + 3]]) as usize;
        pos += 4;
        if pos + elen > ext_end {
            break;
        }
        out.push(etype);
        pos += elen;
    }
    out
}

#[test]
fn build_server_hello_never_places_alpn_in_server_hello_extensions() {
    let secret = b"alpn_sh_forbidden";
    let client_digest = [0x11u8; 32];
    let session_id = vec![0xAA; 32];
    let rng = crate::crypto::SecureRandom::new();

    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        1024,
        &rng,
        Some(b"h2".to_vec()),
        0,
    );
    let exts = server_hello_extension_types(&response);
    assert!(
        !exts.contains(&0x0010),
        "ALPN extension must not appear in ServerHello"
    );
}

#[test]
fn emulated_server_hello_never_places_alpn_in_server_hello_extensions() {
    let secret = b"alpn_emulated_forbidden";
    let client_digest = [0x22u8; 32];
    let session_id = vec![0xAB; 32];
    let rng = crate::crypto::SecureRandom::new();
    let cached = CachedTlsData {
        server_hello_template: ParsedServerHello {
            version: TLS_VERSION,
            random: [0u8; 32],
            session_id: Vec::new(),
            cipher_suite: [0x13, 0x01],
            compression: 0,
            extensions: Vec::new(),
        },
        cert_info: None,
        cert_payload: None,
        app_data_records_sizes: vec![1024],
        total_app_data_len: 1024,
        behavior_profile: TlsBehaviorProfile {
            change_cipher_spec_count: 1,
            app_data_record_sizes: vec![1024],
            ticket_record_sizes: Vec::new(),
            source: TlsProfileSource::Default,
        },
        fetched_at: SystemTime::now(),
        domain: "example.com".to_string(),
    };

    let response = build_emulated_server_hello(
        secret,
        &client_digest,
        &session_id,
        &cached,
        false,
        &rng,
        Some(b"h2".to_vec()),
        0,
    );
    let exts = server_hello_extension_types(&response);
    assert!(
        !exts.contains(&0x0010),
        "ALPN extension must not appear in emulated ServerHello"
    );
}

#[test]
fn test_tls_extension_builder() {
    let key = [0x42u8; 32];

    let mut builder = TlsExtensionBuilder::new();
    builder.add_key_share(&key);
    builder.add_supported_versions(0x0304);

    let result = builder.build();
    let len = u16::from_be_bytes([result[0], result[1]]) as usize;

    assert_eq!(len, result.len() - 2);
    assert!(result.len() > 40);
}

#[test]
fn test_server_hello_builder() {
    let session_id = vec![0x01, 0x02, 0x03, 0x04];
    let key = [0x55u8; 32];

    let builder = ServerHelloBuilder::new(session_id.clone())
        .with_x25519_key(&key)
        .with_tls13_version();

    let record = builder.build_record();
    validate_server_hello_structure(&record).expect("Invalid ServerHello structure");

    assert_eq!(record[0], TLS_RECORD_HANDSHAKE);
    assert_eq!(&record[1..3], &TLS_VERSION);
    assert_eq!(record[5], 0x02);
}

#[test]
fn test_build_server_hello_structure() {
    let secret = b"test secret";
    let client_digest = [0x42u8; 32];
    let session_id = vec![0xAA; 32];

    let rng = crate::crypto::SecureRandom::new();
    let response = build_server_hello(secret, &client_digest, &session_id, 2048, &rng, None, 0);

    assert!(response.len() > 100);
    assert_eq!(response[0], TLS_RECORD_HANDSHAKE);
    validate_server_hello_structure(&response).expect("Invalid ServerHello");

    let server_hello_len = 5 + u16::from_be_bytes([response[3], response[4]]) as usize;
    let ccs_start = server_hello_len;
    assert!(response.len() > ccs_start + 6);
    assert_eq!(response[ccs_start], TLS_RECORD_CHANGE_CIPHER);

    let ccs_len =
        5 + u16::from_be_bytes([response[ccs_start + 3], response[ccs_start + 4]]) as usize;
    let app_start = ccs_start + ccs_len;
    assert!(response.len() > app_start + 5);
    assert_eq!(response[app_start], TLS_RECORD_APPLICATION);
}

#[test]
fn test_build_server_hello_digest() {
    let secret = b"test secret key here";
    let client_digest = [0x42u8; 32];
    let session_id = vec![0xAA; 32];

    let rng = crate::crypto::SecureRandom::new();
    let response1 = build_server_hello(secret, &client_digest, &session_id, 1024, &rng, None, 0);
    let response2 = build_server_hello(secret, &client_digest, &session_id, 1024, &rng, None, 0);

    let digest1 = &response1[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN];
    assert!(!digest1.iter().all(|&b| b == 0));

    let digest2 = &response2[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN];
    assert_ne!(digest1, digest2);
}

#[test]
fn test_server_hello_extensions_length() {
    let session_id = vec![0x01; 32];
    let key = [0x55u8; 32];

    let builder = ServerHelloBuilder::new(session_id)
        .with_x25519_key(&key)
        .with_tls13_version();

    let record = builder.build_record();
    let msg_start = 5;
    let msg_len = u32::from_be_bytes([0, record[6], record[7], record[8]]) as usize;
    let session_id_pos = msg_start + 4 + 2 + 32;
    let session_id_len = record[session_id_pos] as usize;
    let ext_len_pos = session_id_pos + 1 + session_id_len + 2 + 1;
    let ext_len = u16::from_be_bytes([record[ext_len_pos], record[ext_len_pos + 1]]) as usize;
    let extensions_data = &record[ext_len_pos + 2..msg_start + 4 + msg_len];

    assert_eq!(
        ext_len,
        extensions_data.len(),
        "Extension length mismatch: declared {}, actual {}",
        ext_len,
        extensions_data.len()
    );
}

#[test]
fn test_validate_tls_handshake_format() {
    let mut handshake = vec![0u8; 100];
    handshake[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].copy_from_slice(&[0x42; 32]);
    handshake[TLS_DIGEST_POS + TLS_DIGEST_LEN] = 32;

    let secrets = vec![("test".to_string(), b"secret".to_vec())];
    let result = validate_tls_handshake(&handshake, &secrets, true);
    assert!(result.is_none());
}

fn build_client_hello_with_exts(exts: Vec<(u16, Vec<u8>)>, host: &str) -> Vec<u8> {
    let mut body = Vec::new();
    body.extend_from_slice(&TLS_VERSION);
    body.extend_from_slice(&[0u8; 32]);
    body.push(0);
    body.extend_from_slice(&2u16.to_be_bytes());
    body.extend_from_slice(&[0x13, 0x01]);
    body.push(1);
    body.push(0);

    let host_bytes = host.as_bytes();
    let mut sni_ext = Vec::new();
    sni_ext.extend_from_slice(&(host_bytes.len() as u16 + 3).to_be_bytes());
    sni_ext.push(0);
    sni_ext.extend_from_slice(&(host_bytes.len() as u16).to_be_bytes());
    sni_ext.extend_from_slice(host_bytes);

    let mut ext_blob = Vec::new();
    for (typ, data) in exts {
        ext_blob.extend_from_slice(&typ.to_be_bytes());
        ext_blob.extend_from_slice(&(data.len() as u16).to_be_bytes());
        ext_blob.extend_from_slice(&data);
    }
    ext_blob.extend_from_slice(&0x0000u16.to_be_bytes());
    ext_blob.extend_from_slice(&(sni_ext.len() as u16).to_be_bytes());
    ext_blob.extend_from_slice(&sni_ext);

    body.extend_from_slice(&(ext_blob.len() as u16).to_be_bytes());
    body.extend_from_slice(&ext_blob);

    let mut handshake = Vec::new();
    handshake.push(0x01);
    let len_bytes = (body.len() as u32).to_be_bytes();
    handshake.extend_from_slice(&len_bytes[1..4]);
    handshake.extend_from_slice(&body);

    let mut record = Vec::new();
    record.push(TLS_RECORD_HANDSHAKE);
    record.extend_from_slice(&[0x03, 0x01]);
    record.extend_from_slice(&(handshake.len() as u16).to_be_bytes());
    record.extend_from_slice(&handshake);
    record
}

fn build_client_hello_with_raw_extensions(ext_blob: &[u8]) -> Vec<u8> {
    let mut body = Vec::new();
    body.extend_from_slice(&TLS_VERSION);
    body.extend_from_slice(&[0u8; 32]);
    body.push(0);
    body.extend_from_slice(&2u16.to_be_bytes());
    body.extend_from_slice(&[0x13, 0x01]);
    body.push(1);
    body.push(0);
    body.extend_from_slice(&(ext_blob.len() as u16).to_be_bytes());
    body.extend_from_slice(ext_blob);

    let mut handshake = Vec::new();
    handshake.push(0x01);
    let len_bytes = (body.len() as u32).to_be_bytes();
    handshake.extend_from_slice(&len_bytes[1..4]);
    handshake.extend_from_slice(&body);

    let mut record = Vec::new();
    record.push(TLS_RECORD_HANDSHAKE);
    record.extend_from_slice(&[0x03, 0x01]);
    record.extend_from_slice(&(handshake.len() as u16).to_be_bytes());
    record.extend_from_slice(&handshake);
    record
}

#[test]
fn test_extract_sni_with_grease_extension() {
    let ch = build_client_hello_with_exts(vec![(0x0a0a, Vec::new())], "example.com");
    let sni = extract_sni_from_client_hello(&ch);
    assert_eq!(sni.as_deref(), Some("example.com"));
}

#[test]
fn test_extract_sni_tolerates_empty_unknown_extension() {
    let ch = build_client_hello_with_exts(vec![(0x1234, Vec::new())], "test.local");
    let sni = extract_sni_from_client_hello(&ch);
    assert_eq!(sni.as_deref(), Some("test.local"));
}

#[test]
fn test_extract_alpn_single() {
    let mut alpn_data = Vec::new();
    alpn_data.extend_from_slice(&3u16.to_be_bytes());
    alpn_data.push(2);
    alpn_data.extend_from_slice(b"h2");
    let ch = build_client_hello_with_exts(vec![(0x0010, alpn_data)], "alpn.test");
    let alpn = extract_alpn_from_client_hello(&ch);
    let alpn_str: Vec<String> = alpn
        .iter()
        .map(|p| std::str::from_utf8(p).unwrap().to_string())
        .collect();
    assert_eq!(alpn_str, vec!["h2"]);
}

#[test]
fn test_extract_alpn_multiple() {
    let mut alpn_data = Vec::new();
    alpn_data.extend_from_slice(&11u16.to_be_bytes());
    alpn_data.push(2);
    alpn_data.extend_from_slice(b"h2");
    alpn_data.push(4);
    alpn_data.extend_from_slice(b"spdy");
    alpn_data.push(2);
    alpn_data.extend_from_slice(b"h3");
    let ch = build_client_hello_with_exts(vec![(0x0010, alpn_data)], "alpn.test");
    let alpn = extract_alpn_from_client_hello(&ch);
    let alpn_str: Vec<String> = alpn
        .iter()
        .map(|p| std::str::from_utf8(p).unwrap().to_string())
        .collect();
    assert_eq!(alpn_str, vec!["h2", "spdy", "h3"]);
}

#[test]
fn extract_sni_rejects_zero_length_host_name() {
    let mut sni_ext = Vec::new();
    sni_ext.extend_from_slice(&3u16.to_be_bytes());
    sni_ext.push(0);
    sni_ext.extend_from_slice(&0u16.to_be_bytes());

    let mut ext_blob = Vec::new();
    ext_blob.extend_from_slice(&0x0000u16.to_be_bytes());
    ext_blob.extend_from_slice(&(sni_ext.len() as u16).to_be_bytes());
    ext_blob.extend_from_slice(&sni_ext);

    let ch = build_client_hello_with_raw_extensions(&ext_blob);
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_sni_rejects_raw_ipv4_literals() {
    let ch = build_client_hello_with_exts(Vec::new(), "203.0.113.10");
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_sni_rejects_invalid_label_characters() {
    let ch = build_client_hello_with_exts(Vec::new(), "exa_mple.com");
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_sni_rejects_oversized_label() {
    let oversized = format!("{}.example.com", "a".repeat(64));
    let ch = build_client_hello_with_exts(Vec::new(), &oversized);
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_sni_rejects_when_extension_block_is_truncated() {
    let mut ext_blob = Vec::new();
    ext_blob.extend_from_slice(&0x0000u16.to_be_bytes());
    ext_blob.extend_from_slice(&5u16.to_be_bytes());
    ext_blob.extend_from_slice(&[0, 3, 0]);

    let mut ch = build_client_hello_with_raw_extensions(&ext_blob);
    ch.pop();
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_sni_rejects_session_id_len_overflow() {
    let mut ch = build_client_hello_with_exts(Vec::new(), "example.com");
    let sid_len_pos = 5 + 4 + 2 + 32;
    ch[sid_len_pos] = 255;
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_sni_rejects_cipher_suites_len_overflow() {
    let mut ch = build_client_hello_with_exts(Vec::new(), "example.com");
    let sid_len_pos = 5 + 4 + 2 + 32;
    let cipher_len_pos = sid_len_pos + 1 + ch[sid_len_pos] as usize;
    ch[cipher_len_pos] = 0xFF;
    ch[cipher_len_pos + 1] = 0xFF;
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_sni_rejects_compression_methods_len_overflow() {
    let mut ch = build_client_hello_with_exts(Vec::new(), "example.com");
    let sid_len_pos = 5 + 4 + 2 + 32;
    let cipher_len_pos = sid_len_pos + 1 + ch[sid_len_pos] as usize;
    let cipher_len = u16::from_be_bytes([ch[cipher_len_pos], ch[cipher_len_pos + 1]]) as usize;
    let comp_len_pos = cipher_len_pos + 2 + cipher_len;
    ch[comp_len_pos] = 0xFF;
    assert!(extract_sni_from_client_hello(&ch).is_none());
}

#[test]
fn extract_alpn_returns_empty_on_session_id_len_overflow() {
    let mut alpn_data = Vec::new();
    alpn_data.extend_from_slice(&3u16.to_be_bytes());
    alpn_data.push(2);
    alpn_data.extend_from_slice(b"h2");
    let mut ch = build_client_hello_with_exts(vec![(0x0010, alpn_data)], "alpn.test");
    let sid_len_pos = 5 + 4 + 2 + 32;
    ch[sid_len_pos] = 255;
    assert!(extract_alpn_from_client_hello(&ch).is_empty());
}

#[test]
fn extract_alpn_rejects_when_extension_block_is_truncated() {
    let mut ext_blob = Vec::new();
    ext_blob.extend_from_slice(&0x0010u16.to_be_bytes());
    ext_blob.extend_from_slice(&5u16.to_be_bytes());
    ext_blob.extend_from_slice(&[0, 3, 2, b'h']);

    let ch = build_client_hello_with_raw_extensions(&ext_blob);
    assert!(extract_alpn_from_client_hello(&ch).is_empty());
}

#[test]
fn extract_alpn_rejects_nested_length_overflow() {
    let mut alpn_data = Vec::new();
    alpn_data.extend_from_slice(&10u16.to_be_bytes());
    alpn_data.push(8);
    alpn_data.extend_from_slice(b"h2");

    let mut ext_blob = Vec::new();
    ext_blob.extend_from_slice(&0x0010u16.to_be_bytes());
    ext_blob.extend_from_slice(&(alpn_data.len() as u16).to_be_bytes());
    ext_blob.extend_from_slice(&alpn_data);

    let ch = build_client_hello_with_raw_extensions(&ext_blob);
    assert!(extract_alpn_from_client_hello(&ch).is_empty());
}

// ------------------------------------------------------------------
// Additional adversarial checks
// ------------------------------------------------------------------

#[test]
fn empty_secret_hmac_is_supported() {
    let secret: &[u8] = b"";
    let handshake = make_valid_tls_handshake(secret, 0);
    let secrets = vec![("empty".to_string(), secret.to_vec())];
    let result = validate_tls_handshake(&handshake, &secrets, true);
    assert!(
        result.is_some(),
        "Empty HMAC key must not panic and must validate when correct"
    );
}

#[test]
fn server_hello_digest_verifies_against_full_response() {
    let secret = b"fronting_digest_verify_key";
    let client_digest = [0x42u8; TLS_DIGEST_LEN];
    let session_id = vec![0xAA; 32];
    let rng = crate::crypto::SecureRandom::new();

    let response = build_server_hello(secret, &client_digest, &session_id, 1024, &rng, None, 1);
    let mut zeroed = response.clone();
    zeroed[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);

    let mut hmac_input = Vec::with_capacity(TLS_DIGEST_LEN + zeroed.len());
    hmac_input.extend_from_slice(&client_digest);
    hmac_input.extend_from_slice(&zeroed);
    let expected = sha256_hmac(secret, &hmac_input);

    assert_eq!(
        &response[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN],
        &expected,
        "ServerHello digest must be verifiable by a client that recomputes HMAC over full response"
    );
}

#[test]
fn server_hello_digest_fails_after_single_byte_tamper() {
    let secret = b"fronting_tamper_detect_key";
    let client_digest = [0x24u8; TLS_DIGEST_LEN];
    let session_id = vec![0xBB; 32];
    let rng = crate::crypto::SecureRandom::new();

    let mut response = build_server_hello(secret, &client_digest, &session_id, 1024, &rng, None, 0);
    response[TLS_DIGEST_POS + TLS_DIGEST_LEN + 1] ^= 0x01;

    let mut zeroed = response.clone();
    zeroed[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);

    let mut hmac_input = Vec::with_capacity(TLS_DIGEST_LEN + zeroed.len());
    hmac_input.extend_from_slice(&client_digest);
    hmac_input.extend_from_slice(&zeroed);
    let expected = sha256_hmac(secret, &hmac_input);

    assert_ne!(
        &response[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN],
        &expected,
        "Tampering any response byte must invalidate the embedded digest"
    );
}

#[test]
fn server_hello_application_data_payload_varies_across_runs() {
    use std::collections::HashSet;

    let secret = b"fronting_payload_variability_key";
    let client_digest = [0x13u8; TLS_DIGEST_LEN];
    let session_id = vec![0x44; 32];
    let rng = crate::crypto::SecureRandom::new();

    let mut unique_payloads: HashSet<Vec<u8>> = HashSet::new();
    for _ in 0..16 {
        let response = build_server_hello(secret, &client_digest, &session_id, 1024, &rng, None, 0);

        let sh_len = u16::from_be_bytes([response[3], response[4]]) as usize;
        let ccs_pos = 5 + sh_len;
        let ccs_len = u16::from_be_bytes([response[ccs_pos + 3], response[ccs_pos + 4]]) as usize;
        let app_pos = ccs_pos + 5 + ccs_len;

        assert_eq!(response[app_pos], TLS_RECORD_APPLICATION);
        let app_len = u16::from_be_bytes([response[app_pos + 3], response[app_pos + 4]]) as usize;
        let payload = response[app_pos + 5..app_pos + 5 + app_len].to_vec();

        assert!(
            payload.iter().any(|&b| b != 0),
            "Payload must not be all-zero deterministic filler"
        );
        unique_payloads.insert(payload);
    }

    assert!(
        unique_payloads.len() >= 4,
        "ApplicationData payload should vary across runs to reduce fingerprintability"
    );
}

#[test]
fn replay_window_zero_disables_boot_bypass_for_any_nonzero_timestamp() {
    let secret = b"window_zero_boot_bypass_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];

    let ts1 = make_valid_tls_handshake(secret, 1);
    assert!(
        validate_tls_handshake_with_replay_window(&ts1, &secrets, false, 0).is_none(),
        "replay_window_secs=0 must reject nonzero timestamps even in boot-time range"
    );

    let ts0 = make_valid_tls_handshake(secret, 0);
    assert!(
        validate_tls_handshake_with_replay_window(&ts0, &secrets, false, 0).is_none(),
        "replay_window_secs=0 enforces strict skew check and rejects timestamp=0 on normal wall-clock systems"
    );
}

#[test]
fn large_replay_window_does_not_expand_time_skew_acceptance() {
    let secret = b"large_replay_window_skew_bound_test";
    let secrets = vec![("u".to_string(), secret.to_vec())];
    let now: i64 = 1_700_000_000;

    let ts_far_past = (now - 600) as u32;
    let valid = make_valid_tls_handshake(secret, ts_far_past);
    assert!(
        validate_tls_handshake_with_replay_window(&valid, &secrets, false, 86_400).is_none(),
        "large replay window must not relax strict skew check once boot-time bypass is not in play"
    );
}

#[test]
fn parse_tls_record_header_accepts_tls_version_constant() {
    let header = [
        TLS_RECORD_HANDSHAKE,
        TLS_VERSION[0],
        TLS_VERSION[1],
        0x00,
        0x2A,
    ];
    let parsed = parse_tls_record_header(&header).expect("TLS_VERSION header should be accepted");
    assert_eq!(parsed.0, TLS_RECORD_HANDSHAKE);
    assert_eq!(parsed.1, 42);
}

#[test]
fn server_hello_clamps_fake_cert_len_lower_bound() {
    let secret = b"fake_cert_lower_bound_test";
    let client_digest = [0x11u8; TLS_DIGEST_LEN];
    let session_id = vec![0x77; 32];
    let rng = crate::crypto::SecureRandom::new();

    let response = build_server_hello(secret, &client_digest, &session_id, 1, &rng, None, 0);

    let sh_len = u16::from_be_bytes([response[3], response[4]]) as usize;
    let ccs_pos = 5 + sh_len;
    let ccs_len = u16::from_be_bytes([response[ccs_pos + 3], response[ccs_pos + 4]]) as usize;
    let app_pos = ccs_pos + 5 + ccs_len;
    let app_len = u16::from_be_bytes([response[app_pos + 3], response[app_pos + 4]]) as usize;

    assert_eq!(response[app_pos], TLS_RECORD_APPLICATION);
    assert_eq!(
        app_len, 64,
        "fake cert payload must be clamped to minimum 64 bytes"
    );
}

#[test]
fn server_hello_clamps_fake_cert_len_upper_bound() {
    let secret = b"fake_cert_upper_bound_test";
    let client_digest = [0x22u8; TLS_DIGEST_LEN];
    let session_id = vec![0x66; 32];
    let rng = crate::crypto::SecureRandom::new();

    let response = build_server_hello(secret, &client_digest, &session_id, 65_535, &rng, None, 0);

    let sh_len = u16::from_be_bytes([response[3], response[4]]) as usize;
    let ccs_pos = 5 + sh_len;
    let ccs_len = u16::from_be_bytes([response[ccs_pos + 3], response[ccs_pos + 4]]) as usize;
    let app_pos = ccs_pos + 5 + ccs_len;
    let app_len = u16::from_be_bytes([response[app_pos + 3], response[app_pos + 4]]) as usize;

    assert_eq!(response[app_pos], TLS_RECORD_APPLICATION);
    assert_eq!(
        app_len, MAX_TLS_CIPHERTEXT_SIZE,
        "fake cert payload must be clamped to TLS record max bound"
    );
}

#[test]
fn server_hello_new_session_ticket_count_matches_configuration() {
    let secret = b"ticket_count_surface_test";
    let client_digest = [0x33u8; TLS_DIGEST_LEN];
    let session_id = vec![0x55; 32];
    let rng = crate::crypto::SecureRandom::new();

    let tickets: u8 = 3;
    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        1024,
        &rng,
        None,
        tickets,
    );

    let mut pos = 0usize;
    let mut app_records = 0usize;
    while pos + 5 <= response.len() {
        let rtype = response[pos];
        let rlen = u16::from_be_bytes([response[pos + 3], response[pos + 4]]) as usize;
        let next = pos + 5 + rlen;
        assert!(
            next <= response.len(),
            "TLS record must stay inside response bounds"
        );
        if rtype == TLS_RECORD_APPLICATION {
            app_records += 1;
        }
        pos = next;
    }

    assert_eq!(
        app_records,
        1 + tickets as usize,
        "response must contain one main application record plus configured ticket-like tail records"
    );
}

#[test]
fn server_hello_new_session_ticket_count_is_safely_capped() {
    let secret = b"ticket_count_cap_test";
    let client_digest = [0x44u8; TLS_DIGEST_LEN];
    let session_id = vec![0x54; 32];
    let rng = crate::crypto::SecureRandom::new();

    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        1024,
        &rng,
        None,
        u8::MAX,
    );

    let mut pos = 0usize;
    let mut app_records = 0usize;
    while pos + 5 <= response.len() {
        let rtype = response[pos];
        let rlen = u16::from_be_bytes([response[pos + 3], response[pos + 4]]) as usize;
        let next = pos + 5 + rlen;
        assert!(
            next <= response.len(),
            "TLS record must stay inside response bounds"
        );
        if rtype == TLS_RECORD_APPLICATION {
            app_records += 1;
        }
        pos = next;
    }

    assert_eq!(
        app_records, 5,
        "response must cap ticket-like tail records to four plus one main application record"
    );
}

#[test]
fn boot_time_handshake_replay_remains_blocked_after_cache_window_expires() {
    let secret = b"gap_t01_boot_replay";
    let secrets = vec![("user".to_string(), secret.to_vec())];
    let handshake = make_valid_tls_handshake(secret, 1);

    let validation = validate_tls_handshake_with_replay_window(&handshake, &secrets, false, 2)
        .expect("boot-time handshake must validate on first use");

    let checker = crate::stats::ReplayChecker::new(128, std::time::Duration::from_millis(40));
    let digest_half = &validation.digest[..TLS_DIGEST_HALF_LEN];

    assert!(
        !checker.check_and_add_tls_digest(digest_half),
        "first use must not be treated as replay"
    );
    assert!(
        checker.check_and_add_tls_digest(digest_half),
        "immediate second use must be detected as replay"
    );

    std::thread::sleep(std::time::Duration::from_millis(70));

    let validation_after_expiry =
        validate_tls_handshake_with_replay_window(&handshake, &secrets, false, 2)
            .expect("boot-time handshake must still cryptographically validate after cache expiry");
    let digest_half_after_expiry = &validation_after_expiry.digest[..TLS_DIGEST_HALF_LEN];
    assert_eq!(
        digest_half, digest_half_after_expiry,
        "replay key must be stable for same handshake"
    );

    assert!(
        checker.check_and_add_tls_digest(digest_half_after_expiry),
        "after cache window expiry, the same boot-time handshake must still be treated as replay"
    );
}

#[test]
fn adversarial_boot_time_handshake_should_not_be_replayable_after_cache_expiry() {
    let secret = b"gap_t01_boot_replay_adversarial";
    let secrets = vec![("user".to_string(), secret.to_vec())];
    let handshake = make_valid_tls_handshake(secret, 1);

    let validation = validate_tls_handshake_with_replay_window(&handshake, &secrets, false, 2)
        .expect("boot-time handshake must validate on first use");

    let checker = crate::stats::ReplayChecker::new(128, std::time::Duration::from_millis(40));
    let digest_half = &validation.digest[..TLS_DIGEST_HALF_LEN];

    assert!(
        !checker.check_and_add_tls_digest(digest_half),
        "first use must not be treated as replay"
    );
    assert!(
        checker.check_and_add_tls_digest(digest_half),
        "immediate reuse must be rejected as replay"
    );

    std::thread::sleep(std::time::Duration::from_millis(70));

    let validation_after_expiry =
        validate_tls_handshake_with_replay_window(&handshake, &secrets, false, 2)
            .expect("boot-time handshake still validates cryptographically after cache expiry");
    let digest_half_after_expiry = &validation_after_expiry.digest[..TLS_DIGEST_HALF_LEN];

    assert_eq!(
        digest_half, digest_half_after_expiry,
        "replay key must remain stable for the same captured handshake"
    );

    assert!(
        checker.check_and_add_tls_digest(digest_half_after_expiry),
        "security expectation: a boot-time handshake should remain replay-protected even after cache expiry"
    );
}

#[test]
fn stress_short_replay_window_boot_timestamp_replay_cycles_remain_fail_closed_in_window() {
    let secret = b"gap_t01_boot_replay_stress";
    let secrets = vec![("user".to_string(), secret.to_vec())];
    let handshake = make_valid_tls_handshake(secret, 1);

    let checker = crate::stats::ReplayChecker::new(256, std::time::Duration::from_millis(25));

    for cycle in 0..64 {
        let validation = validate_tls_handshake_with_replay_window(&handshake, &secrets, false, 2)
            .expect("boot-time handshake must validate");
        let digest_half = &validation.digest[..TLS_DIGEST_HALF_LEN];

        if cycle == 0 {
            assert!(
                !checker.check_and_add_tls_digest(digest_half),
                "cycle 0: first use must be fresh"
            );
            assert!(
                checker.check_and_add_tls_digest(digest_half),
                "cycle 0: second use must be replay"
            );
        } else {
            assert!(
                checker.check_and_add_tls_digest(digest_half),
                "cycle {cycle}: digest must remain replay-protected across short-window churn"
            );
        }

        std::thread::sleep(std::time::Duration::from_millis(30));
    }
}

#[test]
fn light_fuzz_boot_time_timestamp_matrix_with_short_replay_window_obeys_boot_cap() {
    let secret = b"gap_t01_boot_replay_fuzz";
    let secrets = vec![("user".to_string(), secret.to_vec())];

    let mut s: u64 = 0xA1B2_C3D4_55AA_7733;
    for _ in 0..2048 {
        s ^= s << 7;
        s ^= s >> 9;
        s ^= s << 8;
        let ts = (s as u32) % 8;

        let handshake = make_valid_tls_handshake(secret, ts);
        let accepted =
            validate_tls_handshake_with_replay_window(&handshake, &secrets, false, 2).is_some();

        if ts < 2 {
            assert!(
                accepted,
                "timestamp {ts} must remain boot-time compatible under 2s cap"
            );
        } else {
            assert!(
                !accepted,
                "timestamp {ts} must be rejected when outside replay-window boot cap"
            );
        }
    }
}

#[test]
fn server_hello_application_data_contains_alpn_marker_when_selected() {
    let secret = b"alpn_marker_test";
    let client_digest = [0x55u8; TLS_DIGEST_LEN];
    let session_id = vec![0xAB; 32];
    let rng = crate::crypto::SecureRandom::new();

    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        512,
        &rng,
        Some(b"h2".to_vec()),
        0,
    );

    let sh_len = u16::from_be_bytes([response[3], response[4]]) as usize;
    let ccs_pos = 5 + sh_len;
    let ccs_len = u16::from_be_bytes([response[ccs_pos + 3], response[ccs_pos + 4]]) as usize;
    let app_pos = ccs_pos + 5 + ccs_len;
    let app_len = u16::from_be_bytes([response[app_pos + 3], response[app_pos + 4]]) as usize;
    let app_payload = &response[app_pos + 5..app_pos + 5 + app_len];

    let expected = [0x00u8, 0x10, 0x00, 0x05, 0x00, 0x03, 0x02, b'h', b'2'];
    assert!(
        app_payload
            .windows(expected.len())
            .any(|window| window == expected),
        "first application payload must carry ALPN marker for selected protocol"
    );
}

#[test]
fn server_hello_ignores_oversized_alpn_and_still_caps_ticket_tail() {
    let secret = b"alpn_oversize_ignore_test";
    let client_digest = [0x56u8; TLS_DIGEST_LEN];
    let session_id = vec![0xCD; 32];
    let rng = crate::crypto::SecureRandom::new();
    let oversized_alpn = vec![b'x'; u8::MAX as usize + 1];

    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        512,
        &rng,
        Some(oversized_alpn),
        u8::MAX,
    );

    let mut pos = 0usize;
    let mut app_records = 0usize;
    let mut first_app_payload: Option<&[u8]> = None;
    while pos + 5 <= response.len() {
        let rtype = response[pos];
        let rlen = u16::from_be_bytes([response[pos + 3], response[pos + 4]]) as usize;
        let next = pos + 5 + rlen;
        assert!(
            next <= response.len(),
            "TLS record must stay inside response bounds"
        );
        if rtype == TLS_RECORD_APPLICATION {
            app_records += 1;
            if first_app_payload.is_none() {
                first_app_payload = Some(&response[pos + 5..next]);
            }
        }
        pos = next;
    }
    let marker = [
        0x00u8, 0x10, 0x00, 0x06, 0x00, 0x04, 0x03, b'x', b'x', b'x', b'x',
    ];

    assert_eq!(
        app_records, 5,
        "oversized ALPN must not change the four-ticket cap on tail records"
    );
    assert!(
        !first_app_payload
            .expect("response must contain an application record")
            .windows(marker.len())
            .any(|window| window == marker),
        "oversized ALPN must be ignored rather than embedded into the first application payload"
    );
}

#[test]
fn server_hello_ignores_oversized_alpn_when_marker_would_not_fit() {
    let secret = b"alpn_too_large_to_fit_test";
    let client_digest = [0x57u8; TLS_DIGEST_LEN];
    let session_id = vec![0xEF; 32];
    let rng = crate::crypto::SecureRandom::new();
    let oversized_alpn = vec![0xAB; u8::MAX as usize];

    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        64,
        &rng,
        Some(oversized_alpn),
        0,
    );

    let sh_len = u16::from_be_bytes([response[3], response[4]]) as usize;
    let ccs_pos = 5 + sh_len;
    let ccs_len = u16::from_be_bytes([response[ccs_pos + 3], response[ccs_pos + 4]]) as usize;
    let app_pos = ccs_pos + 5 + ccs_len;
    let app_len = u16::from_be_bytes([response[app_pos + 3], response[app_pos + 4]]) as usize;
    let app_payload = &response[app_pos + 5..app_pos + 5 + app_len];

    let mut marker_prefix = Vec::new();
    marker_prefix.extend_from_slice(&0x0010u16.to_be_bytes());
    marker_prefix.extend_from_slice(&0x0102u16.to_be_bytes());
    marker_prefix.extend_from_slice(&0x0100u16.to_be_bytes());
    marker_prefix.push(0xff);
    marker_prefix.extend_from_slice(&[0xab; 8]);
    assert!(
        !app_payload.starts_with(&marker_prefix),
        "oversized ALPN must not be partially embedded into the ServerHello application record"
    );
}

#[test]
fn server_hello_embeds_full_alpn_marker_when_it_exactly_fits_fake_cert_len() {
    let secret = b"alpn_exact_fit_test";
    let client_digest = [0x58u8; TLS_DIGEST_LEN];
    let session_id = vec![0xA5; 32];
    let rng = crate::crypto::SecureRandom::new();
    let proto = vec![b'z'; 57];

    // marker_len = 4 + (2 + (1 + proto_len)) = 7 + proto_len = 64
    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        64,
        &rng,
        Some(proto.clone()),
        0,
    );

    let sh_len = u16::from_be_bytes([response[3], response[4]]) as usize;
    let ccs_pos = 5 + sh_len;
    let ccs_len = u16::from_be_bytes([response[ccs_pos + 3], response[ccs_pos + 4]]) as usize;
    let app_pos = ccs_pos + 5 + ccs_len;
    let app_len = u16::from_be_bytes([response[app_pos + 3], response[app_pos + 4]]) as usize;
    let app_payload = &response[app_pos + 5..app_pos + 5 + app_len];

    let mut expected_marker = Vec::new();
    expected_marker.extend_from_slice(&0x0010u16.to_be_bytes());
    expected_marker.extend_from_slice(&0x003Cu16.to_be_bytes());
    expected_marker.extend_from_slice(&0x003Au16.to_be_bytes());
    expected_marker.push(57u8);
    expected_marker.extend_from_slice(&proto);

    assert_eq!(app_payload.len(), expected_marker.len());
    assert_eq!(app_payload, expected_marker.as_slice());
}

#[test]
fn server_hello_does_not_embed_partial_alpn_marker_when_one_byte_short() {
    let secret = b"alpn_one_byte_short_test";
    let client_digest = [0x59u8; TLS_DIGEST_LEN];
    let session_id = vec![0xA6; 32];
    let rng = crate::crypto::SecureRandom::new();
    let proto = vec![0xAB; 58];

    // marker_len = 65, fake_cert_len = 64 => marker must be fully skipped.
    let response = build_server_hello(
        secret,
        &client_digest,
        &session_id,
        64,
        &rng,
        Some(proto),
        0,
    );

    let sh_len = u16::from_be_bytes([response[3], response[4]]) as usize;
    let ccs_pos = 5 + sh_len;
    let ccs_len = u16::from_be_bytes([response[ccs_pos + 3], response[ccs_pos + 4]]) as usize;
    let app_pos = ccs_pos + 5 + ccs_len;
    let app_len = u16::from_be_bytes([response[app_pos + 3], response[app_pos + 4]]) as usize;
    let app_payload = &response[app_pos + 5..app_pos + 5 + app_len];

    let mut marker_prefix = Vec::new();
    marker_prefix.extend_from_slice(&0x0010u16.to_be_bytes());
    marker_prefix.extend_from_slice(&0x003Du16.to_be_bytes());
    marker_prefix.extend_from_slice(&0x003Bu16.to_be_bytes());
    marker_prefix.push(58u8);
    marker_prefix.extend_from_slice(&[0xAB; 8]);

    assert!(
        !app_payload.starts_with(&marker_prefix),
        "one-byte-short ALPN marker must be skipped entirely, not partially embedded"
    );
}

#[test]
fn exhaustive_tls_minor_version_classification_matches_policy() {
    for minor in 0u8..=u8::MAX {
        let first = [TLS_RECORD_HANDSHAKE, 0x03, minor];
        let expected = minor == 0x01 || minor == 0x03;
        assert_eq!(
            is_tls_handshake(&first),
            expected,
            "minor version {minor:#04x} classification mismatch"
        );
    }
}

#[test]
fn light_fuzz_tls_header_classifier_and_parser_policy_consistency() {
    // Deterministic xorshift state keeps this fuzz test reproducible.
    let mut s: u64 = 0x9E37_79B9_AA95_5A5D;

    for _ in 0..10_000 {
        s ^= s << 7;
        s ^= s >> 9;
        s ^= s << 8;

        let header = [
            (s & 0xff) as u8,
            ((s >> 8) & 0xff) as u8,
            ((s >> 16) & 0xff) as u8,
            ((s >> 24) & 0xff) as u8,
            ((s >> 32) & 0xff) as u8,
        ];

        let classified = is_tls_handshake(&header[..3]);
        let expected_classified = header[0] == TLS_RECORD_HANDSHAKE
            && header[1] == 0x03
            && (header[2] == 0x01 || header[2] == 0x03);
        assert_eq!(
            classified, expected_classified,
            "classifier policy mismatch for header {header:02x?}"
        );

        let parsed = parse_tls_record_header(&header);
        let expected_parsed =
            header[1] == 0x03 && (header[2] == 0x01 || header[2] == TLS_VERSION[1]);
        assert_eq!(
            parsed.is_some(),
            expected_parsed,
            "parser policy mismatch for header {header:02x?}"
        );
    }
}

#[test]
fn stress_random_noise_handshakes_never_authenticate() {
    let secret = b"stress_noise_secret";
    let secrets = vec![("noise-user".to_string(), secret.to_vec())];

    // Deterministic xorshift state keeps this stress test reproducible.
    let mut s: u64 = 0xD1B5_4A32_9C6E_77F1;

    for _ in 0..5_000 {
        s ^= s << 7;
        s ^= s >> 9;
        s ^= s << 8;

        let len = 1 + ((s as usize) % 196);
        let mut buf = vec![0u8; len];
        for b in &mut buf {
            s ^= s << 7;
            s ^= s >> 9;
            s ^= s << 8;
            *b = (s & 0xff) as u8;
        }

        assert!(
            validate_tls_handshake(&buf, &secrets, true).is_none(),
            "random noise must never authenticate"
        );
    }
}
