//! Acceptance tests for ADR-120 §2.4 — `PrivacyGate::demote` monotonic class
//! transitions and payload-section zeroization.

#![cfg(feature = "std")]

use wifi_densepose_bfld::{
    BfldError, BfldFrame, BfldFrameHeader, BfldPayload, PrivacyClass, PrivacyGate,
};

fn frame_at_class(class: PrivacyClass, with_csi: bool) -> BfldFrame {
    let payload = BfldPayload {
        compressed_angle_matrix: vec![0x11; 32],
        amplitude_proxy: vec![0x22; 16],
        phase_proxy: vec![0x33; 16],
        snr_vector: vec![0x44; 8],
        csi_delta: if with_csi { Some(vec![0x55; 24]) } else { None },
        vendor_extension: vec![0xAA],
    };
    let mut header = BfldFrameHeader::empty();
    header.privacy_class = class.as_u8();
    BfldFrame::from_payload(header, &payload)
}

#[test]
fn demote_to_same_class_is_identity() {
    let f = frame_at_class(PrivacyClass::Derived, false);
    let out = PrivacyGate::demote(f, PrivacyClass::Derived).expect("same-class demote OK");
    assert_eq!({ out.header.privacy_class }, PrivacyClass::Derived.as_u8());
}

#[test]
fn demote_derived_to_anonymous_strips_compressed_angle_matrix() {
    let f = frame_at_class(PrivacyClass::Derived, true);
    let out = PrivacyGate::demote(f, PrivacyClass::Anonymous).expect("demote");
    assert_eq!({ out.header.privacy_class }, PrivacyClass::Anonymous.as_u8());

    let payload = out.parse_payload().expect("payload still parses");
    assert!(
        payload.compressed_angle_matrix.is_empty(),
        "angle matrix must be stripped at class 2",
    );
    // CSI delta also dropped at Anonymous.
    assert!(payload.csi_delta.is_none(), "csi_delta dropped at class 2");
    // Sensing sections preserved.
    assert_eq!(payload.snr_vector.len(), 8);
    assert_eq!(payload.amplitude_proxy.len(), 16);
}

#[test]
fn demote_derived_to_restricted_strips_amplitude_and_phase_too() {
    let f = frame_at_class(PrivacyClass::Derived, true);
    let out = PrivacyGate::demote(f, PrivacyClass::Restricted).expect("demote");
    assert_eq!({ out.header.privacy_class }, PrivacyClass::Restricted.as_u8());

    let payload = out.parse_payload().expect("payload parses");
    assert!(payload.compressed_angle_matrix.is_empty());
    assert!(payload.amplitude_proxy.is_empty(), "amplitude stripped at class 3");
    assert!(payload.phase_proxy.is_empty(), "phase stripped at class 3");
    // SNR + vendor still survive.
    assert_eq!(payload.snr_vector.len(), 8);
    assert_eq!(payload.vendor_extension.len(), 1);
}

#[test]
fn demote_anonymous_to_derived_is_rejected() {
    let f = frame_at_class(PrivacyClass::Anonymous, false);
    match PrivacyGate::demote(f, PrivacyClass::Derived) {
        Err(BfldError::InvalidDemote { from, to }) => {
            assert_eq!(from, PrivacyClass::Anonymous.as_u8());
            assert_eq!(to, PrivacyClass::Derived.as_u8());
        }
        other => panic!("expected InvalidDemote, got {other:?}"),
    }
}

#[test]
fn demote_to_raw_is_rejected_from_any_higher_class() {
    for src in [
        PrivacyClass::Derived,
        PrivacyClass::Anonymous,
        PrivacyClass::Restricted,
    ] {
        let f = frame_at_class(src, false);
        match PrivacyGate::demote(f, PrivacyClass::Raw) {
            Err(BfldError::InvalidDemote { .. }) => {}
            other => panic!("expected InvalidDemote from {src:?}, got {other:?}"),
        }
    }
}

#[test]
fn demote_preserves_frame_crc_consistency_through_wire_roundtrip() {
    // Demote produces a frame; that frame must round-trip through bytes
    // with no CRC error.
    let f = frame_at_class(PrivacyClass::Derived, true);
    let demoted = PrivacyGate::demote(f, PrivacyClass::Anonymous).expect("demote");
    let bytes = demoted.to_bytes();
    let parsed = BfldFrame::from_bytes(&bytes).expect("post-demote frame must round-trip");
    assert_eq!({ parsed.header.privacy_class }, PrivacyClass::Anonymous.as_u8());
}

#[test]
fn demote_clears_has_csi_delta_flag_bit() {
    use wifi_densepose_bfld::frame::flags;
    let f = frame_at_class(PrivacyClass::Derived, true);
    assert_ne!({ f.header.flags } & flags::HAS_CSI_DELTA, 0);

    let out = PrivacyGate::demote(f, PrivacyClass::Anonymous).expect("demote");
    assert_eq!(
        { out.header.flags } & flags::HAS_CSI_DELTA,
        0,
        "HAS_CSI_DELTA must clear when csi_delta is stripped",
    );
}
