#!/usr/bin/env python3
"""Original LockedIn Labs exercise: reconcile synthetic case and credit records.

Python 3.11+, standard library only. No network, database, payment or case writes.
The fixtures are trusted teaching inputs, not a production authorization adapter.
The scoped extract contains credit records for ONE case, not a complete ledger.
"""
from __future__ import annotations

import argparse
import json
from dataclasses import asdict, dataclass, replace


@dataclass(frozen=True)
class Case:
    workspace: str = "SYNTHETIC-WORKSPACE"
    case_id: str = "SYNTHETIC-CASE-104"
    account_id: str = "SYNTHETIC-ACCOUNT-7"
    transaction_id: str = "SYNTHETIC-DEBIT-81"
    expected_minor: int = 12500
    currency: str = "USD"
    expected_kind: str = "provisional"
    investigation_state: str = "open"


@dataclass(frozen=True)
class Entry:
    entry_id: str = "SYNTHETIC-CREDIT-9"
    workspace: str = "SYNTHETIC-WORKSPACE"
    case_id: str = "SYNTHETIC-CASE-104"
    account_id: str = "SYNTHETIC-ACCOUNT-7"
    transaction_id: str = "SYNTHETIC-DEBIT-81"
    amount_minor: int = 12500
    currency: str = "USD"
    kind: str = "provisional"
    status: str = "posted"
    direction: str = "credit"


@dataclass(frozen=True)
class Extract:
    entries: tuple[Entry, ...] = (Entry(),)
    complete: bool = True
    reference: str = "SYNTHETIC-EXTRACT-4"


def reconcile(case: Case, extract: Extract, *, omit_credit_kind: bool = False) -> dict:
    """Compare a case with its scoped credit extract. Never change either input.

    A match establishes agreement within this snapshot, not settlement, case
    closure or fulfilled customer communications. First failing rule wins.
    """
    trace: list[str] = []
    duplicates = 0

    def result(state: str, reason: str, owner: str, action: str,
               matched_ids: tuple[str, ...] = (), statement: str | None = None) -> dict:
        return {
            "state": state, "reason": reason, "owner": owner,
            "next_action": action, "supported_statement": statement,
            "matched_entry_ids": list(matched_ids),
            "duplicate_deliveries": duplicates,
            "extract_reference": extract.reference,
            "investigation_state": case.investigation_state,
            "trace": trace + [reason],
        }

    if not extract.reference or extract.complete is not True:
        return result("incomplete", "incomplete_extract", "integration_operations",
                      "Obtain the complete scoped extract; do not infer a missing credit.")
    trace.append("extract_complete")
    if (type(case.expected_minor) is not int or case.expected_minor <= 0
            or case.currency != "USD"
            or case.expected_kind not in {"provisional", "final"}
            or case.investigation_state not in {"open", "final_credit_confirmed"}
            or not all((case.workspace, case.case_id, case.account_id, case.transaction_id))):
        return result("needs_reconciliation", "unsupported_case", "case_operations",
                      "Resolve the case contract. This exercise supports positive USD credits only.")
    if case.expected_kind == "final" and case.investigation_state != "final_credit_confirmed":
        return result("needs_reconciliation", "case_state_conflict", "case_operations",
                      "Establish the authorized final case outcome before expecting final credit.")
    trace.append("case_contract_valid")

    unique: dict[str, Entry] = {}
    for entry in extract.entries:
        if any(getattr(entry, key) != getattr(case, key)
               for key in ("workspace", "case_id", "account_id", "transaction_id")):
            return result("needs_reconciliation", "reference_mismatch", "integration_operations",
                          "Repair the scoped record mapping; equal amounts do not identify a transaction.")
        if not entry.entry_id:
            return result("needs_reconciliation", "missing_entry_id", "ledger_operations",
                          "Obtain a stable ledger entry identity before deduplicating deliveries.")
        if entry.entry_id in unique:
            if unique[entry.entry_id] != entry:
                return result("needs_reconciliation", "conflicting_entry", "ledger_operations",
                              "Resolve different payloads using the same entry ID; do not take the last arrival.")
            duplicates += 1
        else:
            unique[entry.entry_id] = entry
    trace.append("references_checked_and_exact_deliveries_deduplicated")
    if not unique:
        return result("needs_reconciliation", "missing_credit", "ledger_operations",
                      "Investigate the expected credit missing from this complete scoped extract.")
    if len(unique) > 1:
        return result("needs_reconciliation", "multiple_credit_entries", "ledger_operations",
                      "Investigate distinct entries. Do not erase them or net their amounts automatically.")
    entry = next(iter(unique.values()))
    if type(entry.amount_minor) is not int or entry.amount_minor <= 0 or entry.direction != "credit":
        return result("needs_reconciliation", "invalid_credit", "ledger_operations",
                      "Resolve the amount representation or direction; do not coerce it into a credit.")
    if entry.currency != case.currency:
        return result("needs_reconciliation", "currency_mismatch", "ledger_operations",
                      "Obtain matching currency evidence. The exercise does not perform FX conversion.")
    if entry.amount_minor != case.expected_minor:
        return result("needs_reconciliation", "amount_mismatch", "ledger_operations",
                      "Explain the difference. This one-credit exercise does not assume fees or partial credits.")
    if entry.kind not in {"provisional", "final"}:
        return result("needs_reconciliation", "unknown_credit_kind", "ledger_operations",
                      "Resolve the unfamiliar business meaning before extending the mapping.")
    if not omit_credit_kind and entry.kind != case.expected_kind:
        return result("needs_reconciliation", "credit_kind_mismatch", "case_and_ledger_operations",
                      "Resolve provisional versus final evidence with both owners.")
    trace.append("credit_kind_check_omitted" if omit_credit_kind else "amount_currency_and_kind_match")
    if entry.status == "pending":
        return result("await_posting", "credit_not_posted", "ledger_operations",
                      "Recheck the authoritative posting state; do not report a posted credit.")
    if entry.status != "posted":
        return result("needs_reconciliation", "unsupported_posting_state", "ledger_operations",
                      "Inspect reversal or unfamiliar state; do not infer an effective posted credit.")
    trace.append("credit_posted")
    statement = f"Posted {entry.kind} USD credit matches the case expectation in this extract."
    return result("matched", "matched_snapshot", "case_operations",
                  "Retain the case's investigation state and check outstanding communications separately.",
                  (entry.entry_id,), statement)


def scenarios() -> list[tuple[str, Case, Extract]]:
    case, entry, extract = Case(), Entry(), Extract()
    return [
        ("matched-provisional", case, extract),
        ("wrong-transaction", case, replace(extract, entries=(replace(entry, transaction_id="SYNTHETIC-DEBIT-82"),))),
        ("amount-mismatch", case, replace(extract, entries=(replace(entry, amount_minor=12499),))),
        ("currency-mismatch", case, replace(extract, entries=(replace(entry, currency="EUR"),))),
        ("pending-credit", case, replace(extract, entries=(replace(entry, status="pending"),))),
        ("wrong-credit-kind", case, replace(extract, entries=(replace(entry, kind="final"),))),
        ("missing-credit", case, replace(extract, entries=())),
        ("repeated-delivery", case, replace(extract, entries=(entry, entry))),
        ("conflicting-entry", case, replace(extract, entries=(entry, replace(entry, amount_minor=10000)))),
        ("two-credit-entries", case, replace(extract, entries=(entry, replace(entry, entry_id="SYNTHETIC-CREDIT-10")))),
        ("partial-extract", case, replace(extract, complete=False, entries=())),
        ("reversed-credit", case, replace(extract, entries=(replace(entry, status="reversed"),))),
        ("matched-final", replace(case, expected_kind="final", investigation_state="final_credit_confirmed"),
         replace(extract, entries=(replace(entry, kind="final"),))),
    ]


def report(*, omit_credit_kind: bool = False) -> dict:
    return {
        "synthetic": True,
        "mode": "deliberately_broken_credit_kind" if omit_credit_kind else "guarded",
        "scope": "Read-only comparison of one case and its scoped USD credit extract",
        "cases": [
            {"name": name, "input": {"case": asdict(case), "extract": asdict(extract)},
             "result": reconcile(case, extract, omit_credit_kind=omit_credit_kind)}
            for name, case, extract in scenarios()
        ],
    }


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--json", action="store_true", help="Print inputs, decisions and traces as JSON")
    parser.add_argument("--omit-credit-kind", action="store_true", help="Deliberately break the credit-kind comparison")
    args = parser.parse_args()
    data = report(omit_credit_kind=args.omit_credit_kind)
    if args.json:
        print(json.dumps(data, indent=2))
    else:
        print(f"SYNTHETIC RECONCILIATION / {data['mode']} / no writes")
        for item in data["cases"]:
            outcome = item["result"]
            print(f"{item['name']:23} {outcome['state']:21} {outcome['reason']}")


if __name__ == "__main__":
    main()
