"""
SNIN Emission Paper Pilot — exact calculation v0.4.3 (FROZEN).
Adds required_verdict for the verdict gate; derivation, partial
rating_adjustment and conservation with proof-of-origin are carried
over from v0.4.2. Pure: no I/O, no state.
"""

MICRO = 1_000_000

EMISSION_CLASSES = {
    "verify_accept":    (True, True),
    "verify_reject":    (True, True),
    "recheck_assigned": (True, True),
    "recheck_confirm":  (True, True),
    "recheck_overturn": (True, True),
    "presence":         (False, False),
    "task_receipt":     (False, False),
}

EXCLUDED = {"copycat", "self_circle", "rubber_stamp", "mutual_sleep_digest", "entropy"}

REVERSAL_BASIS_EFFECTS = {
    "fraud":             {"rating_adjustment", "balance_clawback"},
    "misclassification": {"rating_adjustment"},
}

REVIEW_CLASSES = {"verify_accept", "verify_reject",
                  "recheck_assigned", "recheck_confirm", "recheck_overturn"}


def required_verdict(emission_class):
    """A review emission is only payable on a CLEAN attestation. An
    attestation with verdict 'flag' must never fund verify_accept or any
    other review class (falsifier #25090.4)."""
    if emission_class in REVIEW_CLASSES:
        return "accept"
    return None


def quoted_emission(row):
    cls = row.get("emission_class")
    if cls not in EMISSION_CLASSES or cls in EXCLUDED:
        return {"balance": 0, "rating": 0}
    if cls in ("presence", "task_receipt"):
        return {"balance": 0, "rating": 0}
    b_ok, r_ok = EMISSION_CLASSES[cls]
    amt = int(row.get("amount_micro", 0))
    if amt < 0:
        raise ValueError("negative amount_micro")
    return {"balance": amt if b_ok else 0, "rating": amt if r_ok else 0}


def _emissions(rows):
    return [r for r in rows if r.get("event_type") == "emission" and not r.get("is_fixture")]


def _reversals(rows):
    return [r for r in rows if r.get("event_type") == "reversal" and not r.get("is_fixture")]


def net_balance(events, actor):
    bal = 0
    for r in events:
        if r.get("is_fixture") or r.get("actor") != actor:
            continue
        if r.get("event_type") == "reversal":
            if r.get("effect") == "balance_clawback":
                bal -= int(r.get("amount_micro", 0))
            continue
        bal += quoted_emission(r)["balance"]
    return bal


def derived_rating(events):
    rating = {}
    spent = set()
    for r in _emissions(events):
        q = quoted_emission(r)
        rating[r["actor"]] = rating.get(r["actor"], 0) + q["rating"]
    for r in _reversals(events):
        if r.get("effect") != "rating_adjustment":
            continue
        key = (r["reverses_event_id"], "rating_adjustment")
        if key in spent:
            raise ValueError("double rating_adjustment on one credit")
        spent.add(key)
        for orig in _emissions(events):
            if orig["seq"] == r["reverses_event_id"]:
                q = quoted_emission(orig)
                cap = q["rating"]
                amt = min(int(r.get("amount_micro", 0)), cap)
                rating[orig["actor"]] = rating.get(orig["actor"], 0) - amt
                break
    return rating


def conservation(events, issued_caps, edge=0):
    """
    pool = sum of ISSUED assignment caps (signed events)
    gross = balance emissions; clawbacks reduce paid-out balance
    carry = pool - net_payouts, computed
    invariant: net_payouts + carry == pool * (1 - edge)
    """
    pool = int(sum(int(c) for c in issued_caps) * (1 - edge))
    gross = sum(quoted_emission(r)["balance"] for r in _emissions(events))
    claw = sum(int(r.get("amount_micro", 0)) for r in _reversals(events)
               if r.get("effect") == "balance_clawback")
    net_payouts = gross - claw
    carry = pool - net_payouts
    ok = (carry >= 0) and (net_payouts + carry == pool) and (net_payouts <= gross)
    return ok, {
        "pool_from_caps": pool, "gross": gross, "clawbacks": claw,
        "net_payouts": net_payouts, "carry": carry,
        "invariant": f"{net_payouts} + {carry} == {pool}",
    }
