"""999 ACK → claim state transitions for the Inbox Rejected lane.

SP6 T3: apply_999_rejections.
For each set response with code R or E, look up the matching claim and
move it to REJECTED. Idempotent on already-rejected claims.
"""
from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Callable

from sqlalchemy.orm import Session

from cyclone.db import Claim, ClaimState


@dataclass
class Apply999Result:
    matched: list[str] = field(default_factory=list)   # claim ids transitioned
    orphans: list[str] = field(default_factory=list)   # PCNs we couldn't resolve


def _build_reason(code: str, segment_error_count: int) -> str:
    parts = [f"999 AK5 set-level {code}"]
    if segment_error_count:
        parts.append(f"{segment_error_count} segment error(s)")
    return "; ".join(parts)


def apply_999_rejections(
    session: Session,
    parsed_999,
    *,
    claim_lookup: Callable[[str], Claim | None],
) -> Apply999Result:
    """For each set response with code R or E, look up the matching claim and
    move it to REJECTED. Idempotent on already-rejected claims.

    Args:
        session: SQLAlchemy session.
        parsed_999: a ParseResult999 (or any object with .set_responses).
        claim_lookup: callable from patient_control_number → Claim or None.

    Returns:
        Apply999Result with lists of matched claim ids and orphan PCNs.
    """
    result = Apply999Result()
    now = datetime.now(timezone.utc)

    for sr in parsed_999.set_responses:
        code = sr.set_accept_reject.code
        if code not in ("R", "E", "X"):
            continue

        claim = claim_lookup(sr.set_control_number)
        if claim is None:
            result.orphans.append(sr.set_control_number)
            continue

        if claim.state == ClaimState.REJECTED:
            # Idempotent: don't double-mutate.
            continue

        claim.state = ClaimState.REJECTED
        claim.state_changed_at = now
        claim.rejected_at = now
        claim.rejection_reason = _build_reason(
            code, len(sr.segment_errors or [])
        )
        result.matched.append(claim.id)

    if result.matched or result.orphans:
        session.commit()

    return result
