//! SPEC §3.1 check 5 at a sink that holds a key log: the revocation part runs for every
//! capability a pin applies to and needs no grant, status or clock; the authorisation
//! part runs for the capabilities held to jwp-receipts §7.9.

use super::*;
use crate::container::{PinRefusal, Prior};
use crate::pin::PinAuthority;

const P: &str = "https://publisher.example";
const CAP: &str = "wai.neural.int_mlicpp";
const NOW: &str = "2026-09-20T06:00:00Z";

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

fn set_digest() -> [u8; 32] {
    [0x5e; 32]
}

fn other_digest() -> [u8; 32] {
    [0x5f; 32]
}

/// A key log whose head revokes `revoked`, anchored by a pinned record of its first
/// revision, with a current status where `online`, a grant for the set where `grant`,
/// and `CAP`'s sets held to §7.9 where `held_to`.
fn sink(revoked: &[[u8; 32]], online: bool, grant: bool, held_to: bool) -> SinkKeyLog {
    let unsigned = Revision {
        origin: P.into(),
        seq: 0,
        prev: None,
        issued_at: "2026-09-01T00:00:00Z".into(),
        root: [0; 32],
        next_root: commitment(&key(0x62).verifying_key().to_bytes()),
        keys: vec![KeyEntry::active("modd", key(0x6b).verifying_key().to_bytes(), &["module/decoder"])],
        revoked_modules: vec![],
        grant_max_s: 3_600,
        status_max_age_s: 600,
        sig: [0; 64],
        hybrid: None,
    };
    let r0 = Revision::seal(&key(0x61), unsigned.clone()).unwrap();
    let r1 = Revision::seal(
        &key(0x61),
        Revision {
            seq: 1,
            prev: Some(r0.revision_hash()),
            issued_at: "2026-09-10T00:00:00Z".into(),
            revoked_modules: revoked.to_vec(),
            ..unsigned
        },
    )
    .unwrap();
    let statuses = if online { vec![KeyStatus::seal(&key(0x61), &r1, "2026-09-20T05:55:00Z").to_json()] } else { vec![] };
    let grants = if grant {
        vec![ModuleGrant::seal(
            &key(0x6b),
            "did:web:publisher.example#modd",
            P,
            set_digest(),
            ModuleForm::Decoder,
            &[CAP],
            "2026-09-20T05:30:00Z",
            "2026-09-20T06:30:00Z",
        )
        .unwrap()
        .to_json()]
    } else {
        vec![]
    };
    let s = SinkKeyLog::new(vec![r0.clone(), r1], vec![Anchor::pinned(P, r0.revision_hash())])
        .with_statuses(statuses)
        .with_grants(grants)
        .at(NOW);
    if held_to {
        s.hold_pinned_sets(CAP, &[P])
    } else {
        s
    }
}

fn hex32(d: &[u8; 32]) -> String {
    d.iter().map(|b| format!("{b:02x}")).collect()
}

fn pin(d: &[u8; 32]) -> Prior {
    Prior::new("set", hex32(d), "decode-equivalence")
}

fn code(r: Result<(), PinRefusal>) -> &'static str {
    match r {
        Ok(()) => "ok",
        Err(e) => e.code(),
    }
}

#[test]
fn a_revoked_set_is_refused_whatever_check_a_grant_fails_first() {
    // Offline: the grant fails at check 4, and the digest is still revoked.
    let offline = sink(&[set_digest()], false, true, true);
    assert_eq!(
        offline.check(CAP, &pin(&set_digest())),
        Err(PinRefusal::Revoked { capability: CAP.into(), revoked_by: Some(P.into()) })
    );
    // No grant at all.
    let no_grant = sink(&[set_digest()], true, false, true);
    assert_eq!(code(no_grant.check(CAP, &pin(&set_digest()))), "prior_revoked");
    // No trusted clock.
    let no_clock = SinkKeyLog { now: None, ..sink(&[set_digest()], true, true, true) };
    assert_eq!(code(no_clock.check(CAP, &pin(&set_digest()))), "prior_revoked");
    // A grant that reaches check 5 at the granting origin's own head reports no
    // other origin.
    assert_eq!(
        sink(&[set_digest()], true, true, true).check(CAP, &pin(&set_digest())),
        Err(PinRefusal::Revoked { capability: CAP.into(), revoked_by: None })
    );
}

#[test]
fn an_unrevoked_set_without_authorisation_is_not_authorised() {
    assert_eq!(
        sink(&[other_digest()], false, true, true).check(CAP, &pin(&set_digest())),
        Err(PinRefusal::NotAuthorised { capability: CAP.into(), check: Some("4"), reason: Some("module_status_stale") })
    );
    assert_eq!(
        sink(&[], true, false, true).check(CAP, &pin(&set_digest())),
        Err(PinRefusal::NotAuthorised { capability: CAP.into(), check: None, reason: None })
    );
    assert_eq!(code(sink(&[], true, true, true).check(CAP, &pin(&set_digest()))), "ok");
}

#[test]
fn revocation_needs_no_hold_to_section_7_9() {
    let s = sink(&[set_digest()], false, false, false);
    assert!(!s.holds_to_section_7_9(CAP));
    assert_eq!(code(s.check(CAP, &pin(&set_digest()))), "prior_revoked");
    assert_eq!(code(s.check(CAP, &pin(&other_digest()))), "ok");
    // A key log with no anchor revokes nothing.
    let unanchored = SinkKeyLog { anchors: vec![], ..s };
    assert_eq!(code(unanchored.check(CAP, &pin(&set_digest()))), "ok");
}

#[test]
fn an_unpinned_set_is_checked_under_every_digest_a_pin_could_name() {
    let hexes = |ds: &[[u8; 32]]| ds.iter().map(hex32).collect::<Vec<_>>();
    // Revoked under one of its digests: refused, held to §7.9 or not.
    for held_to in [false, true] {
        let s = sink(&[set_digest()], true, true, held_to);
        assert_eq!(code(s.check_unpinned(CAP, &hexes(&[other_digest(), set_digest()]))), "prior_revoked", "held_to={held_to}");
    }
    // Not held to §7.9 and not revoked: nothing to authorise.
    assert_eq!(code(sink(&[], false, false, false).check_unpinned(CAP, &hexes(&[set_digest()]))), "ok");
    // Held to §7.9: authorised under one of its digests is enough.
    assert_eq!(code(sink(&[], true, true, true).check_unpinned(CAP, &hexes(&[other_digest(), set_digest()]))), "ok");
    // Held to §7.9 with no grant, or no digest to identify it: not authorised.
    assert_eq!(code(sink(&[], true, false, true).check_unpinned(CAP, &hexes(&[set_digest()]))), "prior_not_authorised");
    assert_eq!(code(sink(&[], true, true, true).check_unpinned(CAP, &[])), "prior_not_authorised");
}
