"""Pydantic v2 models for parsed X12 TA1 (Interchange Acknowledgment) files.

A TA1 is the lowest-level ack in the X12 envelope stack — it acknowledges
the *interchange* (ISA/IEA), distinct from a 999 which acknowledges
individual transaction sets. Per X12 005010X231A1 the envelope is:

    ISA… → TA1 → IEA…

The TA1 segment carries six fields:

    TA101  Interchange Control Number        (matches ISA13 of source)
    TA102  Interchange Date                  (matches ISA09 of source, CCYYMMDD)
    TA103  Interchange Time                  (matches ISA10 of source, HHMM)
    TA104  Acknowledgment Code               A / E / R
    TA105  Interchange Note Code             numeric code (see spec table)
    TA106  Interchange Date (ack generated)  CCYYMMDD

Standard ack codes:
    A = Accepted (interchange passed envelope validation)
    E = Accepted with errors (interchange accepted but a TA105 note exists)
    R = Rejected (interchange failed validation; see TA105)

Note codes from the X12 spec (005010X231A1) — only the most common ones
are surfaced as named constants below; the raw value is always preserved.
"""
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


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


class _Base(BaseModel):
    """Shared Pydantic base; matches the 837P / 835 / 999 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


# --------------------------------------------------------------------------- #
# Interchange Acknowledgment
# --------------------------------------------------------------------------- #


# The three valid ack codes per X12 spec. We type-narrow so the API
# surfaces the canonical set; raw values outside this set still parse
# but surface as the literal string.
AckCode = Literal["A", "E", "R"]


class Ta1Ack(_Base):
    """One TA1 segment — the per-interchange acknowledgment.

    ``control_number`` matches the ISA13 of the source interchange, so
    operators can correlate a TA1 back to the 837 batch it acknowledged.
    ``interchange_date`` / ``interchange_time`` mirror the ISA09 / ISA10
    fields of the source.

    ``note_code`` is an optional X12 note (e.g. "006" = "Invalid interchange
    date"). We keep the raw string even when it's not in our named map so
    new codes don't silently surface as ``None``.
    """

    control_number: str
    interchange_date: date | None = None
    interchange_time: str | None = None
    ack_code: AckCode
    note_code: str | None = None
    ack_generated_date: date | None = None


class ParseResultTa1(_Base):
    """One TA1 file = one interchange = one Ta1Ack."""

    envelope: Envelope
    ta1: Ta1Ack
    summary: BatchSummary = Field(default_factory=lambda: BatchSummary(input_file=""))
    # The synthesized source_batch_id (e.g. ``"TA1-<ISA13>"``) so the row
    # can FK to a stable identifier even though we don't have an inbound
    # batch to point at. Mirrors the 999 ``999-<ISA13>`` convention.
    source_batch_id: str = ""


__all__ = ["AckCode", "Ta1Ack", "ParseResultTa1"]