"""Pydantic models for parsed-batch records.

``BatchRecord`` is the union type; ``BatchRecord837`` and ``BatchRecord835``
narrow ``result`` to the parser-specific output types. Construction of
``BatchRecord(kind="837p", ...)`` dispatches to ``BatchRecord837`` via
``__new__`` so isinstance checks downstream narrow ``result`` correctly.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, model_validator

from cyclone.parsers.models import ParseResult
from cyclone.parsers.models_835 import ParseResult835

BatchKind = Literal["837p", "835"]


# ---------------------------------------------------------------------------
# BatchRecord: value object preserved from sub-project 1.
# ---------------------------------------------------------------------------


class BatchRecord(BaseModel):
    """One parsed file, with a stable uuid4 id and the full ParseResult.

    ``result`` is a union: ``ParseResult`` for ``kind="837p"`` and
    ``ParseResult835`` for ``kind="835"``. The concrete subclasses
    ``BatchRecord837`` and ``BatchRecord835`` narrow those fields, so
    callers that want type-checked access should use them and check
    ``isinstance`` rather than pattern-matching on ``kind``.

    Constructing ``BatchRecord(kind="837p", ...)`` dispatches to
    ``BatchRecord837``; ``kind="835"`` dispatches to ``BatchRecord835``.
    This lets the union-member narrowing work transparently.
    """

    model_config = ConfigDict(extra="ignore")

    id: str
    kind: BatchKind
    input_filename: str
    parsed_at: datetime  # tz-aware UTC
    result: ParseResult | ParseResult835

    def __new__(
        cls, *args: Any, **kwargs: Any,
    ) -> BatchRecord837 | BatchRecord835 | BatchRecord:
        # Dispatch base-class construction to the right concrete subclass
        # so isinstance checks downstream narrow `result` correctly.
        if cls is BatchRecord:
            kind = kwargs.get("kind")
            if kind is None and args and isinstance(args[0], dict):
                kind = args[0].get("kind")
            if kind == "837p":
                return BatchRecord837(*args, **kwargs)
            if kind == "835":
                return BatchRecord835(*args, **kwargs)
        return super().__new__(cls)

    @model_validator(mode="after")
    def _check_parsed_at_tz(self) -> BatchRecord:
        if self.parsed_at.tzinfo is None:
            raise ValueError(
                "parsed_at must be tz-aware (use datetime.now(timezone.utc))"
            )
        return self


class BatchRecord837(BatchRecord):
    """A parsed 837P (professional claim) batch."""

    kind: Literal["837p"] = "837p"
    result: ParseResult


class BatchRecord835(BatchRecord):
    """A parsed 835 (remittance advice) batch."""

    kind: Literal["835"] = "835"
    result: ParseResult835