"""Handle an 835 ERA Remittance file (SP27 Task 5).

Lifted verbatim from ``scheduler.py:_handle_835``. The handler owns
its own state, dispatches to ``parse_835`` (raises
``CycloneParseError`` on bad EDI), runs the per-payer 835 validator,
stamps the validation report into ``result.summary``, and persists
the BatchRecord via ``cycl_store.add``.

The 835 handler is the largest of the four because the schema
covers per-claim remittances + CAS adjustments + validation.
``PAYER_FACTORIES_835`` lives in ``cyclone.api`` (it was always
intended to live in ``cyclone.payers`` — a TODO pre-dating SP27 —
but moving it is out of Task 5 scope). We import it lazily inside
``handle`` to avoid a module-load-time cycle; the cyclic risk is
acceptable because api.py also imports scheduler lazily (inside its
lifespan handler).

Two-phase ingest closed in SP27 Task 10: the placeholder
``adjustment_amount`` is overwritten by reconcile in the same DB
session as the insert (before commit), so a reader never sees a
half-reconciled Remittance row. If reconcile raises, the whole
ingest rolls back and the scheduler records the per-file error.

``event_bus`` is best-effort and follows the same TODO(sp27-task-6)
sync/async gap as handle_999 / handle_ta1 / handle_277ca.
"""
from __future__ import annotations

import logging
import uuid
from datetime import datetime, timezone

from cyclone.parsers.exceptions import CycloneParseError
from cyclone.parsers.parse_835 import parse as parse_835
from cyclone.parsers.validator_835 import validate as validate_835
from cyclone.store import BatchRecord
from cyclone.store import store as cycl_store

log = logging.getLogger(__name__)


def handle(
    text: str,
    source_file: str,
) -> tuple[str, int]:
    """Parse an 835, run validation, persist batch + remittances.

    Args:
        text: Raw 835 document bytes (decoded).
        source_file: Filename the 835 came from.

    Returns:
        ``(parser_used, claim_count)`` tuple. ``claim_count`` is the
        number of CLP claims parsed; the BatchRecord's summary's
        ``passed`` / ``failed`` fields are derived from the
        validator's ``report.passed`` flag.

    Raises:
        ValueError: on parser-level failure (wraps CycloneParseError).
            Validation failures don't raise — they're stamped into
            ``summary.passed = 0``.

    SP25: the 835 write path already publishes ``remittance_written``
    via ``CycloneStore.add`` (SP21 split). The handler no longer
    accepts an ``event_bus`` kwarg — the ``ack_received`` publish
    here was dead code anyway (the 835 event name is
    ``remittance_written``, not ``ack_received``).
    """
    # SP36 Task 16: PAYER_FACTORIES_835 moved from ``cyclone.api``
    # to ``cyclone.api_routers._shared`` (cross-router helper). Import
    # from the new home. The lazy import is still needed to avoid a
    # circular import at registry load time (handle_835 is imported
    # by the scheduler during the api lifespan).
    from cyclone.api_routers._shared import PAYER_FACTORIES_835

    config = PAYER_FACTORIES_835["co_medicaid_835"]()
    try:
        result = parse_835(text, config, input_file=source_file)
    except CycloneParseError as exc:
        raise ValueError(f"835 parse error: {exc}") from exc

    # Validation report (mirrors the API endpoint).
    report = validate_835(result, config)
    n = len(result.claims)
    if report.passed:
        passed, failed, failed_claim_ids = n, 0, []
    else:
        passed, failed, failed_claim_ids = 0, n, [
            c.payer_claim_control_number for c in result.claims
        ]
    result = result.model_copy(update={
        "validation": report,
        "summary": result.summary.model_copy(update={
            "passed": passed,
            "failed": failed,
            "failed_claim_ids": failed_claim_ids,
        }),
    })

    rec = BatchRecord(
        id=uuid.uuid4().hex,
        kind="835",
        input_filename=source_file,
        parsed_at=datetime.now(timezone.utc),
        result=result,
    )
    cycl_store.add(rec)

    return ("parse_835", len(result.claims))
