"""Pydantic v2 models for parsed 837P claims.

Models mirror the X12 837P structure (Loop 2000A → 2000B → 2300 → 2400) but
flattened to one ``ClaimOutput`` per CLM segment, suitable for JSON serialization
and consumption by the Cyclone frontend.
"""

from __future__ import annotations

from datetime import date
from decimal import Decimal
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, model_serializer


class _Base(BaseModel):
    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


class Address(_Base):
    line1: str
    line2: str | None = None
    city: str
    state: str
    zip: str


class BillingProvider(_Base):
    name: str
    npi: str
    tax_id: str | None = None
    address: Address | None = None


class Subscriber(_Base):
    first_name: str
    last_name: str
    member_id: str
    dob: date | None = None
    gender: Literal["M", "F", "U"] | None = None
    address: Address | None = None


class Payer(_Base):
    name: str
    id: str


class ClaimHeader(_Base):
    claim_id: str
    total_charge: Decimal
    place_of_service: str | None = None
    facility_code_qualifier: str | None = None
    frequency_code: str | None = None
    provider_signature: str | None = None
    assignment: str | None = None
    release_of_info: str | None = None
    prior_auth: str | None = None


class Diagnosis(_Base):
    code: str
    qualifier: str | None = None


class Procedure(_Base):
    qualifier: str
    code: str
    modifiers: list[str] = Field(default_factory=list)


class ServiceLine(_Base):
    line_number: int
    procedure: Procedure
    charge: Decimal
    unit_type: str | None = None
    units: Decimal | None = None
    place_of_service: str | None = None
    service_date: date | None = None
    provider_reference: str | None = None


class ValidationIssue(_Base):
    rule: str
    severity: Literal["error", "warning"]
    message: str
    segment_index: int | None = None


class ValidationReport(_Base):
    passed: bool
    errors: list[ValidationIssue] = Field(default_factory=list)
    warnings: list[ValidationIssue] = Field(default_factory=list)


class Envelope(_Base):
    sender_id: str
    receiver_id: str
    control_number: str
    transaction_date: date
    transaction_time: str | None = None
    implementation_guide: str | None = None
    # SP3 P1 T2: BHT06 transaction type code (was: transaction_set_purpose_code, which is BHT02).
    transaction_type_code: str | None = None


class BatchSummary(_Base):
    input_file: str
    control_number: str | None = None
    transaction_date: date | None = None
    total_claims: int = 0
    passed: int = 0
    failed: int = 0
    failed_claim_ids: list[str] = Field(default_factory=list)
    issues_by_rule: dict[str, int] = Field(default_factory=dict)
    output_dir: str | None = None


class ClaimOutput(_Base):
    claim_id: str
    control_number: str
    transaction_date: date
    billing_provider: BillingProvider
    subscriber: Subscriber
    payer: Payer
    claim: ClaimHeader
    diagnoses: list[Diagnosis] = Field(default_factory=list)
    service_lines: list[ServiceLine] = Field(default_factory=list)
    validation: ValidationReport
    raw_segments: list[list[str]] = Field(default_factory=list)
    # SP3 P1 T2: BHT06 transaction type code (was: transaction_set_purpose_code, which is BHT02).
    # Mirrors envelope.transaction_type_code per parser convention.
    transaction_type_code: str | None = None


class ParseResult(_Base):
    envelope: Envelope | None = None
    claims: list[ClaimOutput] = Field(default_factory=list)
    summary: BatchSummary
