"""
worklist_readiness.py
Signal — STTIL Solutions

Orchestrates dedup + readiness for the live pipeline (Phase 2a coexistence,
docs/build-plans/01-readiness-model-wiring.md). The readiness verdict AUGMENTS
the coverage_calculator output: timing urgency (CoverageFlag) and documentation
completeness (LineStatus) are separate axes, combined at the worklist level.

This is the ONLY module that imports both dedup and readiness for the API.
core/coverage_calculator.py must never import this module (dedup.py imports
FROM coverage_calculator; the reverse edge would be circular).

LINE MEMBERSHIP IS DECIDED BY DEDUP, NEVER RE-DERIVED FROM A ROW. A row's own
plan_type can differ from its dedup group's MERGED plan_type (conflicting
groups route to None — never guessed), so resolving a row's verdict from its
own plan_type can attach the WRONG line's verdict (a reachable false green,
caught in independent review 2026-07-06). All row-level resolution goes
through the (patient_id, device_type, DOS-as-date) group key via
ReadinessIndex, which records the dedup output itself.

Non-gradeable lines (non-CGM devices) are never graded: they carry no verdict,
matching the locked design (preserved and displayed, not graded).

PHI CONTRACT: patient_id is the sole crosswalk key; it is hashed in any log
line (dedup._hash_pid pattern). No names, SSNs, DOBs, or contact fields.
"""

from __future__ import annotations

import hashlib
from dataclasses import dataclass, field
from datetime import date
from typing import Optional

from core.coverage_calculator import ShipmentRecord
from core.dedup import (
    UNKNOWN_PLAN_TYPE,
    CoverageLine,
    MergedShipment,
    _as_date,
    assign_to_coverage_lines,
    dedup,
)
from core.readiness import ReadinessVerdict, evaluate_readiness

# (patient_id, device_type, plan_type_id) — mirrors CoverageLineKey.
LineKey = tuple[str, str, str]
# (patient_id, device_type, DOS-as-date) — the locked dedup group key.
GroupKey = tuple[str, str, date]


@dataclass
class ReadinessIndex:
    """Dedup-derived resolution index for one upload batch.

    by_line:         verdicts per gradeable coverage line
    lines:           every CoverageLine (graded ones carry line_status)
    line_by_group:   dedup group -> the line that group's shipment belongs to
    merged_by_group: dedup group -> its MergedShipment (display-field truth)
    """

    by_line: dict[LineKey, ReadinessVerdict] = field(default_factory=dict)
    lines: dict[LineKey, "CoverageLine"] = field(default_factory=dict)
    line_by_group: dict[GroupKey, LineKey] = field(default_factory=dict)
    merged_by_group: dict[GroupKey, MergedShipment] = field(default_factory=dict)


def _confirmed_for(
    patient_id: str, confirmed_visits: dict[str, date]
) -> Optional[date]:
    """Look up a staff-confirmed visit date by hashed patient_id.

    confirmed_visits is keyed by sha256(patient_id) hex digest, the same keying
    coverage_calculator.calculate_batch uses.
    """
    return confirmed_visits.get(
        hashlib.sha256(patient_id.encode()).hexdigest()
    )


def build_readiness_index(
    records: list[ShipmentRecord],
    confirmed_visits: Optional[dict[str, date]] = None,
    overrides: Optional[list] = None,
) -> ReadinessIndex:
    """Dedup records into coverage lines, apply staff overrides to the
    grading inputs (P4: swo/pa device-scoped; staff knowledge wins over the
    CSV cell), grade each gradeable line, and record the group->line
    membership map so row-level resolution never re-derives it.

    Grades the CURRENT shipment only (latest DOS per line — locked rule);
    prior shipments are collapsed history. Non-gradeable lines are skipped.
    Visit overrides arrive via confirmed_visits (patient-scoped fan-out by
    construction).
    """
    confirmed_visits = confirmed_visits or {}
    merged = dedup(records)
    lines = assign_to_coverage_lines(merged)

    if overrides:
        from core.overrides import apply_overrides

        apply_overrides(lines, overrides)

    index = ReadinessIndex()
    for key, line in lines.items():
        line_key = (key.patient_id, key.device_type, key.plan_type_id)
        index.lines[line_key] = line
        if not line.gradeable:
            continue
        cur = line.current_shipment
        verdict = evaluate_readiness(
            line.plan_type if line.plan_type != UNKNOWN_PLAN_TYPE else None,
            csv_swo_status=cur.csv_swo_status,
            csv_visit_date=cur.csv_visit_date,
            confirmed_visit_date=_confirmed_for(
                key.patient_id, confirmed_visits
            ),
            csv_pecos_verified=cur.csv_pecos_verified,
            csv_pa_status=cur.csv_pa_status,
            csv_diagnosis_on_file=cur.csv_diagnosis_on_file,
        )
        index.by_line[line_key] = verdict
        # The grading step owns line_status (dedup leaves it None) — the
        # rollup (P3) reads it from the line.
        line.line_status = verdict.line_status.value

    for m in merged:
        gkey: GroupKey = (m.patient_id, m.device_type, m.shipment_date)
        index.merged_by_group[gkey] = m
        # Membership from the MERGED plan_type — the same rule
        # assign_to_coverage_lines used, so this cannot diverge from it.
        index.line_by_group[gkey] = (
            m.patient_id,
            m.device_type,
            m.plan_type or UNKNOWN_PLAN_TYPE,
        )

    return index


def readiness_by_line(
    records: list[ShipmentRecord],
    confirmed_visits: Optional[dict[str, date]] = None,
) -> dict[LineKey, ReadinessVerdict]:
    """Line verdicts only (convenience wrapper over build_readiness_index)."""
    return build_readiness_index(records, confirmed_visits).by_line


def resolve_group(
    index: ReadinessIndex,
    patient_id: str,
    device_type: str,
    shipment_date,
) -> tuple[Optional[ReadinessVerdict], Optional[LineKey], Optional[MergedShipment]]:
    """Resolve a result row to (verdict, line_key, merged_shipment) through the
    dedup group key. The only sanctioned row-level resolution path."""
    gkey: GroupKey = (patient_id, device_type, _as_date(shipment_date))
    merged = index.merged_by_group.get(gkey)
    line_key = index.line_by_group.get(gkey)
    verdict = index.by_line.get(line_key) if line_key else None
    return verdict, line_key, merged


def merged_to_record(m: MergedShipment) -> ShipmentRecord:
    """Synthetic ShipmentRecord from a dedup merge, used when the exact
    row lookup misses (order-numbered CSVs: CoverageResult carries no
    order_number). Field policy matches dedup exactly (latest-non-null doc
    fields, collected order numbers), so the legacy doc_state and the
    readiness verdict grade from the SAME values and cannot contradict.
    order_number/hcpcs join the collected display lists — display-only fields.
    """
    return ShipmentRecord(
        patient_id=m.patient_id,
        device_type=m.device_type,
        shipment_date=m.shipment_date,
        quantity=m.total_quantity,
        payer=m.payer,
        component=m.components[0] if m.components else "sensor",
        csv_visit_date=m.csv_visit_date,
        csv_swo_status=m.csv_swo_status,
        csv_pecos_verified=m.csv_pecos_verified,
        csv_pa_status=m.csv_pa_status,
        csv_diagnosis_on_file=m.csv_diagnosis_on_file,
        csv_transfer_from=m.csv_transfer_from,
        order_number=", ".join(m.order_numbers) if m.order_numbers else None,
        hcpcs=", ".join(m.hcpcs_codes) if m.hcpcs_codes else None,
        plan_type=m.plan_type,
    )


def readiness_for_record(
    record: ShipmentRecord, confirmed_visit_date: Optional[date] = None
) -> ReadinessVerdict:
    """Grade a single record as its own line (the Confirm Visit recompute path)."""
    return evaluate_readiness(
        record.plan_type,
        csv_swo_status=record.csv_swo_status,
        csv_visit_date=record.csv_visit_date,
        confirmed_visit_date=confirmed_visit_date,
        csv_pecos_verified=record.csv_pecos_verified,
        csv_pa_status=record.csv_pa_status,
        csv_diagnosis_on_file=record.csv_diagnosis_on_file,
    )


def readiness_stats(verdicts: dict[LineKey, ReadinessVerdict]) -> dict:
    """Line-level counts per LineStatus value, for measurable live verification.

    Callers pass only the lines actually reachable from response rows, so the
    stats always reconcile with the records in the same response.
    """
    counts: dict[str, int] = {}
    for verdict in verdicts.values():
        label = verdict.line_status.value
        counts[label] = counts.get(label, 0) + 1
    counts["lines_total"] = len(verdicts)
    return counts
