"""End-to-end: gate decisions -> witness cosign (append-only) -> RFC 3161 anchor.

Proves the three pieces compose into the standard construction the research
confirmed: the Merkle log gives append-only consistency, the witness enforces it
with its own key before cosigning, and the RFC 3161 timestamp binds a
third-party time+integrity presumption to the tree ROOT (not each entry).
"""
from abom import sign
from abom.bom import build_composition
from abom.gate import Action, Gate
from abom.signer import LocalSigner
from abom.witness import Witness
from abom.anchor import anchor_head


def _manifest(tmp_path):
    key = sign.load_or_create_key(tmp_path / "owner.pem")
    return build_composition(
        agent={"name": "a", "version": "1"},
        components=[{"type": "tool", "name": "read_docs"}],
        controls={}, key=key,
    )


def test_gate_witness_anchor_end_to_end(tmp_path):
    manifest = _manifest(tmp_path)
    operator = LocalSigner(sign.load_or_create_key(tmp_path / "owner.pem"))
    # the witness key is DISTINCT from the operator's — that separation is the point
    witness = Witness(LocalSigner(sign.load_or_create_key(tmp_path / "witness.pem")))
    assert witness.key_id != operator.key_id

    gate = Gate(manifest, signer=operator)

    # decision 1 -> witness cosigns the first head
    gate.check(Action(tool="read_docs"))
    head1 = gate.head()
    cosig1 = witness.cosign(head1)
    assert cosig1["tree_size"] == 1

    # decision 2 -> append-only growth; witness requires a consistency proof
    gate.check(Action(tool="nope"))
    head2 = gate.head()
    proof = gate.consistency_since(1)  # 1 -> 2
    cosig2 = witness.cosign(head2, consistency=proof)
    assert cosig2["tree_size"] == 2

    # anyone can verify the witness cosignature with only its public key
    assert Witness.verify_cosignature(cosig2, witness_public_key_b64=witness.public_key_b64)

    # anchor the cosigned root to an RFC 3161 TSA (injected, no network) —
    # the timestamp binds the ROOT, exactly as ERS/CT prescribe
    token = anchor_head(head2, tsa_url="https://tsa.example/tsr",
                        _post=lambda u, q, timeout: b"TSA-TOKEN")
    assert token["root_hash"] == head2["root_hash"]
    assert token["tree_size"] == 2
    assert token["timestamp_token_b64"]


def test_witness_rejects_operator_rewrite(tmp_path):
    """If the operator rewrites history, the witness won't cosign the forged head —
    the property a self-signed log can't provide on its own."""
    manifest = _manifest(tmp_path)
    operator = LocalSigner(sign.load_or_create_key(tmp_path / "owner.pem"))
    witness = Witness(LocalSigner(sign.load_or_create_key(tmp_path / "witness.pem")))

    gate = Gate(manifest, signer=operator)
    gate.check(Action(tool="read_docs"))
    gate.check(Action(tool="a"))
    gate.check(Action(tool="b"))
    witness.cosign(gate.head())  # witness has now attested size-3

    # operator builds a different size-3 log (rewrote a leaf) and asks to cosign
    from abom.merkle import MerkleLog
    forged = MerkleLog()
    for e in (b"x", b"y", b"EVIL"):
        forged.append(e)
    from abom.witness import WitnessRejected
    import pytest
    bad = {"old_size": 3, "new_size": 3, "proof": [], "new_root": forged.head().root_hash}
    with pytest.raises(WitnessRejected):
        witness.cosign(forged.head().as_dict(), consistency=bad)
