"""
SNIN Emission Paper Pilot — append-only ledger store (FROZEN v0.4.1).
Blocker #2 fixed (astranaut01 #24832): reversal integrity is ENFORCED here,
not merely declared. This module is the storage layer the schema refers to.

Guarantees:
  - append-only: rows cannot be edited or deleted after append (immutable)
  - fixture gate: is_fixture=true forces amount_micro=0 (emitted_amount=0)
  - emission classes validated; excluded classes rejected at the boundary
  - presence/task_receipt forced to amount 0
  - reversal checks:
      * target exists and is an emission row (not a reversal, not a fixture)
      * actor matches the target's actor
      * amount does not exceed the target's credit for that effect
      * basis permits the effect (fraud -> both; misclassification -> rating only)
      * UNIQUE(reverses_event_id, effect): duplicate reversal rejected
"""
import json
import emission as E

ALLOWED_REVERSAL_EFFECTS = E.REVERSAL_BASIS_EFFECTS


class LedgerError(Exception):
    pass


class LedgerStore:
    def __init__(self, rows=None):
        self._rows = []
        self._by_seq = {}
        if rows:
            for r in rows:
                self.append(r)

    def snapshot(self):
        """Deep copy of all rows (audit-friendly)."""
        return json.loads(json.dumps(self._rows))

    def _immutable(self, row):
        return json.dumps(row, sort_keys=True)

    # ---- validation ----------------------------------------------------

    def validate_emission(self, row):
        cls = row.get("emission_class")
        if cls not in E.EMISSION_CLASSES:
            raise LedgerError(f"unknown or excluded emission_class: {cls}")
        if row.get("is_fixture"):
            if int(row.get("amount_micro", 0)) != 0:
                raise LedgerError("fixture gate: is_fixture=true => amount_micro MUST be 0")
        if cls in ("presence", "task_receipt"):
            if int(row.get("amount_micro", 0)) != 0:
                raise LedgerError(f"{cls} never emits: amount_micro MUST be 0")
        if int(row.get("amount_micro", 0)) < 0:
            raise LedgerError("negative amount_micro")
        return True

    def validate_reversal(self, row):
        if not row.get("reverses_event_id"):
            raise LedgerError("reversal missing reverses_event_id")
        target = self._by_seq.get(row["reverses_event_id"])
        if target is None:
            raise LedgerError(f"reversal target seq {row['reverses_event_id']} does not exist")
        if target.get("type") != "emission":
            raise LedgerError("reversal target must be an emission row, not another reversal")
        if target.get("is_fixture"):
            raise LedgerError("reversal target must not be a fixture row")
        if row.get("actor") != target.get("actor"):
            raise LedgerError(
                f"reversal actor {row.get('actor')} != target actor {target.get('actor')}")
        effect = row.get("effect")
        basis = row.get("basis")
        if effect not in ALLOWED_REVERSAL_EFFECTS.get(basis, set()):
            raise LedgerError(f"effect {effect} not permitted for basis {basis}")
        # UNIQUE(reverses_event_id, effect)
        for r in self._rows:
            if r.get("type") == "reversal" and \
               r.get("reverses_event_id") == row["reverses_event_id"] and \
               r.get("effect") == effect:
                raise LedgerError(f"duplicate reversal: ({row['reverses_event_id']}, {effect}) already exists")
        # amount cap vs target credit
        q = E.quoted_emission(target)
        cap = q.get("balance", 0) if effect == "balance_clawback" else q.get("rating", 0)
        if int(row.get("amount_micro", 0)) > cap:
            raise LedgerError(
                f"over-{effect}: {row.get('amount_micro')} > target credit {cap}")
        return True

    # ---- append --------------------------------------------------------

    def append(self, row):
        r = dict(row)
        seq = r.get("seq")
        if seq is None:
            seq = max([x["seq"] for x in self._rows] or [0]) + 1
            r["seq"] = seq
        if seq in self._by_seq:
            raise LedgerError(f"duplicate seq {seq}")
        r["type"] = r.get("type") or (
            "reversal" if r.get("reverses_event_id") else "emission")
        if r["type"] == "emission":
            self.validate_emission(r)
        elif r["type"] == "reversal":
            self.validate_reversal(r)
        else:
            raise LedgerError(f"unknown row type {r['type']}")
        self._rows.append(r)
        self._by_seq[seq] = r
        return seq

    def to_jsonl(self):
        return "\n".join(json.dumps(r, sort_keys=True) for r in self._rows)
