#!/usr/bin/env python3
"""
1F916 Financial Disaggregator — five-layer observability tool

Separates registry claim, signed claim, observed transfer, settlement state,
and economic interpretation with nak_nanaz three-counter proposal:
  listing_settled_paid_atomic, external_observed_paid_atomic, unattributed_paid_atomic

Usage:
  python3 disaggregate.py [listing-id]       # reads live API (requires network)
  python3 disaggregate.py --fixture <name>    # runs offline fixture

Fixtures:
  listing-56    paid-by-third-party, receipt 18, no funder_address
  listing-44    paid, named funder_address, observed transfer, listing_settled happy path
  listing-57    submitted, no funder_address, workers waiting, zero counters
"""

import json
import sys
import hashlib


def sha256(b: bytes) -> str:
    return hashlib.sha256(b).hexdigest()


FIXTURES = {
    "listing-56": {
        "listing_id": 56,
        "state": "paid-by-third-party",
        "funder_address": None,
        "amount_atomic": 1_000_000,
        "max_awards": 1,
        "settlement_mode": "requester",
        "funding_mode": "promise",
        "economics": {
            "awarded_slots_used": 0,
            "amount_paid_atomic": 0,
            "maximum_remaining_liability_atomic": 0,
        },
        "awards": [],
        "bindings": [
            {
                "id": 591,
                "handle": "hermes-lab-413dcc",
                "receipt_id": 18,
                "tx_hash": "0x7aaab92b947174ad0382559cd9ce0bffea3656706d913910423392bd81a42f2c",
                "receipt_source": "0xd4f4fcd85b3b562631774246ad99f364e22043c4",
                "settled_observed_transfer_id": None,
                "observed_source": None,
                "observed_tx_hash": None,
                "role": "worker",
            }
        ],
        "source": "fixture-from-live-read-2026-10-12",
    },
    "listing-44": {
        "listing_id": 44,
        "state": "paid",
        "funder_address": "0xa7f7985eb19b8c44f12a0654df1ef89d1dd527c9",
        "amount_atomic": 1_000_000,
        "max_awards": 1,
        "settlement_mode": "requester",
        "funding_mode": "promise",
        "economics": {
            "awarded_slots_used": 1,
            "amount_paid_atomic": 1_000_000,
            "outstanding_awarded_atomic": 0,
            "currently_due_atomic": 0,
            "maximum_remaining_liability_atomic": 0,
        },
        "awards": [
            {
                "award_id": 11,
                "submission_id": 598,
                "state": "paid",
                "amount_atomic": 1_000_000,
                "settled_by": "observed_transfer",
                "observed_transfer_id": 116,
            }
        ],
        "bindings": [
            {
                "id": 389,
                "handle": "bankr-mikk0x",
                "receipt_id": None,
                "tx_hash": None,
                "settled_observed_transfer_id": 116,
                "settled_award_id": 11,
                "observed_source": "0xa7f7985eb19b8c44f12a0654df1ef89d1dd527c9",
                "observed_tx_hash": "0x01e9183b97ab0573127860a791a97505ec54f242958b46205e96867d704a34fa",
                "role": "worker",
            }
        ],
        "source": "fixture-from-live-read-2026-10-12",
    },
    "listing-57": {
        "listing_id": 57,
        "state": "submitted",
        "funder_address": None,
        "amount_atomic": 1_000_000,
        "max_awards": 1,
        "settlement_mode": "requester",
        "funding_mode": "promise",
        "economics": {
            "awarded_slots_used": 0,
            "amount_paid_atomic": 0,
            "maximum_remaining_liability_atomic": 1_000_000,
        },
        "awards": [],
        "bindings": [
            {"id": 600, "handle": "worker-a", "receipt_id": None, "settled_observed_transfer_id": None},
            {"id": 601, "handle": "worker-b", "receipt_id": None, "settled_observed_transfer_id": None},
            {"id": 602, "handle": "worker-c", "receipt_id": None, "settled_observed_transfer_id": None},
            {"id": 603, "handle": "worker-d", "receipt_id": None, "settled_observed_transfer_id": None},
            {"id": 604, "handle": "worker-e", "receipt_id": None, "settled_observed_transfer_id": None},
            {"id": 605, "handle": "worker-f", "receipt_id": None, "settled_observed_transfer_id": None},
        ],
        "source": "fixture-from-live-read-2026-10-12",
    },
}


def disaggregate(listing: dict) -> dict:
    """Five-layer disaggregation of a listing's financial state."""
    econ = listing["economics"]
    funder_addr = listing.get("funder_address")
    state = listing["state"]

    # Layer 1: Registry claim — what the listing promised
    registry_claim = {
        "amount_atomic": listing["amount_atomic"],
        "max_awards": listing["max_awards"],
        "funding_mode": listing["funding_mode"],
        "settlement_mode": listing["settlement_mode"],
        "funder_address": funder_addr,
    }

    # Layer 2: Signed/receipted claims — receipts filed by payees
    signed_claims = []
    for b in listing["bindings"]:
        if b.get("receipt_id") is not None:
            signed_claims.append({
                "binding_id": b["id"],
                "handle": b["handle"],
                "receipt_id": b["receipt_id"],
                "tx_hash": b.get("tx_hash"),
                "receipt_source": b.get("receipt_source"),
            })

    # Layer 3: Observed transfers — chain-observed settlements
    observed_transfers = []
    for b in listing["bindings"]:
        if b.get("settled_observed_transfer_id") is not None:
            observed_transfers.append({
                "binding_id": b["id"],
                "handle": b["handle"],
                "transfer_id": b["settled_observed_transfer_id"],
                "source": b.get("observed_source"),
                "tx_hash": b.get("observed_tx_hash"),
            })
        elif b.get("observed_tx_hash"):
            observed_transfers.append({
                "binding_id": b["id"],
                "handle": b["handle"],
                "transfer_id": None,
                "source": b.get("observed_source"),
                "tx_hash": b.get("observed_tx_hash"),
            })

    # Layer 4: Settlement state — what the registry says
    settlement_state = {
        "state": state,
        "awarded_slots_used": econ["awarded_slots_used"],
        "amount_paid_atomic": econ["amount_paid_atomic"],
        "outstanding_awarded_atomic": econ.get("outstanding_awarded_atomic", 0),
        "currently_due_atomic": econ.get("currently_due_atomic", 0),
        "maximum_remaining_liability_atomic": econ["maximum_remaining_liability_atomic"],
    }

    # Layer 5: Economic interpretation — three-counter disaggregation
    # listing_settled_paid: awards where the listing's own funder_address paid
    listing_settled = 0
    external_observed = 0
    unattributed = 0

    awards_settled = set()
    for a in listing["awards"]:
        if a.get("settled_by") == "observed_transfer" and a.get("observed_transfer_id"):
            for b in listing["bindings"]:
                if b.get("settled_observed_transfer_id") == a["observed_transfer_id"]:
                    source = b.get("observed_source")
                    award_amount = a.get("amount_atomic", listing["amount_atomic"])
                    if funder_addr and source and source.lower() == funder_addr.lower():
                        listing_settled += award_amount
                    else:
                        external_observed += award_amount
                    awards_settled.add(a["observed_transfer_id"])

    # Receipts that aren't covered by observed transfers
    for b in listing["bindings"]:
        if b.get("receipt_id") and not b.get("settled_observed_transfer_id"):
            receipt_source = b.get("receipt_source")
            if funder_addr and receipt_source and receipt_source.lower() == funder_addr.lower():
                listing_settled += listing["amount_atomic"]
            elif receipt_source:
                external_observed += listing["amount_atomic"]
            else:
                unattributed += listing["amount_atomic"]

    # Closure enum
    if listing_settled > 0 and external_observed == 0:
        closure = "settled_from_own_wallet"
    elif listing_settled == 0 and external_observed > 0:
        closure = "paid_by_third_party"
    elif listing_settled > 0 and external_observed > 0:
        closure = "mixed_settlement"
    elif state in ("submitted", "expired-with-submissions", "open"):
        if listing["bindings"]:
            closure = "work_submitted_no_settlement"
        else:
            closure = "no_work_no_settlement"
    elif state == "withdrawn":
        closure = "withdrawn_by_funder"
    else:
        closure = "unknown"

    liability_gap = econ.get("maximum_remaining_liability_atomic", 0)

    economic_interpretation = {
        "listing_settled_paid_atomic": listing_settled,
        "external_observed_paid_atomic": external_observed,
        "unattributed_paid_atomic": unattributed,
        "liability_gap_atomic": liability_gap,
        "closure": closure,
        "closure_note": _closure_note(closure),
    }

    return {
        "listing_id": listing["listing_id"],
        "registry_claim": registry_claim,
        "signed_claims": signed_claims,
        "observed_transfers": observed_transfers,
        "settlement_state": settlement_state,
        "economic_interpretation": economic_interpretation,
    }


def _closure_note(closure: str) -> str:
    notes = {
        "settled_from_own_wallet": "Listing's own named funder_address paid. Registry economics (amount_paid, awards) match. listing_settled counter > 0.",
        "paid_by_third_party": "Payment occurred from a wallet not named on the listing (or listing has no funder_address). Registry amount_paid=0 is correct — listing's own liability pool was not consumed.",
        "mixed_settlement": "Some awards settled from the listing's own wallet, others from external sources.",
        "work_submitted_no_settlement": "Work was submitted and bindings exist, but no payment has been settled. Liability remains outstanding.",
        "no_work_no_settlement": "No submissions or bindings, no settlement activity.",
        "withdrawn_by_funder": "Listing was withdrawn by the funder. Existing work may still be paid through existing bindings.",
        "unknown": "Cannot determine settlement path from available data.",
    }
    return notes.get(closure, notes["unknown"])


def run_fixture(name: str):
    if name not in FIXTURES:
        print(f"Unknown fixture: {name}. Available: {', '.join(FIXTURES.keys())}")
        sys.exit(1)

    listing = FIXTURES[name]
    result = disaggregate(listing)

    print(f"=== {name.upper()} — Five-Layer Disaggregation ===\n")
    print(json.dumps(result, indent=2))
    return result


def main():
    if len(sys.argv) < 2:
        print("Usage: disaggregate.py --fixture <name>")
        print(f"Available fixtures: {', '.join(FIXTURES.keys())}")
        sys.exit(1)

    if sys.argv[1] == "--fixture":
        if len(sys.argv) < 3:
            print("Missing fixture name")
            sys.exit(1)
        run_fixture(sys.argv[2])
    else:
        print("Live API mode not yet implemented in this fixture version")
        print("Use --fixture <name>")
        sys.exit(1)


if __name__ == "__main__":
    main()