//! Robustness/fuzz tests for protocol deserializers (§8.2).
//!
//! Targets every parser an instance or peer could feed us bytes for:
//!   * `Pack::decode` — reachable via federation push and P2P deploy.
//!   * `p2p::read_frame` — every byte from a dialing peer comes through here.
//!   * The federation JSON wire types (`PushManifest`, `InfoResponse`,
//!     `RefList`, `RefsResponse`).
//!   * The P2P `DeployManifest`.
//!
//! All of these must accept malformed input without panicking.

mod support;

use std::io::Cursor;
use std::panic::{catch_unwind, AssertUnwindSafe};

use levcs_protocol::p2p::{read_frame, DeployManifest};
use levcs_protocol::v2::{
    CursorExpiredV1, MissingObjectsRequestV1, ProjectionStageChunkV1, ProjectionStageManifestV1,
    ProjectionStageSessionV1, SignedClientOperationV2, SignedReadRequestV2, SignedRepoSnapshotV1,
    TransactionEvidenceV1, TransactionStatusV1,
};
use levcs_protocol::{CanonicalCodec, InfoResponse, Pack, PushManifest, RefList, RefsResponse};

const ITERS: u32 = 5_000;

fn lcg(state: &mut u64) -> u64 {
    *state = state
        .wrapping_mul(6364136223846793005)
        .wrapping_add(1442695040888963407);
    *state
}

fn rand_bytes(seed: &mut u64, n: usize) -> Vec<u8> {
    (0..n).map(|_| (lcg(seed) >> 33) as u8).collect()
}

fn rand_size(seed: &mut u64) -> usize {
    match lcg(seed) % 5 {
        0 => 0,
        1 => (lcg(seed) % 16) as usize,
        2 => (lcg(seed) % 256) as usize,
        3 => 256 + (lcg(seed) % 4096) as usize,
        _ => 4096 + (lcg(seed) % 16384) as usize,
    }
}

fn assert_no_panic<F, T>(label: &str, seed: u64, input: &[u8], parser: F)
where
    F: FnOnce(&[u8]) -> T,
{
    let r = catch_unwind(AssertUnwindSafe(|| parser(input)));
    if r.is_err() {
        panic!(
            "{label} panicked on seed {seed:#x}, input len {}: {:02x?}",
            input.len(),
            &input[..input.len().min(64)]
        );
    }
}

#[test]
fn pack_decode_does_not_panic_on_random_bytes() {
    let mut seed = 0x6677_8899_aabb_ccddu64;
    for _ in 0..ITERS {
        let n = rand_size(&mut seed);
        let bytes = rand_bytes(&mut seed, n);
        assert_no_panic("Pack::decode", seed, &bytes, |b| {
            let _ = Pack::decode(b);
        });
        assert_no_panic("Pack::decode_prefix", seed, &bytes, |b| {
            let _ = Pack::decode_prefix(b);
        });
    }
}

/// Pack decoder does internal arithmetic over object_count and entry
/// sizes — both could overflow on hostile inputs. Build packs with a
/// valid magic + extreme `object_count` and verify the decoder rejects
/// gracefully without trying to allocate `count * struct_size` bytes.
#[test]
fn pack_decode_handles_extreme_object_counts() {
    let mut seed = 0xc0ffee_aaaa_5555u64;
    for _ in 0..512 {
        let mut bytes = b"LVPK".to_vec();
        bytes.extend_from_slice(&1u32.to_le_bytes()); // version
        let count = match lcg(&mut seed) % 4 {
            0 => u64::MAX,
            1 => u64::MAX / 2,
            2 => 1u64 << 50,
            _ => lcg(&mut seed),
        };
        bytes.extend_from_slice(&count.to_le_bytes());
        // Append a small amount of random body — far less than `count`
        // entries' worth, so the decoder must report "truncated", not panic.
        let pad = (lcg(&mut seed) % 256) as usize;
        bytes.extend(rand_bytes(&mut seed, pad));
        assert_no_panic("Pack::decode(huge count)", seed, &bytes, |b| {
            let _ = Pack::decode(b);
        });
    }
}

#[test]
fn p2p_read_frame_does_not_panic_on_random_bytes() {
    let mut seed = 0x1212_3434_5656_7878u64;
    for _ in 0..ITERS {
        let n = rand_size(&mut seed);
        let bytes = rand_bytes(&mut seed, n);
        let r = catch_unwind(AssertUnwindSafe(|| {
            let mut cur = Cursor::new(&bytes);
            let _ = read_frame(&mut cur);
        }));
        if r.is_err() {
            panic!(
                "read_frame panicked on seed {seed:#x}, input len {}: {:02x?}",
                bytes.len(),
                &bytes[..bytes.len().min(64)]
            );
        }
    }
}

/// Forge frame headers that claim plausible-looking lengths just shy of
/// the cap, then provide far less payload than promised. The reader must
/// return EOF/short-read errors, not panic.
#[test]
fn p2p_read_frame_handles_lying_length_prefixes() {
    use levcs_protocol::p2p::MAX_FRAME_BYTES;
    let mut seed = 0xfeed_face_1357_2468u64;
    for _ in 0..512 {
        let mut hdr = Vec::new();
        let claimed_len = match lcg(&mut seed) % 4 {
            0 => MAX_FRAME_BYTES as u32 + 1,
            1 => MAX_FRAME_BYTES as u32,
            2 => u32::MAX,
            _ => (lcg(&mut seed) & 0xffff_ffff) as u32,
        };
        hdr.extend_from_slice(&claimed_len.to_be_bytes());
        // Provide some random tail, far short of `claimed_len` bytes.
        let tail_len = (lcg(&mut seed) % 64) as usize;
        let tail = rand_bytes(&mut seed, tail_len);
        hdr.extend(tail);
        let r = catch_unwind(AssertUnwindSafe(|| {
            let mut cur = Cursor::new(&hdr);
            let _ = read_frame(&mut cur);
        }));
        if r.is_err() {
            panic!(
                "read_frame panicked on lying header (claimed_len={claimed_len}, seed {seed:#x}): {:02x?}",
                hdr
            );
        }
    }
}

#[test]
fn deploy_manifest_json_does_not_panic() {
    let mut seed = 0x9999_8888_7777_6666u64;
    for _ in 0..ITERS {
        let n = rand_size(&mut seed);
        let bytes = rand_bytes(&mut seed, n);
        assert_no_panic("DeployManifest::deserialize", seed, &bytes, |b| {
            let _: Result<DeployManifest, _> = serde_json::from_slice(b);
        });
    }
}

/// Build inputs that *look* like JSON (random ASCII with braces and
/// quotes) — closer to what a buggy or hostile peer might produce than
/// pure random bytes, and pushes the JSON parser into deeper code paths.
#[test]
fn json_wire_types_handle_pseudo_json_inputs() {
    let mut seed = 0x4444_5555_6666_7777u64;
    let pool: &[u8] = b"{}[],:\"\\nrue0123456789abcdefghijklmnopqrstuvwxyz_- ";
    for _ in 0..ITERS {
        let n = (lcg(&mut seed) % 4096) as usize;
        let bytes: Vec<u8> = (0..n)
            .map(|_| pool[(lcg(&mut seed) as usize) % pool.len()])
            .collect();
        assert_no_panic("PushManifest", seed, &bytes, |b| {
            let _: Result<PushManifest, _> = serde_json::from_slice(b);
        });
        assert_no_panic("InfoResponse", seed, &bytes, |b| {
            let _: Result<InfoResponse, _> = serde_json::from_slice(b);
        });
        assert_no_panic("RefList", seed, &bytes, |b| {
            let _: Result<RefList, _> = serde_json::from_slice(b);
        });
        assert_no_panic("RefsResponse", seed, &bytes, |b| {
            let _: Result<RefsResponse, _> = serde_json::from_slice(b);
        });
        assert_no_panic("DeployManifest(pseudo)", seed, &bytes, |b| {
            let _: Result<DeployManifest, _> = serde_json::from_slice(b);
        });
    }
}

/// A pack assembled from valid object bytes that the encoder picks the
/// delta path on. Then we mutate that valid pack and re-decode — the
/// decoder must refuse mutated bytes, never panic.
#[test]
fn pack_decode_survives_mutation_of_valid_pack() {
    // Construct a tiny valid pack so we have something to mutate.
    let mut pk = Pack::new();
    pk.push(1, b"hello world fuzz".to_vec());
    pk.push(1, b"hello world fuzz, slight variant".to_vec());
    let valid = pk.encode();

    let mut seed = 0x7777_8888_9999_aaaau64;
    for _ in 0..ITERS {
        let mut buf = valid.clone();
        match lcg(&mut seed) % 4 {
            0 => {
                let flips = (lcg(&mut seed) % 4 + 1) as usize;
                for _ in 0..flips {
                    if buf.is_empty() {
                        break;
                    }
                    let idx = (lcg(&mut seed) as usize) % buf.len();
                    let bit = (lcg(&mut seed) % 8) as u8;
                    buf[idx] ^= 1 << bit;
                }
            }
            1 => {
                let new_len = (lcg(&mut seed) as usize) % buf.len().max(1);
                buf.truncate(new_len);
            }
            2 => {
                // Stomp on the entry-count field (bytes 8..16).
                let off = 8 + (lcg(&mut seed) as usize) % 8;
                buf[off] = buf[off].wrapping_add((lcg(&mut seed) & 0xff) as u8);
            }
            _ => {
                let extra = (lcg(&mut seed) % 64) as usize;
                buf.extend(rand_bytes(&mut seed, extra));
            }
        }
        assert_no_panic("Pack::decode(mut)", seed, &buf, |b| {
            let _ = Pack::decode(b);
        });
    }
}

#[test]
fn every_phase0_v2_decoder_rejects_hostile_bytes_without_panicking() {
    let mut seed = 0x20_26_07_24_f0_f0_f0_f0u64;
    for _ in 0..ITERS {
        let n = rand_size(&mut seed).min(16 * 1024);
        let bytes = rand_bytes(&mut seed, n);
        assert_no_panic("SignedClientOperationV2", seed, &bytes, |b| {
            let _ = SignedClientOperationV2::decode_canonical(b);
        });
        assert_no_panic("SignedReadRequestV2", seed, &bytes, |b| {
            let _ = SignedReadRequestV2::decode_canonical(b);
        });
        assert_no_panic("MissingObjectsRequestV1", seed, &bytes, |b| {
            let _ = MissingObjectsRequestV1::decode_canonical(b);
        });
        assert_no_panic("SignedRepoSnapshotV1", seed, &bytes, |b| {
            let _ = SignedRepoSnapshotV1::decode_canonical(b);
        });
        assert_no_panic("TransactionEvidenceV1", seed, &bytes, |b| {
            let _ = TransactionEvidenceV1::decode_canonical(b);
        });
        assert_no_panic("TransactionStatusV1", seed, &bytes, |b| {
            let _ = TransactionStatusV1::decode_canonical(b);
        });
        assert_no_panic("CursorExpiredV1", seed, &bytes, |b| {
            let _ = CursorExpiredV1::decode_canonical(b);
        });
        assert_no_panic("ProjectionStageSessionV1", seed, &bytes, |b| {
            let _ = ProjectionStageSessionV1::decode_canonical(b);
        });
        assert_no_panic("ProjectionStageChunkV1", seed, &bytes, |b| {
            let _ = ProjectionStageChunkV1::decode_canonical(b);
        });
        assert_no_panic("ProjectionStageManifestV1", seed, &bytes, |b| {
            let _ = ProjectionStageManifestV1::decode_canonical(b);
        });
    }
}

#[test]
fn valid_v2_envelope_mutations_never_panic_or_verify_as_original() {
    let valid = SignedClientOperationV2::Push(support::signed_normal_push())
        .encode_canonical()
        .unwrap();
    let mut seed = 0x55_aa_55_aa_20_26_07_24u64;
    for _ in 0..ITERS {
        let mut bytes = valid.clone();
        match lcg(&mut seed) % 3 {
            0 => {
                let index = (lcg(&mut seed) as usize) % bytes.len();
                bytes[index] ^= 1 << (lcg(&mut seed) % 8);
            }
            1 => {
                let length = (lcg(&mut seed) as usize) % bytes.len();
                bytes.truncate(length);
            }
            _ => {
                let extra = (lcg(&mut seed) % 32) as usize;
                bytes.extend(rand_bytes(&mut seed, extra));
            }
        }
        let is_real_mutation = bytes != valid;
        assert_no_panic("mutated v2 envelope", seed, &bytes, |b| {
            if let Ok(decoded) = SignedClientOperationV2::decode_canonical(b) {
                let verified = decoded.verify().is_ok();
                assert!(
                    !(is_real_mutation && verified),
                    "mutated bytes decoded and verified as valid at seed {seed:#x}"
                );
            }
        });
    }
}
