"""Pipeline B: NOT_IN_835 visits → fresh 837Ps, batched by (member_id, ISO-week).

One 837P per (member, ISO-week) pair. Per-visit files would be 4,509 SFTP
round-trips; per-batch (no member split) would lose the operator's
ability to track per-member. Member-week is the standard clearinghouse
pattern and matches AxisCare's billing cycle.

The timely-filing gate is applied per visit before batching. The
override flag is the same for the whole batch (a per-batch override).
"""
from collections import defaultdict
from dataclasses import dataclass
from datetime import date
from pathlib import Path

from cyclone.rebill.reconcile import VisitRow
from cyclone.rebill.timely_filing import timely_filing_decision


@dataclass(frozen=True)
class MemberWeekBatch:
    """Pre-emission 837P shape for Pipeline B. The orchestrator's serializer
    adapter converts this to a ClaimOutput for serialize_837.

    Note: frozen=True is shallow — `visits` is a mutable list, so
    `mb.visits.append(x)` works even though `mb.visits = new_list` raises
    FrozenInstanceError. Treat the batch as logically immutable.
    """

    member_id: str
    iso_year: int
    iso_week: int
    visits: list[VisitRow]
    has_overridden_visits: bool


def build_pipeline_b_batches(
    visits: list[VisitRow],
    as_of: date,
    override: bool,
) -> list[MemberWeekBatch]:
    """Group rebillable visits by (member_id, ISO-week).

    Two filters are applied per visit before batching:
      1. timely_filing_decision(...).rebillable — drops past-window
         visits unless `override=True` (per-batch override flag).
      2. implicit: only visits that survive (1) are grouped; the rest
         are surfaced as EXCLUDED_TIMELY_FILING in the summary CSV.

    Returns batches sorted by (member_id, iso_year, iso_week) for
    deterministic filenames downstream.
    """
    by_key: dict[tuple[str, int, int], list[VisitRow]] = defaultdict(list)
    overridden_keys: set[tuple[str, int, int]] = set()
    for v in visits:
        decision = timely_filing_decision(v.date, as_of, override)
        if not decision.rebillable:
            continue
        iso = v.date.isocalendar()
        key = (v.member_id, iso.year, iso.week)
        by_key[key].append(v)
        if decision.override and not decision.within_window:
            overridden_keys.add(key)
    return [
        MemberWeekBatch(
            member_id=member, iso_year=yr, iso_week=wk,
            visits=vs, has_overridden_visits=(member, yr, wk) in overridden_keys,
        )
        for (member, yr, wk), vs in sorted(by_key.items())
    ]


def batch_visits_by_member_week(
    visits: list[VisitRow],
    as_of: date,
    override: bool,
) -> dict[str, list[VisitRow]]:
    """Thin wrapper over build_pipeline_b_batches.

    Returns ``{member_id: [VisitRow, ...]}`` — collapsing the (member,
    ISO-week) batches back down to a per-member visit list. This is the
    shape the orchestrator's summary aggregation expects.

    PROVISIONAL: this helper is defined for the Task 12 orchestrator's
    anticipated use, but has no callers in this branch. If Task 12's
    actual needs differ, this function may be removed or replaced without
    notice.

    This also collapses multiple weeks for the same member into one
    list. If the orchestrator needs week-aware per-member iteration,
    use build_pipeline_b_batches directly.
    """
    batches = build_pipeline_b_batches(visits, as_of=as_of, override=override)
    out: dict[str, list[VisitRow]] = defaultdict(list)
    for b in batches:
        out[b.member_id].extend(b.visits)
    return dict(out)


def write_pipeline_b_files(
    batches: list[MemberWeekBatch],
    out_dir: Path,
    serialize_837_fn,
    tpid: str,
) -> list[Path]:
    """Write one 837P per batch, using HCPF-spec filenames.

    `serialize_837_fn(batch)` returns the X12 string. The 837P envelope
    is pure ASCII so we write it as text. The filename is
    `cyclone.edi.filenames.build_outbound_filename(tpid, '837P')`.
    """
    # Lazy: keep cyclone.rebill.pipeline_b importable without zoneinfo from filenames.
    from cyclone.edi.filenames import build_outbound_filename

    out_dir.mkdir(parents=True, exist_ok=True)
    paths: list[Path] = []
    for b in batches:
        body = serialize_837_fn(b)
        fname = build_outbound_filename(tpid, "837P")
        path = out_dir / fname
        path.write_text(body, encoding="ascii")
        paths.append(path)
    return paths
