#![allow(clippy::too_many_arguments)]

use crate::crypto::{SecureRandom, sha256_hmac};
use crate::protocol::constants::{
    MAX_TLS_CIPHERTEXT_SIZE, TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER,
    TLS_RECORD_HANDSHAKE, TLS_VERSION,
};
use crate::protocol::tls::{
    ClientHelloTlsVersion, ServerHelloKeyShare, TLS_DIGEST_LEN, TLS_DIGEST_POS,
    TLS_NAMED_GROUP_X25519, TLS_NAMED_GROUP_X25519MLKEM768,
};
use crate::tls_front::types::{
    CachedTlsData, ParsedCertificateInfo, TlsExtension, TlsProfileSource,
};
use crc32fast::Hasher;

const MIN_APP_DATA: usize = 64;
const MAX_APP_DATA: usize = MAX_TLS_CIPHERTEXT_SIZE;
const MAX_TICKET_RECORDS: usize = 4;
const EXT_SUPPORTED_VERSIONS: u16 = 0x002b;
const EXT_KEY_SHARE: u16 = 0x0033;
const EXT_ALPN: u16 = 0x0010;

#[derive(Clone, Copy)]
enum FallbackShapeFamily {
    NginxLike,
    BoringSslLike,
    RustlsLike,
}

fn parse_profiled_key_share_group(data: &[u8]) -> Option<u16> {
    if data.len() < 4 {
        return None;
    }

    let group = u16::from_be_bytes([data[0], data[1]]);
    let key_exchange_len = u16::from_be_bytes([data[2], data[3]]) as usize;
    if data.len() != 4 + key_exchange_len {
        return None;
    }

    match group {
        TLS_NAMED_GROUP_X25519 | TLS_NAMED_GROUP_X25519MLKEM768 => Some(group),
        _ => None,
    }
}

fn effective_profiled_server_hello_record_len(cached: &CachedTlsData) -> usize {
    if cached.behavior_profile.server_hello_record_len == 0 {
        cached.server_hello_template.record_body_len()
    } else {
        cached.behavior_profile.server_hello_record_len
    }
}

fn should_replay_profiled_server_hello_shape(cached: &CachedTlsData) -> bool {
    matches!(
        cached.behavior_profile.source,
        TlsProfileSource::Raw | TlsProfileSource::Merged
    ) && cached
        .server_hello_template
        .is_replay_safe_tls13_shape(effective_profiled_server_hello_record_len(cached))
}

/// Return the origin-profiled ServerHello key_share group when it is replay-safe.
pub(crate) fn profiled_server_hello_key_share_group(cached: &CachedTlsData) -> Option<u16> {
    if !should_replay_profiled_server_hello_shape(cached) {
        return None;
    }

    cached
        .server_hello_template
        .extensions
        .iter()
        .find(|ext| ext.ext_type == EXT_KEY_SHARE)
        .and_then(|ext| parse_profiled_key_share_group(&ext.data))
}

fn jitter_and_clamp_sizes(sizes: &[usize], rng: &SecureRandom) -> Vec<usize> {
    sizes
        .iter()
        .map(|&size| {
            let base = size.clamp(MIN_APP_DATA, MAX_APP_DATA);
            let jitter_range = ((base as f64) * 0.03).round() as i64;
            if jitter_range == 0 {
                return base;
            }
            let mut rand_bytes = [0u8; 2];
            rand_bytes.copy_from_slice(&rng.bytes(2));
            let span = 2 * jitter_range + 1;
            let delta = (u16::from_le_bytes(rand_bytes) as i64 % span) - jitter_range;
            let adjusted = (base as i64 + delta).clamp(MIN_APP_DATA as i64, MAX_APP_DATA as i64);
            adjusted as usize
        })
        .collect()
}

fn app_data_body_capacity(sizes: &[usize]) -> usize {
    sizes.iter().map(|&size| size.saturating_sub(17)).sum()
}

fn ensure_payload_capacity(mut sizes: Vec<usize>, payload_len: usize) -> Vec<usize> {
    if payload_len == 0 {
        return sizes;
    }

    let mut body_total = app_data_body_capacity(&sizes);
    if body_total >= payload_len {
        return sizes;
    }

    if let Some(last) = sizes.last_mut() {
        let free = MAX_APP_DATA.saturating_sub(*last);
        let grow = free.min(payload_len - body_total);
        *last += grow;
        body_total += grow;
    }

    while body_total < payload_len {
        let remaining = payload_len - body_total;
        let chunk = (remaining + 17).clamp(MIN_APP_DATA, MAX_APP_DATA);
        sizes.push(chunk);
        body_total += chunk.saturating_sub(17);
    }

    sizes
}

fn fallback_shape_family(cached: &CachedTlsData) -> FallbackShapeFamily {
    match cached.behavior_profile.source {
        TlsProfileSource::Rustls => FallbackShapeFamily::RustlsLike,
        TlsProfileSource::Default => {
            let mut hasher = Hasher::new();
            hasher.update(cached.domain.as_bytes());
            hasher.update(&cached.total_app_data_len.to_le_bytes());
            if hasher.finalize() & 1 == 0 {
                FallbackShapeFamily::NginxLike
            } else {
                FallbackShapeFamily::BoringSslLike
            }
        }
        TlsProfileSource::Raw | TlsProfileSource::Merged => FallbackShapeFamily::NginxLike,
    }
}

fn fallback_total_app_data_len(cached: &CachedTlsData) -> usize {
    cached
        .total_app_data_len
        .max(cached.app_data_records_sizes.iter().sum())
        .max(1024)
}

fn push_fallback_size(sizes: &mut Vec<usize>, size: usize) {
    sizes.push(size.clamp(MIN_APP_DATA, MAX_APP_DATA));
}

fn fallback_family_app_data_sizes(cached: &CachedTlsData) -> Vec<usize> {
    let mut sizes = Vec::with_capacity(1);
    let size = if matches!(cached.behavior_profile.source, TlsProfileSource::Rustls) {
        cached
            .app_data_records_sizes
            .first()
            .copied()
            .unwrap_or_else(|| fallback_total_app_data_len(cached))
    } else {
        fallback_total_app_data_len(cached)
    };
    push_fallback_size(&mut sizes, size);
    sizes
}

fn emulated_app_data_sizes(cached: &CachedTlsData) -> Vec<usize> {
    match cached.behavior_profile.source {
        TlsProfileSource::Raw | TlsProfileSource::Merged => {
            if let Some(size) = cached.behavior_profile.app_data_record_sizes.first() {
                return vec![(*size).clamp(MIN_APP_DATA, MAX_APP_DATA)];
            }
            if let Some(size) = cached.app_data_records_sizes.first() {
                return vec![(*size).clamp(MIN_APP_DATA, MAX_APP_DATA)];
            }
            return vec![
                cached
                    .total_app_data_len
                    .max(1024)
                    .clamp(MIN_APP_DATA, MAX_APP_DATA),
            ];
        }
        TlsProfileSource::Default | TlsProfileSource::Rustls => {
            return fallback_family_app_data_sizes(cached);
        }
    }
}

fn emulated_change_cipher_spec_count(_cached: &CachedTlsData) -> usize {
    1
}

fn emulated_ticket_record_sizes(
    cached: &CachedTlsData,
    new_session_tickets: u8,
    rng: &SecureRandom,
) -> Vec<usize> {
    let target_count = usize::from(new_session_tickets.min(MAX_TICKET_RECORDS as u8));
    if target_count == 0 {
        return Vec::new();
    }

    let profiled_sizes = match cached.behavior_profile.source {
        TlsProfileSource::Raw | TlsProfileSource::Merged => {
            cached.behavior_profile.ticket_record_sizes.as_slice()
        }
        TlsProfileSource::Default | TlsProfileSource::Rustls => &[],
    };

    let mut sizes = Vec::with_capacity(target_count);
    sizes.extend(profiled_sizes.iter().copied().take(target_count));

    while sizes.len() < target_count {
        let family = fallback_shape_family(cached);
        let base = match family {
            FallbackShapeFamily::NginxLike => 96,
            FallbackShapeFamily::BoringSslLike => 80,
            FallbackShapeFamily::RustlsLike => 112,
        };
        sizes.push(base + rng.range(64));
    }

    sizes
}

fn build_compact_cert_info_payload(cert_info: &ParsedCertificateInfo) -> Option<Vec<u8>> {
    let mut fields = Vec::new();

    if let Some(subject) = cert_info.subject_cn.as_deref() {
        fields.push(format!("CN={subject}"));
    }
    if let Some(issuer) = cert_info.issuer_cn.as_deref() {
        fields.push(format!("ISSUER={issuer}"));
    }
    if let Some(not_before) = cert_info.not_before_unix {
        fields.push(format!("NB={not_before}"));
    }
    if let Some(not_after) = cert_info.not_after_unix {
        fields.push(format!("NA={not_after}"));
    }
    if !cert_info.san_names.is_empty() {
        let san = cert_info
            .san_names
            .iter()
            .take(8)
            .map(String::as_str)
            .collect::<Vec<_>>()
            .join(",");
        fields.push(format!("SAN={san}"));
    }

    if fields.is_empty() {
        return None;
    }

    let mut payload = fields.join(";").into_bytes();
    if payload.len() > 512 {
        payload.truncate(512);
    }
    Some(payload)
}

fn hash_compact_cert_info_payload(cert_payload: Vec<u8>) -> Option<Vec<u8>> {
    if cert_payload.is_empty() {
        return None;
    }

    let mut hashed = Vec::with_capacity(cert_payload.len());
    let mut seed_hasher = Hasher::new();
    seed_hasher.update(&cert_payload);
    let mut state = seed_hasher.finalize();

    while hashed.len() < cert_payload.len() {
        let mut hasher = Hasher::new();
        hasher.update(&state.to_le_bytes());
        hasher.update(&cert_payload);
        state = hasher.finalize();

        let block = state.to_le_bytes();
        let remaining = cert_payload.len() - hashed.len();
        let copy_len = remaining.min(block.len());
        hashed.extend_from_slice(&block[..copy_len]);
    }

    Some(hashed)
}

fn push_supported_versions_extension(extensions: &mut Vec<u8>) {
    extensions.extend_from_slice(&EXT_SUPPORTED_VERSIONS.to_be_bytes());
    extensions.extend_from_slice(&(2u16).to_be_bytes());
    extensions.extend_from_slice(&0x0304u16.to_be_bytes());
}

fn push_key_share_entry(extensions: &mut Vec<u8>, group: u16, key_exchange: &[u8]) {
    let Ok(key_exchange_len) = u16::try_from(key_exchange.len()) else {
        return;
    };
    let Some(entry_len) = key_exchange.len().checked_add(4) else {
        return;
    };
    let Ok(entry_len) = u16::try_from(entry_len) else {
        return;
    };

    extensions.extend_from_slice(&EXT_KEY_SHARE.to_be_bytes());
    extensions.extend_from_slice(&entry_len.to_be_bytes());
    extensions.extend_from_slice(&group.to_be_bytes());
    extensions.extend_from_slice(&key_exchange_len.to_be_bytes());
    extensions.extend_from_slice(key_exchange);
}

fn push_key_share_extension(extensions: &mut Vec<u8>, server_key_share: &ServerHelloKeyShare) {
    push_key_share_entry(
        extensions,
        server_key_share.group(),
        server_key_share.key_exchange(),
    );
}

fn replay_profiled_server_hello_extension(
    ext: &TlsExtension,
    extensions: &mut Vec<u8>,
    server_key_share: &ServerHelloKeyShare,
    saw_supported_versions: &mut bool,
    saw_key_share: &mut bool,
) {
    match ext.ext_type {
        EXT_SUPPORTED_VERSIONS if !*saw_supported_versions => {
            push_supported_versions_extension(extensions);
            *saw_supported_versions = true;
        }
        EXT_KEY_SHARE if !*saw_key_share => {
            push_key_share_extension(extensions, server_key_share);
            *saw_key_share = true;
        }
        EXT_ALPN => {}
        _ => {}
    }
}

fn build_profiled_server_hello_extensions(
    cached: &CachedTlsData,
    server_key_share: &ServerHelloKeyShare,
) -> Vec<u8> {
    let capacity = cached
        .server_hello_template
        .extensions
        .iter()
        .map(|ext| 4 + ext.data.len())
        .sum::<usize>()
        .max(44);
    let mut extensions = Vec::with_capacity(capacity);
    let mut saw_supported_versions = false;
    let mut saw_key_share = false;

    if should_replay_profiled_server_hello_shape(cached) {
        for ext in &cached.server_hello_template.extensions {
            replay_profiled_server_hello_extension(
                ext,
                &mut extensions,
                server_key_share,
                &mut saw_supported_versions,
                &mut saw_key_share,
            );
        }
    }

    if !saw_supported_versions {
        push_supported_versions_extension(&mut extensions);
    }
    if !saw_key_share {
        push_key_share_extension(&mut extensions, server_key_share);
    }

    extensions
}

/// Build a ServerHello + CCS + ApplicationData sequence using cached TLS metadata.
pub fn build_emulated_server_hello(
    secret: &[u8],
    client_digest: &[u8; TLS_DIGEST_LEN],
    session_id: &[u8],
    cached: &CachedTlsData,
    use_full_cert_payload: bool,
    serverhello_compact: bool,
    client_tls_version: ClientHelloTlsVersion,
    selected_cipher_suite: [u8; 2],
    server_key_share: &ServerHelloKeyShare,
    rng: &SecureRandom,
    alpn: Option<Vec<u8>>,
    new_session_tickets: u8,
) -> Vec<u8> {
    // ServerHello carries the authenticated digest bytes that the client verifies.
    let extensions = build_profiled_server_hello_extensions(cached, server_key_share);
    let extensions_len = extensions.len() as u16;

    let body_len = 2 + 32 + 1 + session_id.len() + 2 + 1 + 2 + extensions.len();

    let mut message = Vec::with_capacity(4 + body_len);
    message.push(0x02);
    let len_bytes = (body_len as u32).to_be_bytes();
    message.extend_from_slice(&len_bytes[1..4]);
    message.extend_from_slice(&cached.server_hello_template.version);
    message.extend_from_slice(&[0u8; 32]);
    message.push(session_id.len() as u8);
    message.extend_from_slice(session_id);
    let cipher = if selected_cipher_suite != [0, 0] {
        selected_cipher_suite
    } else if cached.server_hello_template.cipher_suite == [0, 0] {
        [0x13, 0x01]
    } else {
        cached.server_hello_template.cipher_suite
    };
    message.extend_from_slice(&cipher);
    message.push(cached.server_hello_template.compression);
    message.extend_from_slice(&extensions_len.to_be_bytes());
    message.extend_from_slice(&extensions);

    let mut server_hello = Vec::with_capacity(5 + message.len());
    server_hello.push(TLS_RECORD_HANDSHAKE);
    server_hello.extend_from_slice(&TLS_VERSION);
    server_hello.extend_from_slice(&(message.len() as u16).to_be_bytes());
    server_hello.extend_from_slice(&message);

    // ChangeCipherSpec is part of the client-visible TLS shim prefix.
    let change_cipher_spec_count = emulated_change_cipher_spec_count(cached);
    let mut change_cipher_spec = Vec::with_capacity(change_cipher_spec_count * 6);
    for _ in 0..change_cipher_spec_count {
        change_cipher_spec.extend_from_slice(&[
            TLS_RECORD_CHANGE_CIPHER,
            TLS_VERSION[0],
            TLS_VERSION[1],
            0x00,
            0x01,
            0x01,
        ]);
    }

    // Telegram clients authenticate the hello prefix and then expose any later
    // ApplicationData bytes to the MTProto packet parser.
    let mut sizes = {
        let base_sizes = emulated_app_data_sizes(cached);
        match cached.behavior_profile.source {
            TlsProfileSource::Raw | TlsProfileSource::Merged => base_sizes
                .into_iter()
                .map(|size| size.clamp(MIN_APP_DATA, MAX_APP_DATA))
                .collect(),
            TlsProfileSource::Default | TlsProfileSource::Rustls => {
                jitter_and_clamp_sizes(&base_sizes, rng)
            }
        }
    };
    let compact_payload = if serverhello_compact {
        cached
            .cert_info
            .as_ref()
            .and_then(build_compact_cert_info_payload)
            .and_then(hash_compact_cert_info_payload)
    } else {
        None
    };
    let full_payload = cached
        .cert_payload
        .as_ref()
        .map(|payload| payload.certificate_message.as_slice())
        .filter(|payload| !payload.is_empty());
    let selected_payload: Option<&[u8]> = match client_tls_version {
        ClientHelloTlsVersion::Tls13 => None,
        ClientHelloTlsVersion::Tls12 => {
            if serverhello_compact {
                if use_full_cert_payload {
                    full_payload.or(compact_payload.as_deref())
                } else {
                    compact_payload.as_deref()
                }
            } else {
                full_payload
            }
        }
    };

    if let Some(payload) = selected_payload {
        sizes = ensure_payload_capacity(sizes, payload.len());
    }

    let mut app_data = Vec::new();
    // ALPN selection is encrypted inside EncryptedExtensions in real TLS 1.3.
    // Keeping the FakeTLS record body opaque avoids a stable plaintext marker.
    let _ = alpn;
    let mut payload_offset = 0usize;
    for size in sizes {
        let mut rec = Vec::with_capacity(5 + size);
        rec.push(TLS_RECORD_APPLICATION);
        rec.extend_from_slice(&TLS_VERSION);
        rec.extend_from_slice(&(size as u16).to_be_bytes());

        if let Some(payload) = selected_payload {
            if size > 17 {
                let body_len = size - 17;
                let remaining = payload.len().saturating_sub(payload_offset);
                let copy_len = remaining.min(body_len);
                if copy_len > 0 {
                    rec.extend_from_slice(&payload[payload_offset..payload_offset + copy_len]);
                    payload_offset += copy_len;
                }
                if body_len > copy_len {
                    rec.extend_from_slice(&rng.bytes(body_len - copy_len));
                }
                rec.push(0x16);
                rec.extend_from_slice(&rng.bytes(16));
            } else {
                rec.extend_from_slice(&rng.bytes(size));
            }
        } else if size > 17 {
            let body_len = size - 17;
            let mut body = Vec::with_capacity(body_len);
            body.extend_from_slice(&rng.bytes(body_len));
            rec.extend_from_slice(&body);
            rec.push(0x16);
            rec.extend_from_slice(&rng.bytes(16));
        } else {
            rec.extend_from_slice(&rng.bytes(size));
        }
        app_data.extend_from_slice(&rec);
    }

    // Optional NewSessionTicket mimic records are an explicit fingerprint opt-in.
    let mut tickets = Vec::new();
    for ticket_len in emulated_ticket_record_sizes(cached, new_session_tickets, rng) {
        let mut rec = Vec::with_capacity(5 + ticket_len);
        rec.push(TLS_RECORD_APPLICATION);
        rec.extend_from_slice(&TLS_VERSION);
        rec.extend_from_slice(&(ticket_len as u16).to_be_bytes());
        rec.extend_from_slice(&rng.bytes(ticket_len));
        tickets.extend_from_slice(&rec);
    }

    let mut response = Vec::with_capacity(
        server_hello.len() + change_cipher_spec.len() + app_data.len() + tickets.len(),
    );
    response.extend_from_slice(&server_hello);
    response.extend_from_slice(&change_cipher_spec);
    response.extend_from_slice(&app_data);
    response.extend_from_slice(&tickets);

    // The digest authenticates the server response bytes emitted by this builder.
    let mut hmac_input = Vec::with_capacity(TLS_DIGEST_LEN + response.len());
    hmac_input.extend_from_slice(client_digest);
    hmac_input.extend_from_slice(&response);
    let digest = sha256_hmac(secret, &hmac_input);
    response[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].copy_from_slice(&digest);

    response
}

#[cfg(test)]
#[path = "tests/emulator_security_tests.rs"]
mod security_tests;

#[cfg(test)]
#[path = "tests/emulator_profile_fidelity_security_tests.rs"]
mod emulator_profile_fidelity_security_tests;

#[cfg(test)]
mod tests {
    use std::time::SystemTime;

    use crate::tls_front::types::{
        CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsCertPayload, TlsExtension,
        TlsProfileSource,
    };

    use super::{
        build_compact_cert_info_payload, build_emulated_server_hello,
        hash_compact_cert_info_payload, profiled_server_hello_key_share_group,
    };
    use crate::crypto::SecureRandom;
    use crate::protocol::constants::{
        TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE,
    };
    use crate::protocol::tls::{
        ClientHelloTlsVersion, ServerHelloKeyShare, TLS_NAMED_GROUP_X25519,
        TLS_NAMED_GROUP_X25519MLKEM768,
    };

    fn first_app_data_payload(response: &[u8]) -> &[u8] {
        let hello_len = u16::from_be_bytes([response[3], response[4]]) as usize;
        let ccs_start = 5 + hello_len;
        let ccs_len =
            u16::from_be_bytes([response[ccs_start + 3], response[ccs_start + 4]]) as usize;
        let app_start = ccs_start + 5 + ccs_len;
        let app_len =
            u16::from_be_bytes([response[app_start + 3], response[app_start + 4]]) as usize;
        &response[app_start + 5..app_start + 5 + app_len]
    }

    fn server_hello_cipher_suite(response: &[u8]) -> [u8; 2] {
        let mut pos = 5 + 4 + 2 + 32;
        let session_id_len = response[pos] as usize;
        pos += 1 + session_id_len;
        [response[pos], response[pos + 1]]
    }

    fn server_hello_extension_types(response: &[u8]) -> Vec<u16> {
        let record_len = u16::from_be_bytes([response[3], response[4]]) as usize;
        let handshake_end = 5 + record_len;
        let mut pos = 5 + 4 + 2 + 32;
        let session_id_len = response[pos] as usize;
        pos += 1 + session_id_len + 2 + 1;
        let extensions_len = u16::from_be_bytes([response[pos], response[pos + 1]]) as usize;
        pos += 2;
        let extensions_end = (pos + extensions_len).min(handshake_end);
        let mut out = Vec::new();

        while pos + 4 <= extensions_end {
            let ext_type = u16::from_be_bytes([response[pos], response[pos + 1]]);
            let ext_len = u16::from_be_bytes([response[pos + 2], response[pos + 3]]) as usize;
            pos += 4;
            if pos + ext_len > extensions_end {
                break;
            }
            out.push(ext_type);
            pos += ext_len;
        }

        out
    }

    fn make_cached(cert_payload: Option<TlsCertPayload>) -> CachedTlsData {
        CachedTlsData {
            server_hello_template: ParsedServerHello {
                version: [0x03, 0x03],
                random: [0u8; 32],
                session_id: Vec::new(),
                cipher_suite: [0x13, 0x01],
                compression: 0,
                extensions: Vec::new(),
            },
            cert_info: None,
            cert_payload,
            app_data_records_sizes: vec![64],
            total_app_data_len: 64,
            behavior_profile: TlsBehaviorProfile::default(),
            fetched_at: SystemTime::now(),
            domain: "example.com".to_string(),
        }
    }

    fn test_server_key_share() -> ServerHelloKeyShare {
        ServerHelloKeyShare::new(TLS_NAMED_GROUP_X25519MLKEM768, vec![0x42; 1120])
    }

    fn server_key_share_extension_data(group: u16, len: usize) -> Vec<u8> {
        let mut data = Vec::new();
        data.extend_from_slice(&group.to_be_bytes());
        data.extend_from_slice(&(len as u16).to_be_bytes());
        data.resize(4 + len, 0x42);
        data
    }

    #[test]
    fn profiled_server_hello_key_share_group_reads_raw_x25519_profile() {
        let mut cached = make_cached(None);
        cached.behavior_profile.source = TlsProfileSource::Raw;
        cached.server_hello_template.extensions = vec![
            TlsExtension {
                ext_type: 0x002b,
                data: vec![0x03, 0x04],
            },
            TlsExtension {
                ext_type: 0x0033,
                data: server_key_share_extension_data(TLS_NAMED_GROUP_X25519, 32),
            },
        ];

        assert_eq!(
            profiled_server_hello_key_share_group(&cached),
            Some(TLS_NAMED_GROUP_X25519)
        );
    }

    #[test]
    fn profiled_server_hello_key_share_group_ignores_default_profile() {
        let mut cached = make_cached(None);
        cached.server_hello_template.extensions = vec![TlsExtension {
            ext_type: 0x0033,
            data: server_key_share_extension_data(TLS_NAMED_GROUP_X25519, 32),
        }];

        assert_eq!(profiled_server_hello_key_share_group(&cached), None);
    }

    #[test]
    fn test_build_emulated_server_hello_uses_cached_cert_payload() {
        let cert_msg = vec![0x0b, 0x00, 0x00, 0x05, 0x00, 0xaa, 0xbb, 0xcc, 0xdd];
        let cached = make_cached(Some(TlsCertPayload {
            cert_chain_der: vec![vec![0x30, 0x01, 0x00]],
            certificate_message: cert_msg.clone(),
        }));
        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x11; 32],
            &[0x22; 16],
            &cached,
            true,
            true,
            ClientHelloTlsVersion::Tls12,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        assert_eq!(response[0], TLS_RECORD_HANDSHAKE);
        let hello_len = u16::from_be_bytes([response[3], response[4]]) as usize;
        let ccs_start = 5 + hello_len;
        assert_eq!(response[ccs_start], TLS_RECORD_CHANGE_CIPHER);
        let app_start = ccs_start + 6;
        assert_eq!(response[app_start], TLS_RECORD_APPLICATION);

        let payload = first_app_data_payload(&response);
        assert!(payload.starts_with(&cert_msg));
    }

    #[test]
    fn test_build_emulated_server_hello_uses_selected_cipher_suite() {
        let cached = make_cached(None);
        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x10; 32],
            &[0x20; 16],
            &cached,
            false,
            true,
            ClientHelloTlsVersion::Tls13,
            [0x13, 0x03],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        assert_eq!(server_hello_cipher_suite(&response), [0x13, 0x03]);
    }

    #[test]
    fn test_build_emulated_server_hello_replays_profiled_safe_extension_order() {
        let mut cached = make_cached(None);
        cached.server_hello_template.extensions = vec![
            TlsExtension {
                ext_type: 0x002b,
                data: vec![0x03, 0x04],
            },
            TlsExtension {
                ext_type: 0x0010,
                data: vec![0x00, 0x03, 0x02, b'h', b'2'],
            },
            TlsExtension {
                ext_type: 0x0033,
                data: vec![0; 36],
            },
        ];
        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x21; 32],
            &[0x22; 16],
            &cached,
            false,
            true,
            ClientHelloTlsVersion::Tls13,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            Some(b"h2".to_vec()),
            0,
        );

        assert_eq!(
            server_hello_extension_types(&response),
            vec![0x002b, 0x0033]
        );
    }

    #[test]
    fn test_build_emulated_server_hello_replays_safe_raw_extension_order() {
        let mut cached = make_cached(None);
        cached.behavior_profile.source = TlsProfileSource::Raw;
        cached.server_hello_template.extensions = vec![
            TlsExtension {
                ext_type: 0x0033,
                data: server_key_share_extension_data(TLS_NAMED_GROUP_X25519, 32),
            },
            TlsExtension {
                ext_type: 0x002b,
                data: vec![0x03, 0x04],
            },
        ];
        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x21; 32],
            &[0x22; 16],
            &cached,
            false,
            true,
            ClientHelloTlsVersion::Tls13,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        assert_eq!(
            server_hello_extension_types(&response),
            vec![0x0033, 0x002b]
        );
    }

    #[test]
    fn test_build_emulated_server_hello_uses_canonical_order_for_unsafe_raw_shape() {
        let mut cached = make_cached(None);
        cached.behavior_profile.source = TlsProfileSource::Raw;
        cached.server_hello_template.extensions = vec![
            TlsExtension {
                ext_type: 0x0010,
                data: vec![0x00, 0x03, 0x02, b'h', b'2'],
            },
            TlsExtension {
                ext_type: 0x0033,
                data: server_key_share_extension_data(TLS_NAMED_GROUP_X25519, 32),
            },
            TlsExtension {
                ext_type: 0x002b,
                data: vec![0x03, 0x04],
            },
        ];
        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x21; 32],
            &[0x22; 16],
            &cached,
            false,
            true,
            ClientHelloTlsVersion::Tls13,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        assert_eq!(
            server_hello_extension_types(&response),
            vec![0x002b, 0x0033]
        );
    }

    #[test]
    fn test_build_emulated_server_hello_random_fallback_when_no_cert_payload() {
        let cached = make_cached(None);
        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x22; 32],
            &[0x33; 16],
            &cached,
            true,
            true,
            ClientHelloTlsVersion::Tls12,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        let payload = first_app_data_payload(&response);
        assert!(payload.len() >= 64);
        assert_eq!(payload[payload.len() - 17], 0x16);
    }

    #[test]
    fn test_build_emulated_server_hello_uses_compact_payload_after_first() {
        let cert_msg = vec![0x0b, 0x00, 0x00, 0x05, 0x00, 0xaa, 0xbb, 0xcc, 0xdd];
        let mut cached = make_cached(Some(TlsCertPayload {
            cert_chain_der: vec![vec![0x30, 0x01, 0x00]],
            certificate_message: cert_msg,
        }));
        cached.cert_info = Some(crate::tls_front::types::ParsedCertificateInfo {
            not_after_unix: Some(1_900_000_000),
            not_before_unix: Some(1_700_000_000),
            issuer_cn: Some("Issuer".to_string()),
            subject_cn: Some("example.com".to_string()),
            san_names: vec!["example.com".to_string(), "www.example.com".to_string()],
        });

        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x44; 32],
            &[0x55; 16],
            &cached,
            false,
            true,
            ClientHelloTlsVersion::Tls12,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        let payload = first_app_data_payload(&response);
        let expected_hashed_payload = build_compact_cert_info_payload(
            cached
                .cert_info
                .as_ref()
                .expect("test fixture must provide certificate info"),
        )
        .and_then(hash_compact_cert_info_payload)
        .expect("compact certificate info payload must be present for this test");
        let copied_prefix_len = expected_hashed_payload
            .len()
            .min(payload.len().saturating_sub(17));
        assert_eq!(
            &payload[..copied_prefix_len],
            &expected_hashed_payload[..copied_prefix_len]
        );
    }

    #[test]
    fn test_build_emulated_server_hello_tls13_never_uses_cert_payload() {
        let cert_msg = vec![0x0b, 0x00, 0x00, 0x05, 0x00, 0xaa, 0xbb, 0xcc, 0xdd];
        let cached = make_cached(Some(TlsCertPayload {
            cert_chain_der: vec![vec![0x30, 0x01, 0x00]],
            certificate_message: cert_msg.clone(),
        }));

        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x56; 32],
            &[0x78; 16],
            &cached,
            true,
            true,
            ClientHelloTlsVersion::Tls13,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        let payload = first_app_data_payload(&response);
        assert!(
            !payload.starts_with(&cert_msg),
            "TLS 1.3 response path must not expose certificate payload bytes"
        );
    }

    #[test]
    fn test_build_emulated_server_hello_keeps_alpn_marker_out_of_random_payload() {
        let mut cached = make_cached(None);
        cached.cert_info = Some(crate::tls_front::types::ParsedCertificateInfo {
            not_after_unix: Some(1_900_000_000),
            not_before_unix: Some(1_700_000_000),
            issuer_cn: Some("Issuer".to_string()),
            subject_cn: Some("example.com".to_string()),
            san_names: vec!["example.com".to_string()],
        });

        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x90; 32],
            &[0x91; 16],
            &cached,
            false,
            false,
            ClientHelloTlsVersion::Tls12,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            Some(b"h2".to_vec()),
            0,
        );

        let payload = first_app_data_payload(&response);
        let expected_alpn_marker = [0x00u8, 0x10, 0x00, 0x05, 0x00, 0x03, 0x02, b'h', b'2'];
        assert!(
            !payload.starts_with(&expected_alpn_marker),
            "random fallback payload must not expose plaintext ALPN marker bytes"
        );
    }

    #[test]
    fn test_build_emulated_server_hello_ignores_tail_records_for_profiled_tls() {
        let mut cached = make_cached(None);
        cached.app_data_records_sizes = vec![27, 3905, 537, 69];
        cached.total_app_data_len = 4538;
        cached.behavior_profile.source = TlsProfileSource::Merged;
        cached.behavior_profile.app_data_record_sizes = vec![27, 3905, 537];
        cached.behavior_profile.ticket_record_sizes = vec![69];

        let rng = SecureRandom::new();
        let response = build_emulated_server_hello(
            b"secret",
            &[0x12; 32],
            &[0x34; 16],
            &cached,
            false,
            true,
            ClientHelloTlsVersion::Tls13,
            [0x13, 0x01],
            &test_server_key_share(),
            &rng,
            None,
            0,
        );

        let hello_len = u16::from_be_bytes([response[3], response[4]]) as usize;
        let ccs_start = 5 + hello_len;
        let mut pos = ccs_start + 6;
        let mut app_lens = Vec::new();
        while pos + 5 <= response.len() {
            let record_len = u16::from_be_bytes([response[pos + 3], response[pos + 4]]) as usize;
            assert_eq!(response[pos], TLS_RECORD_APPLICATION);
            app_lens.push(record_len);
            pos += 5 + record_len;
        }
        assert_eq!(app_lens, vec![64]);
        assert_eq!(pos, response.len());
    }
}
