"""Visit-to-835 reconciliation on (member_id, procedure, DOS).

Authoritative match key. The 5% tolerance on PAID handles the legitimate
charge-amount differences between AxisCare's per-unit CSV and the 835's
per-SVC breakdown.
"""
from collections import defaultdict
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from enum import Enum
from typing import Iterable

# SvcRow is owned by parse_835_svc (Task 1) — re-exported here so callers
# of `cyclone.rebill.reconcile` only need one import.
from cyclone.rebill.parse_835_svc import SvcRow  # noqa: F401  (re-exported)

PAID_TOLERANCE = Decimal("0.05")            # charge match tolerance
PARTIAL_RATIO  = Decimal("0.95")            # PAID if total_paid >= 95% of billed


class OutcomeCategory(str, Enum):
    PAID = "PAID"
    PARTIAL = "PARTIAL"
    DENIED = "DENIED"
    NOT_IN_835 = "NOT_IN_835"


@dataclass(frozen=True)
class VisitRow:
    date: date
    member_id: str
    procedure: str
    billed: Decimal


@dataclass(frozen=True)
class ReconcileOutcome:
    visit: VisitRow
    category: OutcomeCategory
    unpaid: Decimal
    matched_svc_count: int


def reconcile_visits_to_835(
    visits: Iterable[VisitRow],
    svcs: Iterable[SvcRow],
) -> list[ReconcileOutcome]:
    """Index SVCs by (member_id, procedure, DOS), best-of-N outcome per visit."""
    by_key: dict[tuple[str, str, date], list[SvcRow]] = defaultdict(list)
    for s in svcs:
        by_key[(s.member_id, s.procedure, s.svc_date)].append(s)

    out: list[ReconcileOutcome] = []
    for v in visits:
        cands = by_key.get((v.member_id, v.procedure, v.date), [])
        if not cands:
            out.append(ReconcileOutcome(v, OutcomeCategory.NOT_IN_835, v.billed, 0))
            continue
        total_paid = sum((c.paid for c in cands), Decimal("0"))
        any_paid = any(c.paid > 0 for c in cands)
        if any_paid and total_paid >= v.billed * PARTIAL_RATIO:
            cat, unpaid = OutcomeCategory.PAID, Decimal("0")
        elif any_paid:
            cat, unpaid = OutcomeCategory.PARTIAL, max(Decimal("0"), v.billed - total_paid)
        else:
            cat, unpaid = OutcomeCategory.DENIED, v.billed
        out.append(ReconcileOutcome(v, cat, unpaid, len(cands)))
    return out
