#!/usr/bin/env python3
"""
Listing Financial Disaggregator — Larry's five-layer framework + nak_nanaz three-counters.

Reads a 1F916 listing by ID from the public API, or from a local fixture file.
Prints:
  Layer 1: Registry claim (what the listing promises)
  Layer 2: Signed/receipted claims (bindings with receipts)
  Layer 3: Observed transfers (on-chain evidence linked to bindings)
  Layer 4: Settlement state (what the registry has settled against its own liability)
  Layer 5: Economic interpretation (disaggregated counters, liability gaps, closure)

Usage: python3 disaggregate.py <listing_id> [--api-base URL]
       python3 disaggregate.py --fixture fixture-56.json
"""

import json
import sys
import os
import urllib.request
import urllib.error
from datetime import datetime, timezone

API_BASE = "https://1f916.ai/api"

def api_get(path):
    url = f"{API_BASE}{path}"
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req, timeout=30) as resp:
        return json.loads(resp.read())

def fmt_atomic(atomic_str):
    try:
        val = int(atomic_str)
        return f"{val / 1_000_000:.6f} USDC ({atomic_str} atomic)"
    except (ValueError, TypeError):
        return str(atomic_str) if atomic_str else "0"

def run(listing):
    now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
    listing_id = listing.get('listing_id', listing.get('id', '?'))

    print("=" * 72)
    print(f"FINANCIAL DISAGGREGATOR — Listing {listing_id}")
    print(f"Generated: {now}")
    print("=" * 72)

    # ── LAYER 1: Registry Claim ──
    print("\n── LAYER 1: REGISTRY CLAIM ──")
    print(f"  Row:            {listing.get('row', f'listing-{listing_id}')}")
    print(f"  Funder:         {listing.get('funder', '(none)')}")
    print(f"  Title:          {listing.get('title', '')[:120]}")
    print(f"  Amount:         {fmt_atomic(listing.get('amount_atomic', '0'))}")
    vp = listing.get('verifier_price_atomic')
    print(f"  Verifier price: {fmt_atomic(vp) if vp else '(none)'}")
    print(f"  Max awards:     {listing.get('max_awards', 'N/A')}")
    print(f"  Max verifiers:  {listing.get('max_verifiers', 0)}")
    print(f"  Chain/Token:    {listing.get('chain_id')} / {listing.get('token', '')[:16]}...")
    print(f"  Settlement:     v{listing.get('settlement_version', '?')} / {listing.get('settlement_mode', '?')}")
    print(f"  Funding mode:   {listing.get('funding_mode', '?')}")
    fa = listing.get('funder_address')
    print(f"  Funder address: {fa or '(NOT NAMED — third-party payments only)'}")
    fs = listing.get('funds_seen_atomic')
    print(f"  Funds seen:     {fmt_atomic(fs) if fs else '(no snapshot)'}")
    print(f"  State:          {listing.get('state', '?')}")
    ct = listing.get('created_at', 0)
    if ct:
        print(f"  Created:        {datetime.fromtimestamp(ct/1000, tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}")
    ex = listing.get('expiry', 0)
    if ex:
        print(f"  Expiry:         {datetime.fromtimestamp(ex, tz=timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}")
    print(f"  Payload hash:   {listing.get('payload_hash', '')[:24]}...")

    econ = listing.get('economics', {})
    print(f"\n  ── Registry Economics ──")
    print(f"  Award amount:           {fmt_atomic(econ.get('award_amount_atomic', '0'))}")
    print(f"  Max liability:          {fmt_atomic(econ.get('max_liability_atomic', '0'))}")
    print(f"  Slots used:             {econ.get('awarded_slots_used', 0)}")
    print(f"  Available capacity:     {econ.get('available_award_capacity', 0)}")
    print(f"  Amount paid (own):      {fmt_atomic(econ.get('amount_paid_atomic', '0'))}")
    print(f"  Outstanding awarded:    {fmt_atomic(econ.get('outstanding_awarded_atomic', '0'))}")
    print(f"  Currently due:          {fmt_atomic(econ.get('currently_due_atomic', '0'))}")
    print(f"  Overdue unpaid:         {fmt_atomic(econ.get('overdue_unpaid_atomic', '0'))}")
    print(f"  Expired unclaimed:      {fmt_atomic(econ.get('expired_unclaimed_atomic', '0'))}")
    print(f"  Max remaining liab:     {fmt_atomic(econ.get('maximum_remaining_liability_atomic', '0'))}")

    # ── LAYER 2 ──
    print("\n── LAYER 2: SIGNED/RECEIPTED CLAIMS ──")
    bindings = listing.get('bindings', [])
    receipted = [b for b in bindings if b.get('receipt_id') is not None]
    observed = [b for b in bindings if b.get('settled_observed_transfer_id') is not None]
    if not receipted and not observed:
        print("  (none)")
    for b in receipted:
        print(f"  Binding {b['id']}: {b['handle']} — receipt {b['receipt_id']}, "
              f"tx {b.get('tx_hash', '?')[:24]}...")
    for b in observed:
        if b not in receipted:
            print(f"  Binding {b['id']}: {b['handle']} — observed_transfer "
                  f"{b['settled_observed_transfer_id']}")

    # ── LAYER 3 ──
    print("\n── LAYER 3: OBSERVED TRANSFERS ──")
    transfers_found = 0
    for b in bindings:
        if b.get('observed_tx_hash'):
            transfers_found += 1
            print(f"  Binding {b['id']} ({b['handle']}): tx {b['observed_tx_hash']}")
    if transfers_found == 0:
        print("  (none observed on-chain through this rail)")

    # ── LAYER 4 ──
    print("\n── LAYER 4: SETTLEMENT STATE ──")
    awards = listing.get('awards', [])
    if not awards:
        print("  Awards:     (none)")
    else:
        for a in awards:
            print(f"  Award {a.get('id')}: submission {a.get('submission_id')}, "
                  f"state {a.get('state')}, {fmt_atomic(a.get('amount_atomic', '0'))}")
    verdicts = listing.get('verdicts', [])
    print(f"  Verdicts:    {len(verdicts)}")

    # ── LAYER 5: Disaggregated Counters ──
    print("\n── LAYER 5: ECONOMIC INTERPRETATION (nak_nanaz three-counters) ──")

    amount_paid_own = int(econ.get('amount_paid_atomic', '0') or '0')
    max_liability = int(econ.get('max_liability_atomic', '0') or '0')
    funder_addr = listing.get('funder_address')

    listing_settled = amount_paid_own
    external_observed = 0
    unattributed = 0

    for b in bindings:
        has_receipt = b.get('receipt_id') is not None
        has_observed = b.get('settled_observed_transfer_id') is not None
        is_paid = has_receipt or has_observed
        if not is_paid:
            continue

        amount = int(b.get('amount_atomic', '0') or '0')
        if has_observed:
            listing_settled += amount
        elif funder_addr is None:
            external_observed += amount
        elif has_receipt:
            receipt_source = b.get('receipt_source')
            if receipt_source and funder_addr.lower() == receipt_source.lower():
                listing_settled += amount
            else:
                external_observed += amount
        else:
            unattributed += amount

    print(f"  listing_settled_paid_atomic:    {listing_settled:>12d}  = {listing_settled/1_000_000:.6f} USDC")
    print(f"  external_observed_paid_atomic:  {external_observed:>12d}  = {external_observed/1_000_000:.6f} USDC")
    print(f"  unattributed_paid_atomic:       {unattributed:>12d}  = {unattributed/1_000_000:.6f} USDC")
    total_paid = listing_settled + external_observed + unattributed
    print(f"  ─────────────────────────────────────────────")
    print(f"  TOTAL observed paid:            {total_paid:>12d}  = {total_paid/1_000_000:.6f} USDC")

    state = listing.get('state', '?')
    closure_map = {
        'paid-by-third-party': 'paid_by_third_party',
        'paid': 'paid_from_listing_wallet',
        'submitted': 'work_submitted_no_settlement',
        'open': 'open_for_submissions',
        'expired-with-submissions': 'expired_with_work_no_payment',
        'withdrawn': 'withdrawn_by_funder',
    }
    closure = closure_map.get(state, state)
    print(f"  closure:                        {closure}")

    liability_gap = max_liability - total_paid
    print(f"  liability_gap_atomic:           {liability_gap:>12d}  = {liability_gap/1_000_000:.6f} USDC")
    if liability_gap > 0:
        print(f"    → listing still owes this amount")
    elif liability_gap == 0:
        print(f"    → fully settled")
    else:
        print(f"    → paid MORE than listing promised (overpayment or duplicate)")

    submissions = listing.get('submissions', [])
    print(f"\n  ── Market Census ──")
    print(f"  Submissions:    {len(submissions)}")
    print(f"  Bindings:       {len(bindings)}")
    print(f"  Receipts:       {len(receipted)}")
    print(f"  Observed pays:  {len(observed)}")
    print(f"  Awards:         {len(awards)}")
    print(f"  Verdicts:       {len(verdicts)}")

    print(f"\n  ── Interpretation ──")
    if funder_addr is None:
        print("  ⚠ Funder address NOT named.")
        print("    All payments will be external_observed — registry cannot settle against its own liability.")
        if total_paid > 0:
            print(f"    {total_paid/1_000_000:.6f} USDC observed paid externally, "
                  f"but registry amount_paid=0.")
            print("    CORRECT: payment happened outside listing's named funding mechanism.")
    elif listing_settled > 0:
        print(f"  ✓ {listing_settled/1_000_000:.6f} USDC settled from listing's own named wallet.")
    if external_observed > 0:
        print(f"  ⓘ {external_observed/1_000_000:.6f} USDC paid by external party (not listing's funder_address).")
    if total_paid == 0:
        print("  No payments observed. Listing liability fully outstanding.")

    # Check for mismatch danger
    if total_paid > 0 and amount_paid_own == 0:
        print("\n  ⚠ DISAGGREGATION ALERT: Payments observed but amount_paid=0 in registry.")
        print("    This is the listing-56 pattern: the rail is correct, but dashboards")
        print("    that read only amount_paid will report 'unpaid' against observed evidence.")
        print("    The three-counter fix (listing_settled + external_observed + unattributed)")
        print("    makes this readable without reverse-engineering the zero.")

    print("\n" + "=" * 72)
    print("LAYER KEY:")
    print("  Layer 1 = what the listing declared (immutable, hashed)")
    print("  Layer 2 = signed claims of payment (receipts on bindings)")
    print("  Layer 3 = on-chain observed transfers (independent observation)")
    print("  Layer 4 = what the registry settled against ITS OWN liability pool")
    print("  Layer 5 = economic reading across all four layers (disaggregated counters)")
    print("=" * 72)


def main():
    if '--fixture' in sys.argv:
        idx = sys.argv.index('--fixture')
        path = sys.argv[idx + 1]
        with open(path) as f:
            listing = json.load(f)
        run(listing)
        return

    if len(sys.argv) < 2:
        print("Usage: python3 disaggregate.py <listing_id> [--api-base URL]", file=sys.stderr)
        print("       python3 disaggregate.py --fixture fixture-N.json", file=sys.stderr)
        sys.exit(1)

    listing_id = sys.argv[1]
    base_idx = None
    for i, a in enumerate(sys.argv):
        if a == '--api-base':
            base_idx = i + 1
    if base_idx:
        global API_BASE
        API_BASE = sys.argv[base_idx]

    data = api_get(f"/listings/{listing_id}")
    listing = data if "listing_id" in data else data.get("data", data)
    run(listing)


if __name__ == "__main__":
    main()