"""Pydantic v2 models for parsed 277CA (Claim Acknowledgment) files.

Mirrors the X12 005010X214 segment shape (HCPF sends this transaction
back after we submit an 837P batch):

- ``AcknowledgmentHeader`` (BHT) — beginning of hierarchical transaction
- ``ClaimStatus`` (STC + REF*1K + REF*EJ + AMT*YU + DTP*472) — one per
  payer-acknowledged claim. Carries the category/status code and the
  payer claim control number used to match back to a Cyclone claim row.
- ``ParseResult277CA`` — top-level envelope + per-claim status list.

Why this lean shape: the 277CA is a status-reporting transaction, not a
claim. We surface just enough information to (a) match each STC row back
to a Cyclone claim by ``payer_claim_control_number`` (REF*1K) and
(b) classify it as accepted / pended / rejected / paid so the Inbox
Payer-Rejected lane can light up.

Per HCPF X12 File Naming Standards, the inbound filename uses ``277``
in the file_type slot (the transaction-set id ``277CA`` is conveyed in
the ST segment itself, not in the filename).
"""
from __future__ import annotations

from datetime import date
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, model_serializer

from cyclone.parsers.models import BatchSummary, Envelope, ValidationIssue, ValidationReport


# --------------------------------------------------------------------------- #
# Shared base
# --------------------------------------------------------------------------- #


class _Base(BaseModel):
    """Shared Pydantic base; matches the 999 / 835 / 270 / 271 models for JSON consistency."""

    model_config = ConfigDict(extra="ignore", str_strip_whitespace=True)

    @model_serializer(mode="wrap")
    def _serialize(self, handler):  # type: ignore[no-untyped-def]
        data = handler(self)
        for key, value in data.items():
            if isinstance(value, date):
                data[key] = value.isoformat()
        return data


# --------------------------------------------------------------------------- #
# 277CA segment shapes
# --------------------------------------------------------------------------- #


# Status category codes per X12 005010X214 §1.4.2 STC01-1.
# We categorize each STC into a small set of lanes the Inbox cares about.
ACCEPTED_CODES = {"A1", "A2", "A3"}
REJECTED_CODES = {"A4", "A6", "A7"}
PENDED_CODES = {"A8", "A9"}
PAID_CODES = {"P1", "P2", "P3", "P4", "P5"}


def classify_status_code(code: str) -> Literal["accepted", "rejected", "pended", "paid", "unknown"]:
    """Map an STC01-1 category code to an Inbox lane label.

    Unknown codes (rare; new payer-specific codes) become ``"unknown"``
    so the parser doesn't raise — operators can still see them in the
    277CA detail view.
    """
    code = (code or "").strip().upper()
    if code in ACCEPTED_CODES:
        return "accepted"
    if code in REJECTED_CODES:
        return "rejected"
    if code in PENDED_CODES:
        return "pended"
    if code in PAID_CODES:
        return "paid"
    return "unknown"


class AcknowledgmentHeader(_Base):
    """BHT — Beginning of Hierarchical Transaction.

    BHT01 = hierarchical_structure_code (e.g. "0085" for Claim Ack).
    BHT02 = transaction_set_purpose_code (e.g. "08" for Status).
    BHT03 = reference_identification (often the submitter batch id).
    BHT04 = transaction_set_creation_date (CCYYMMDD).
    BHT05 = transaction_set_creation_time (HHMM).
    BHT06 = transaction_type_code (e.g. "TH").
    """

    hierarchical_structure_code: str
    transaction_set_purpose_code: str
    reference_identification: str | None = None
    transaction_set_creation_date: date | None = None
    transaction_set_creation_time: str | None = None
    transaction_type_code: str | None = None


class ClaimStatus(_Base):
    """STC + REF*1K + REF*EJ + AMT*YU + DTP*472 — one payer-acknowledged claim.

    STC01 is a composite of three elements: ``category_code:status_code:entity_identifier``.
    HCPF only populates the category code (e.g. ``A3:19:PR``); the parser
    surfaces ``category_code`` and ``status_code`` separately.

    ``payer_claim_control_number`` is the REF*1K value the payer assigned
    (or echoed back) for the claim. This is what matches back to a
    Cyclone claim row (we store it on Claim.payer_claim_control_number
    during 837 serialize).

    ``amount`` is the AMT*YU value (total claim charge amount
    acknowledged by the payer). Optional — HCPF omits it for non-claim
    statuses (e.g. subscriber-level pends).
    """

    status_code: str
    status_description: str | None = None
    entity_identifier: str | None = None
    status_effective_date: date | None = None
    status_action_code: str | None = None
    total_claim_charge_amount: float | None = None
    payer_claim_control_number: str | None = None
    billing_provider_tax_id: str | None = None
    service_date: date | None = None
    classification: Literal["accepted", "rejected", "pended", "paid", "unknown"] = "unknown"


class ParseResult277CA(_Base):
    """Top-level parsed 277CA document.

    Multiple claims can be acknowledged in a single 277CA; the parser
    flattens the HL hierarchy (20→21→19→PT) into one ClaimStatus per
    Patient-level HL. Subscriber- and provider-level statuses (when no
    Patient HL exists) are also captured under ``unscoped_statuses``
    for operator visibility.
    """

    envelope: Envelope
    bht: AcknowledgmentHeader
    claim_statuses: list[ClaimStatus] = Field(default_factory=list)
    unscoped_statuses: list[ClaimStatus] = Field(default_factory=list)
    summary: BatchSummary


__all__ = [
    "ACCEPTED_CODES",
    "AcknowledgmentHeader",
    "ClaimStatus",
    "PAID_CODES",
    "PENDED_CODES",
    "ParseResult277CA",
    "REJECTED_CODES",
    "classify_status_code",
]


# Silence unused-import lints.
_ = (BaseModel, ValidationIssue, ValidationReport)
