"""R3 — immutable event log acceptance checks.

Acceptance predicate (roadmap R3): replaying a fixture circuit produces a
schema-valid JSONL whose SHA-256 matches the manifest; the file is append-only
(no in-place edits). This is also instrument-validation gate item 6.
"""

import hashlib
import json

import pytest

from cmd_chat.sor.events import (
    EVENT_TYPES,
    EventLog,
    replay_and_seal,
    replay_fixture_circuit,
    validate_record,
)
from cmd_chat.sor.provenance import Node, RunManifest

KNOWN_PUB_B64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8="


def _manifest(root, run_id="20260719T010203Z", seed=123456789):
    return RunManifest(
        run_id=run_id,
        sor_seed=seed,
        topology="1-house",
        selector="static",
        churn_schedule_id="fixture-churn-0",
        nodes=[Node(role="host", persona_pub_b64=KNOWN_PUB_B64, engine="docker")],
        engine_kind="docker",
        worktree_root=root,
    )


def _read_lines(path):
    return [json.loads(l) for l in path.read_text().splitlines() if l.strip()]


def test_replay_schema_valid_and_hash_matches_manifest(tmp_path):
    run_dir = tmp_path / "run"
    sealed = replay_and_seal(run_dir, _manifest(tmp_path), pool=5, hops=3, rebuilds=2)

    events_path = run_dir / "events.jsonl"
    assert events_path.is_file()

    # Every line schema-valid, uses a known event type, and carries the seed.
    records = _read_lines(events_path)
    assert len(records) > 0
    for rec in records:
        validate_record(rec)
        assert rec["event"] in EVENT_TYPES
        assert rec["seed"] == 123456789

    # SHA-256 of the closed file matches what was sealed into the manifest.
    disk_sha = hashlib.sha256(events_path.read_bytes()).hexdigest()
    assert sealed["events"]["sha256"] == disk_sha
    manifest_doc = json.loads((run_dir / "manifest.json").read_text())
    assert manifest_doc["events"]["sha256"] == disk_sha
    assert manifest_doc["timestamps"]["stop_utc"] is not None


def test_replay_is_deterministic_modulo_timestamps(tmp_path):
    # Same seed -> identical circuit-build sequence -> identical event bodies
    # (drop the wall-clock ts). Two independent runs must match field-for-field.
    a_dir = tmp_path / "a"
    b_dir = tmp_path / "b"
    replay_fixture_circuit(a_dir, "runA", 42, pool=6, hops=3, rebuilds=3)
    replay_fixture_circuit(b_dir, "runB", 42, pool=6, hops=3, rebuilds=3)

    def _strip(path, run_id):
        out = []
        for r in _read_lines(path):
            r = dict(r)
            r.pop("ts")
            r["run_id"] = "X"  # normalize the only other per-run field
            out.append(r)
        return out

    assert _strip(a_dir / "events.jsonl", "runA") == _strip(b_dir / "events.jsonl", "runB")


def test_append_only_no_in_place_edits(tmp_path):
    run_dir = tmp_path / "run"
    log = EventLog(run_dir, "runX", 7)
    log.emit("circuit_build", circuit_id="c0", hop_index=0)
    first_bytes = (run_dir / "events.jsonl").read_bytes()
    first_len = len(first_bytes)

    log.emit("hop_add", node_fp="00000001", circuit_id="c0", hop_index=0, bytes_=10, latency_ms=1.0)
    log.close()

    after = (run_dir / "events.jsonl").read_bytes()
    # The file only grew; the original prefix is byte-identical (append-only).
    assert len(after) > first_len
    assert after[:first_len] == first_bytes


def test_seal_is_once_only(tmp_path):
    run_dir = tmp_path / "run"
    replay_and_seal(run_dir, _manifest(tmp_path), rebuilds=1)
    # A second seal attempt on the same manifest is refused (immutability).
    from cmd_chat.sor.provenance import seal_manifest

    with pytest.raises(ValueError):
        seal_manifest(run_dir, "0" * 64)


def test_unknown_event_rejected(tmp_path):
    log = EventLog(tmp_path / "run", "runX", 1)
    with pytest.raises(ValueError):
        log.emit("not_a_real_event")
    log.close()


def test_closed_log_refuses_emit(tmp_path):
    log = EventLog(tmp_path / "run", "runX", 1)
    log.emit("circuit_build", circuit_id="c0")
    log.close()
    with pytest.raises(RuntimeError):
        log.emit("circuit_build", circuit_id="c1")
