use super::*;
use crate::energy_class::{CounterFiltering, Uncertainty};
use ed25519_dalek::SigningKey;

fn key(s: u8) -> SigningKey {
    SigningKey::from_bytes(&[s; 32])
}

fn chip(ppm: u32) -> EnergyProvenance {
    EnergyProvenance::OnChipCounter { uncertainty: Uncertainty { relative_ppm: ppm, window_us: 2_977_104 }, filtering: CounterFiltering::Unknown }
}

/// The registry is the set of identity domains the code hashes under, less their
/// version byte: one name each, no name twice.
#[test]
fn the_table_is_the_identity_domains_the_code_hashes_under() {
    let domains: &[&[u8]] = &[
        b"wai:video-receipt-id\x01",
        b"wai:video-manifest-receipt-id\x01",
        b"wai:video-ssai-receipt-id\x01",
        b"wai:video-keys-receipt-id\x01",
        b"wai:video-package-receipt-id\x01",
        b"wai:video-channel-receipt-id\x01",
        b"wai:world-receipt-id\x01",
        b"wai:film-receipt-id\x01",
        b"wai:haptic-receipt-id\x01",
        b"wai:score-receipt-id\x01",
        b"wai:splat4d-receipt-id\x01",
        b"wai:audio-receipt-id\x01",
        b"wai:ternary-receipt-id\x01",
        b"wai:asset-receipt-id\x01",
        b"wai:energy-binding-id\x01",
        b"moq-jwp:claim-link\x01",
        b"moq-jwp:object\x01",
    ];
    for d in domains {
        assert!(profile_name_of_domain(d).is_some(), "{}", String::from_utf8_lossy(d));
    }
    let mut names: Vec<&str> = profiles().iter().map(|p| p.name).collect();
    names.sort();
    names.dedup();
    assert_eq!(names.len(), profiles().len());
    assert!(profile_name_of_domain(b"wai:phasor-compute-id\x01").is_none(), "a domain that identifies no figure");
    // Exactly one profile names a group, and only the energy binding has no signer.
    assert_eq!(profiles().iter().filter(|p| p.grouped).map(|p| p.name).collect::<Vec<_>>(), ["moq-jwp:object"]);
    assert_eq!(profiles().iter().filter(|p| !p.ed25519_signer).map(|p| p.name).collect::<Vec<_>>(), ["wai:energy-binding-id"]);
}

#[test]
fn an_energy_claim_is_viewed_by_its_link_hash() {
    use crate::claim_receipt::{EnergyClaim, SubjectType};
    use crate::energy_binding::Coverage;
    let c = ClaimReceipt::seal(
        &key(0x22),
        "m",
        SubjectType::Object,
        [1; 32],
        0,
        "2026-09-30T12:00:00Z",
        ClaimBody::Energy(EnergyClaim::new(500, &chip(20_000), Coverage::Whole, "measured", "https://example.org/m", 1, "u", None)),
        None,
    )
    .unwrap();
    let v = c.figure_view().unwrap();
    assert_eq!(v.profile, "moq-jwp:claim-link");
    assert_eq!(v.identity, c.link_hash());
    assert_eq!(v.figures, vec![Figure { index: None, joules_micro: 500, class: Some(chip(20_000)) }]);
    assert_eq!(v.signer, Some(key(0x22).verifying_key().to_bytes()));
    let mut bad = c.clone();
    bad.sig[0] ^= 1;
    assert!(bad.figure_view().is_none(), "a claim that does not verify shows no figure");
}

#[test]
fn a_binding_is_viewed_by_its_identity_and_has_no_signer() {
    let body: serde_json::Value = serde_json::from_str(
        r#"{"reconstructionHash":"4dbdc2b2b62cb00749785bc84202236dbc3777d74660611b8e58812f0cfde6c3","alg":"sha256","microjoules":10565,"acquisition":"ModelBased","coverage":"Whole","workUnits":48,"method":"https://transaction.science/wai/extensions/energy-measurement"}"#,
    )
    .unwrap();
    let v = BindingBody(&body).figure_view().unwrap();
    assert_eq!(v.profile, "wai:energy-binding-id");
    let mut h = blake3::Hasher::new();
    h.update(b"wai:energy-binding-id\x01");
    h.update(crate::claim_receipt::jcs(&body).unwrap().as_bytes());
    assert_eq!(v.identity, *h.finalize().as_bytes());
    assert_eq!(v.signer, None);
    assert_eq!(v.figures[0].class, Some(EnergyProvenance::ModelBased));
    // A body holding an integer no canonical form holds has no identity.
    let mut big = body.clone();
    big["workUnits"] = serde_json::Value::from(1u64 << 53);
    assert!(binding_identity(&big).is_none());
}

#[test]
fn a_delivered_object_is_viewed_at_its_index_within_its_group() {
    use crate::jwp_delivery::{GroupBuilder, ObjectLabel};
    let env = |i: u8| vec![i; 9];
    let mut b = GroupBuilder::default();
    b.add(crate::jwp_hash::content_hash(&env(0)), 300, "o", Some(ObjectLabel::Class(chip(20_000))));
    b.add(crate::jwp_hash::content_hash(&env(1)), 200, "o", None);
    let (g, objs) = b.finish(&key(0x11), "p", 1, 2, "2026-09-30T12:00:00Z", "2026-09-30T12:00:01Z", None, false).unwrap();
    let v = DeliveredObject { object: &objs[1], group: &g, envelope: &env(1) }.figure_view().unwrap();
    assert_eq!(v.profile, "moq-jwp:object");
    assert_eq!(v.identity, objs[1].content_hash);
    assert_eq!(v.group, Some(g.link_hash()));
    assert_eq!(v.figures, vec![Figure { index: Some(1), joules_micro: 200, class: None }]);
    let v = DeliveredObject { object: &objs[0], group: &g, envelope: &env(0) }.figure_view().unwrap();
    assert_eq!(v.figures[0].class, Some(chip(20_000)));
    assert!(DeliveredObject { object: &objs[0], group: &g, envelope: &env(9) }.figure_view().is_none());
}

#[cfg(feature = "world")]
#[test]
fn a_world_session_is_viewed_by_its_receipt_hash() {
    use crate::world::{receipt::SessionReceipt, Checkpoint};
    let cps = vec![Checkpoint { tick: 100, hash: [0x11; 32] }];
    let r = SessionReceipt::build(&key(9), "did:key:pin", [0xab; 32], 3, &[], &cps, 12_345, 999, None);
    let v = r.figure_view().unwrap();
    assert_eq!((v.profile.as_str(), v.identity), ("wai:world-receipt-id", r.receipt_hash()));
    assert_eq!(v.figures[0], Figure { index: None, joules_micro: 12_345, class: None });
    let l = r.with_energy_class(&key(9), chip(15_403)).unwrap();
    let v = l.figure_view().unwrap();
    assert_eq!((v.identity, v.figures[0].class.clone()), (l.receipt_hash(), Some(chip(15_403))));
    let mut bad = l.clone();
    bad.sig[0] ^= 1;
    assert!(bad.figure_view().is_none());
}

#[test]
fn a_provenance_receipt_is_viewed_step_by_step() {
    use crate::provenance::{AssetReceipt, ProvStep};
    let steps = vec![
        ProvStep::new("capture", [0; 32], [0x10; 32], 1, 0),
        ProvStep::new("encode.png", [0x10; 32], [0x20; 32], 1, 4_200).with_energy_class(chip(15_403)).unwrap(),
    ];
    let r = AssetReceipt::build(&key(9), "did:web:pin#prov", [0x20; 32], "image", "wai.image.png", 0, steps, None);
    let v = r.figure_view().unwrap();
    assert_eq!((v.profile.as_str(), v.identity), ("wai:asset-receipt-id", r.receipt_hash()));
    assert_eq!(v.figure(Some(0)).unwrap().class, None);
    assert_eq!(v.figure(Some(1)), Some(&Figure { index: Some(1), joules_micro: 4_200, class: Some(chip(15_403)) }));
    assert_eq!(v.figure(Some(2)), None);
}

#[cfg(feature = "video_receipt")]
#[test]
#[allow(deprecated)] // the receipt sealed over the JSON form, as its legacy pins are
fn the_video_receipt_is_viewed_by_its_receipt_hash() {
    let p = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors/int_video/int_video_bitstream.json");
    let bytes = std::fs::read(p).unwrap();
    let bs: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
    let r = crate::video_receipt::VideoReceipt::seal_decode(&key(7), "m", &bytes, &bs, 4_200, Some(chip(30_000)), None).unwrap();
    let v = r.figure_view().unwrap();
    assert_eq!((v.profile.as_str(), v.identity), ("wai:video-receipt-id", r.receipt_hash()));
    assert_eq!(v.figures[0].class, Some(chip(30_000)));
}

#[cfg(feature = "video_manifest")]
#[test]
fn the_ott_receipts_are_viewed_under_their_profiles() {
    use crate::wai_video_manifest::{ManifestReceipt, Segment, SegmentKind, SessionClass};
    let class = SessionClass {
        content_id: "asset".into(),
        entitlement: "tier".into(),
        geo: "US".into(),
        blackouts: vec![],
        ad_pod_id: "pod".into(),
        window: 7,
    };
    let segments = vec![Segment { index: 0, kind: SegmentKind::Content, uri: "seg0.ts".into(), duration_ms: 6000, ad_id: None }];
    let grant = wai_quantum::quantum_ops::GrantRef {
        grant_hash: [1; 32],
        capability: "video.manifest".into(),
        joule_ceiling_micro: 1_000_000,
        funds_ceiling: None,
    };
    let r = ManifestReceipt::seal(&key(3), "o", class, segments, 300, 9_000, Some(EnergyProvenance::ModelBased), grant, None);
    let v = r.figure_view().unwrap();
    assert_eq!((v.profile.as_str(), v.identity), ("wai:video-manifest-receipt-id", r.receipt_hash()));
    assert_eq!(v.figures[0].class, Some(EnergyProvenance::ModelBased));
    // The other OTT families' profiles are the receipt-id domains they hash under.
    for (name, domain) in [
        ("wai:video-ssai-receipt-id", &b"wai:video-ssai-receipt-id\x01"[..]),
        ("wai:video-keys-receipt-id", b"wai:video-keys-receipt-id\x01"),
        ("wai:video-package-receipt-id", b"wai:video-package-receipt-id\x01"),
        ("wai:video-channel-receipt-id", b"wai:video-channel-receipt-id\x01"),
    ] {
        assert_eq!(profile_name_of_domain(domain), Some(name));
    }
}

/// A `wai.video.ssai` receipt of the break form (live-ads §8) is viewed under the stitched
/// form's profile: the two share the identity domain.
#[cfg(feature = "video_ssai")]
#[test]
fn a_break_form_ssai_receipt_is_viewed_under_the_ssai_profile() {
    use crate::wai_video_ssai::{asset_format, decide, pod_hash, AdCandidate, BreakBinding, SsaiBreakReceipt};
    let pod = vec![AdCandidate { ad_id: "a".into(), creative_hash: [1; 32], duration_ms: 15_000, price_micros: 20_000 }];
    let mut imps = decide(&pod, 1, 30_000, 50_000);
    imps[0].quartiles_reached = 5;
    let binding = BreakBinding {
        room: [2; 32],
        cue: [3; 32],
        session: [4; 16],
        start_offset_ms: 0,
        format: asset_format::HLS,
        asset_list_sha256: [5; 32],
        avail_ms: 30_000,
        slots: 1,
        funds_remaining_micros: 50_000,
    };
    let grant = wai_quantum::quantum_ops::GrantRef { grant_hash: [6; 32], capability: "wai.live.room".into(), joule_ceiling_micro: u64::MAX, funds_ceiling: None };
    let r = SsaiBreakReceipt::seal(&key(4), "d", binding, pod_hash(&pod), imps, 1, 700, Some(EnergyProvenance::ModelBased), grant);
    let v = r.figure_view().unwrap();
    assert_eq!((v.profile.as_str(), v.identity), ("wai:video-ssai-receipt-id", r.receipt_hash()));
    assert_eq!(v.figures[0].class, Some(EnergyProvenance::ModelBased));
    let mut bad = r;
    bad.sig[0] ^= 1;
    assert!(bad.figure_view().is_none(), "a receipt that does not verify has no view");
}

#[cfg(feature = "quantum_receipt")]
#[test]
fn a_quantum_receipt_is_viewed_in_either_form() {
    use wai_quantum::quantum::{Circuit, Measure};
    use wai_quantum::quantum_energy::{Labelled, MaybeLabelled};
    use wai_quantum::quantum_receipt::QuantumReceipt;
    let mut c = Circuit::new(3);
    c.h(0).cx(0, 1);
    let r = QuantumReceipt::seal(&key(1), "did:key:pin", &c, Some(Measure { seed: 7, shots: 1024 }), 117_000, None).unwrap();
    let u = MaybeLabelled::Unlabelled(r.clone());
    let v = u.figure_view().unwrap();
    // The profile name is pinned by literal: it is read through the quantum crate's
    // labelling trait, so a change there must fail here.
    assert_eq!(v.profile, "wai:quantum-receipt-id");
    assert_eq!((v.identity, v.figures[0].class.clone()), (r.receipt_hash(), None));
    let q = crate::energy_class::to_quantum_class(&chip(15_403)).unwrap();
    let l = MaybeLabelled::Labelled(Labelled::seal(r, q, &key(1)).unwrap());
    let v = l.figure_view().unwrap();
    assert_eq!((v.identity, v.figures[0].class.clone()), (l.receipt_hash(), Some(chip(15_403))));
}

#[cfg(feature = "quantum_ops")]
#[test]
fn the_quantum_ops_receipts_name_their_profiles() {
    use wai_quantum::quantum_energy::MaybeLabelled;
    use wai_quantum::quantum_ops::*;
    let grant = |cap: &str| GrantRef { grant_hash: content_hash(b"g"), capability: cap.into(), joule_ceiling_micro: 5_000_000, funds_ceiling: Some(100) };
    fn view<R: wai_quantum::quantum_energy::Labellable>(r: R) -> String {
        MaybeLabelled::Unlabelled(r).figure_view().expect("verifies").profile
    }
    let k = key(1);
    let job = JobReceipt::seal(&k, "p", "wai.quantum.circuit", content_hash(b"c"), content_hash(b"r"), None, 1000, 250, grant("quantum.job.run"), None);
    let mit = MitigationReceipt::seal(&k, "p", "wai.quantum.circuit", "zne.richardson", "ZZ", content_hash(b"raw"), None, 744_178, 912_680, 20_971, 48_000, 900_000, grant("quantum.mitigate"), None);
    let dec = DecodeReceipt::seal(&k, "p", "toric:L7", "relay-bp.min-sum", content_hash(b"H"), content_hash(b"s"), content_hash(b"c"), true, 3, 46_656, 900_000, grant("quantum.decode"), None);
    let cmp = CompileReceipt::seal(&k, "p", "clifford-tableau", content_hash(b"s"), content_hash(b"t"), content_hash(b"cpl"), true, 3, 4096, 300_000, grant("quantum.compile"), None);
    let rea = RearrangeReceipt::seal(&k, "p", "hungarian-lsap", content_hash(b"i"), content_hash(b"t"), content_hash(b"p"), true, 41, 318, 450_000, grant("quantum.rearrange"), None);
    let cal = CalibrationReceipt::seal(&k, "p", "lab:rig", "q3", content_hash(b"cfg"), vec![], 1_000, grant("quantum.calibrate"), None);
    assert_eq!(view(job), "wai:quantum-job-id");
    assert_eq!(view(mit), "wai:quantum-mitigate-id");
    assert_eq!(view(dec), "wai:quantum-decode-id");
    assert_eq!(view(cmp), "wai:quantum-compile-id");
    assert_eq!(view(rea), "wai:quantum-rearrange-id");
    assert_eq!(view(cal), "wai:quantum-calibration-id");
}

#[cfg(feature = "quantum_fabric")]
#[test]
fn a_fabric_receipt_names_its_profile() {
    use wai_quantum::quantum_energy::EnergyClass;
    use wai_quantum::quantum_fabric::{detect_local, FabricReceipt};
    let member = detect_local();
    let entry = member.kernels[0].clone();
    let r = FabricReceipt::seal(&key(1), "p", [3; 32], &member, &entry, 2_500, Some(EnergyClass::ModelBased));
    let v = r.figure_view().expect("verifies");
    assert_eq!(v.profile, "wai:quantum-fabric-id");
    assert_eq!(profile_name_of_domain(b"wai:quantum-fabric-id\x01"), Some("wai:quantum-fabric-id"));
    assert_eq!((v.identity, v.figures[0].joules_micro), (r.receipt_hash(), 2_500));
    assert_eq!(v.figures[0].class, Some(crate::energy_class::from_quantum_class(&EnergyClass::ModelBased)));
    assert_eq!(v.signer, Some(r.signer_pubkey));
    let unmetered = FabricReceipt::seal(&key(1), "p", [3; 32], &member, &entry, 0, None);
    assert_eq!(unmetered.figure_view().expect("verifies").figures[0].class, None);
    let mut forged = r.clone();
    forged.joules_micro += 1;
    assert!(forged.figure_view().is_none());
}

#[cfg(feature = "quantum_phasor_meter")]
#[test]
fn a_phasor_receipt_names_its_profile() {
    use wai_quantum::quantum_energy::MaybeLabelled;
    use wai_quantum::quantum_phasor_meter::{computation_id, result_fingerprint, PhasorReceipt, PhasorWork};
    let work = PhasorWork { n_features: 200, input_dim: 2, n_samples: 300 };
    let r = PhasorReceipt::seal(&key(1), "p", "wai.quantum.kernel", computation_id("wai.quantum.kernel", &[0.5]), result_fingerprint(&[0.1]), work, 117_000, None);
    assert_eq!(MaybeLabelled::Unlabelled(r).figure_view().unwrap().profile, "wai:phasor-receipt-id");
}

#[cfg(feature = "quantum_qbom")]
#[test]
fn a_bill_of_materials_is_viewed_entry_by_entry() {
    use wai_quantum::quantum_energy::LabelledQbom;
    use wai_quantum::quantum_qbom::QbomBuilder;
    let q = QbomBuilder::new("job", [0x51; 32], "sim").stage("compile", [0x52; 32], 120_000).stage("execute", [0x53; 32], 90_000).seal(&key(1), "p");
    let v = q.figure_view().unwrap();
    assert_eq!((v.profile.as_str(), v.identity), ("wai:quantum-qbom-id", q.receipt_hash()));
    assert_eq!(v.figure(Some(1)), Some(&Figure { index: Some(1), joules_micro: 90_000, class: None }));
    let c = crate::energy_class::to_quantum_class(&chip(15_403)).unwrap();
    let l = LabelledQbom::seal(q, vec![Some(c), None], &key(1)).unwrap();
    let v = l.figure_view().unwrap();
    assert_eq!(v.identity, l.receipt_hash());
    assert_eq!(v.figure(Some(0)).unwrap().class, Some(chip(15_403)));
    assert_eq!(v.figure(Some(1)).unwrap().class, None);
}

mod meter_reports {
    use super::*;
    use crate::energy_figure::meter_report::{default_path, emit, parse_args, ReportArgs};
    use crate::energy_report::{BracketSummary, BracketValues, BuildRecord};

    fn live(n: u64, mean_uw: u64, sd_uw: u64) -> BracketSummary {
        BracketSummary { n, mean_uw, sd_uw, live_intervals: n, longest_live_run: n }
    }

    fn bracket() -> BracketValues {
        BracketValues {
            idle: live(10, 20_000_000, 115_471),
            busy: live(14, 30_000_000, 115_471),
            window_us: 3_000_000,
            completions: 60,
            in_flight: 10,
            opening: crate::energy_class::WindowOpening::FromRest,
        }
    }

    #[test]
    fn a_report_is_written_by_default_beside_the_receipt() {
        assert_eq!(default_path(Some("out/r.json")), Some("out/r.report.json".into()));
        assert_eq!(default_path(Some("receipt")), Some("receipt.report.json".into()));
        assert_eq!(default_path(None), None, "no receipt file: the report goes to standard output");
        let a = parse_args(&["x.wscr".into(), "--no-report".into(), "r.json".into(), "--previous-report".into(), "p.json".into()]);
        assert_eq!(a, Ok(ReportArgs { positional: vec!["x.wscr".into(), "r.json".into()], no_report: true, previous_report: Some("p.json".into()) }));
        assert!(!parse_args(&["x".into()]).unwrap().no_report, "on unless opted out");
        // A missing value is an error, not a report with no parent.
        assert!(parse_args(&["x".into(), "--previous-report".into()]).is_err(), "at the end");
        assert!(parse_args(&["x".into(), "--previous-report".into(), "--no-report".into()]).is_err(), "followed by an option");
    }

    /// A report on a world session, sealed from a bracket: it checks against the
    /// receipt, chains to the previous report, and leaves the receipt's bytes as they were.
    #[cfg(feature = "world")]
    #[test]
    fn a_meter_report_checks_against_its_receipt_and_changes_nothing() {
        use crate::world::{receipt::SessionReceipt, Checkpoint};
        let b = bracket();
        let f = b.figure().unwrap();
        let class = EnergyProvenance::OnChipCounter {
            uncertainty: Uncertainty { relative_ppm: b.least_relative_ppm(f).unwrap(), window_us: b.window_us },
            filtering: CounterFiltering::Unknown,
        };
        let cps = vec![Checkpoint { tick: 1, hash: [1; 32] }];
        let r = SessionReceipt::build(&key(9), "s", [2; 32], 0, &[], &cps, f, 1, None).with_energy_class(&key(9), class).unwrap();
        let (hash, json) = (r.receipt_hash(), r.to_json());
        let view = r.figure_view().unwrap();
        let first = emit(&view, None, Some(b), BuildRecord::of_this_build(), "reader:example", &key(9), "s", None, "2026-09-30T12:00:00Z").unwrap();
        first.verify().unwrap();
        assert_eq!(first.check_figure_against(Some(&view)), Ok(()));
        let second = emit(&view, None, Some(b), BuildRecord::of_this_build(), "reader:example", &key(9), "s", Some(&first), "2026-09-30T12:00:01Z").unwrap();
        assert_eq!(second.check_parent(Some(&first)), Ok(()));
        assert_eq!((r.receipt_hash(), r.to_json()), (hash, json), "the receipt is unchanged by a report about it");
        // Another key's report is refused before it is written.
        assert!(emit(&view, None, Some(b), BuildRecord::of_this_build(), "reader:example", &key(8), "s", None, "2026-09-30T12:00:00Z").is_err());
        // An unmetered receipt gets none.
        let u = SessionReceipt::build(&key(9), "s", [2; 32], 0, &[], &cps, 0, 1, None);
        assert!(emit(&u.figure_view().unwrap(), None, None, BuildRecord::of_this_build(), "reader:example", &key(9), "s", None, "2026-09-30T12:00:00Z").is_err());
    }

    /// One report per labelled provenance step, by index.
    #[test]
    fn a_provenance_step_gets_its_own_report() {
        use crate::provenance::{AssetReceipt, ProvStep};
        let model = EnergyProvenance::ModelBased;
        let steps = vec![ProvStep::new("capture", [0; 32], [1; 32], 1, 0), ProvStep::new("encode", [1; 32], [2; 32], 1, 77).with_energy_class(model.clone()).unwrap()];
        let r = AssetReceipt::build(&key(9), "s", [2; 32], "image", "wai.image.png", 0, steps, None);
        let view = r.figure_view().unwrap();
        let rep = emit(&view, Some(1), None, BuildRecord::of_this_build(), "reader:example", &key(9), "s", None, "2026-09-30T12:00:00Z").unwrap();
        assert_eq!(rep.check_figure_against(Some(&view)), Ok(()));
        assert!(emit(&view, Some(0), None, BuildRecord::of_this_build(), "reader:example", &key(9), "s", None, "2026-09-30T12:00:00Z").is_err(), "an unmetered step gets none");
    }
}
