"""Tests for the Merkle transparency log — RFC 6962 inclusion & consistency."""
import pytest

from abom.merkle import (
    MerkleLog,
    consistency_proof,
    hash_leaf,
    inclusion_proof,
    merkle_root,
    verify_consistency,
    verify_consistency_hex,
    verify_inclusion,
    verify_inclusion_hex,
)


def leaves(n):
    return [hash_leaf(f"entry-{i}".encode()) for i in range(n)]


def test_empty_and_single_roots_are_stable():
    import hashlib
    assert merkle_root([]) == hashlib.sha256(b"").digest()
    one = leaves(1)
    assert merkle_root(one) == one[0]


@pytest.mark.parametrize("n", [1, 2, 3, 4, 5, 7, 8, 9, 16, 31, 100])
def test_inclusion_proof_verifies_for_every_leaf(n):
    ls = leaves(n)
    root = merkle_root(ls)
    for i in range(n):
        proof = inclusion_proof(ls, i)
        assert verify_inclusion(ls[i], i, n, proof, root), f"leaf {i}/{n} failed"


def test_inclusion_proof_rejects_wrong_leaf():
    ls = leaves(8)
    root = merkle_root(ls)
    proof = inclusion_proof(ls, 3)
    wrong = hash_leaf(b"tampered")
    assert not verify_inclusion(wrong, 3, 8, proof, root)


def test_inclusion_proof_rejects_wrong_root():
    ls = leaves(8)
    proof = inclusion_proof(ls, 3)
    bad_root = hash_leaf(b"not the root")
    assert not verify_inclusion(ls[3], 3, 8, proof, bad_root)


@pytest.mark.parametrize("n", [2, 3, 4, 5, 8, 13, 16, 50])
def test_consistency_proof_verifies_for_all_prefixes(n):
    ls = leaves(n)
    new_root = merkle_root(ls)
    for m in range(1, n + 1):
        old_root = merkle_root(ls[:m])
        proof = consistency_proof(ls, m, n)
        assert verify_consistency(m, n, proof, old_root, new_root), f"m={m} n={n} failed"


def test_consistency_detects_tampered_history():
    """If a published leaf is edited, consistency to the old root must fail."""
    ls = leaves(8)
    old_root = merkle_root(ls[:5])
    # tamper: rewrite leaf 2, then extend
    tampered = list(ls)
    tampered[2] = hash_leaf(b"rewritten")
    new_root = merkle_root(tampered)
    proof = consistency_proof(tampered, 5, 8)
    assert not verify_consistency(5, 8, proof, old_root, new_root)


def test_merkle_log_append_inclusion_and_consistency():
    log = MerkleLog()
    for i in range(6):
        log.append(f"action-{i}".encode())
    assert log.size == 6

    # inclusion of a known entry verifies
    proof = log.prove_inclusion(4)
    assert verify_inclusion_hex(b"action-4", proof)
    # and fails for the wrong bytes at that index
    assert not verify_inclusion_hex(b"action-999", proof)

    # snapshot the head, append more, prove consistency
    old_root = log.head().root_hash
    for i in range(6, 10):
        log.append(f"action-{i}".encode())
    cproof = log.prove_consistency(6)
    assert verify_consistency_hex(old_root, cproof)


def test_append_only_violation_is_detectable_via_log():
    """Simulate an operator who rewrote an entry: consistency from old head fails."""
    log = MerkleLog()
    for i in range(5):
        log.append(f"a-{i}".encode())
    old_root = log.head().root_hash

    # operator rebuilds the log with entry 1 changed (the attack the chain can't stop)
    evil = MerkleLog()
    for i in range(5):
        evil.append(b"HACKED" if i == 1 else f"a-{i}".encode())
    evil.append(b"a-5")
    cproof = evil.prove_consistency(5)
    # the new log cannot produce a valid consistency proof to the honest old root
    assert not verify_consistency_hex(old_root, cproof)
