"""Payer-specific configuration for the Cyclone parsers.

The same parser module can ingest 837P / 835 files from any payer, but
the validation rules differ. ``PayerConfig`` (837P) and ``PayerConfig835``
(835 ERA) carry the differences so the validator functions can be
payer-aware without branching in the parser.
"""

from __future__ import annotations

import re
from typing import Pattern

from pydantic import BaseModel, ConfigDict, Field


# Full CMS Place of Service code list (X12 837P CLM05-1).
# Two-digit zero-padded codes covering the entire CMS POS set as of 2026.
CMS_PLACE_OF_SERVICE_CODES: set[str] = {
    "01", "02", "03", "04", "05", "06", "07", "08", "09",
    "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
    "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
    "30", "31", "32", "33", "34", "41", "42", "49", "50", "51",
    "52", "53", "54", "55", "56", "57", "58", "59", "60", "61",
    "62", "63", "64", "65", "66", "67", "68", "69", "70", "71",
    "72", "73", "74", "75", "76", "77", "78", "79", "80", "81",
    "82", "83", "84", "85", "86", "87", "88", "89", "90", "91",
    "92", "93", "94", "95", "96", "97", "98", "99",
}


# --------------------------------------------------------------------------- #
# 837P (Professional Claim)
# --------------------------------------------------------------------------- #


class PayerConfig(BaseModel):
    model_config = ConfigDict(frozen=True)

    name: str
    sbr09_claim_filing: str = "MC"
    allowed_claim_frequencies: set[int] = Field(default_factory=lambda: {1, 7, 8})
    require_ref_g1_for_adjustments: bool = False
    allowed_bht06: set[str] = Field(default_factory=lambda: {"CH"})
    payer_id: str = ""
    payer_name: str = ""
    no_patient_loop: bool = False
    encounter_claim_in_same_batch: bool = False
    # CLM05-2 Facility Code Qualifier. "B" = CMS Place of Service code.
    allowed_facility_qualifiers: set[str] = Field(default_factory=lambda: {"B"})
    # CLM05-1 Facility Type Code / Place of Service. Defaults to the full CMS POS list.
    allowed_place_of_service_codes: set[str] = Field(
        default_factory=lambda: set(CMS_PLACE_OF_SERVICE_CODES)
    )

    @classmethod
    def co_medicaid(cls) -> "PayerConfig":
        """Defaults for Colorado Medical Assistance Program (FFS).

        Source: ``docs/companionguides/837p.md``.
        """
        return cls(
            name="Colorado Medical Assistance Program",
            sbr09_claim_filing="MC",
            allowed_claim_frequencies={1, 7, 8},
            # Lenient in v1 — see spec §9 R031.
            require_ref_g1_for_adjustments=False,
            allowed_bht06={"CH"},
            payer_id="SKCO0",
            payer_name="COHCPF",
            no_patient_loop=True,
            encounter_claim_in_same_batch=False,
            allowed_facility_qualifiers={"B"},
            allowed_place_of_service_codes=set(CMS_PLACE_OF_SERVICE_CODES),
        )

    @classmethod
    def generic_837p(cls) -> "PayerConfig":
        """Relaxed defaults for an unknown payer. Structural rules only."""
        return cls(
            name="Generic 837P",
            sbr09_claim_filing="",
            allowed_claim_frequencies={1, 2, 3, 4, 5, 6, 7, 8, 9},
            require_ref_g1_for_adjustments=False,
            allowed_bht06={"CH", "RP"},
            payer_id="",
            payer_name="",
            no_patient_loop=False,
            encounter_claim_in_same_batch=True,
            allowed_facility_qualifiers={"B"},
            allowed_place_of_service_codes=set(CMS_PLACE_OF_SERVICE_CODES),
        )


# --------------------------------------------------------------------------- #
# 835 ERA (Health Care Claim Payment/Advice)
# --------------------------------------------------------------------------- #


class PayerConfig835(BaseModel):
    """Payer-specific configuration for the 835 ERA parser.

    Mirrors :class:`PayerConfig` but carries the 835-specific fields
    (handling codes, status codes, CO Medicaid tax IDs / health plan ID).
    Kept in a separate class — not a subclass — because the 835 and 837P
    validation surfaces don't share enough structure to make inheritance
    pay off.
    """

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

    name: str
    allowed_handling_codes: set[str] = Field(
        default_factory=lambda: {"C", "D", "I", "X"}
    )
    allowed_status_codes: set[str] = Field(
        default_factory=lambda: {"1", "2", "3", "4", "19", "20", "21", "22", "23", "25"}
    )
    # CO Medicaid sends BPR10 with or without the IRS hyphen — accept both
    # the dashed form ("81-1725341", "84-0644739") and the un-dashed
    # 10-digit form ("811725341", "840644739") that newer production 835s
    # use. Also accept "1811725341" (no leading 8, no hyphen), which is
    # what real CO Medicaid data sends on Information Only (BPR01="I") ERAs.
    expected_payer_tax_ids: set[str] = Field(default_factory=set)
    # CO uses "7912900843" as the Health Plan ID on N104.
    expected_payer_health_plan_id: str = ""
    # Regex matched against Payer.name (N102). Example: r"^CO_(TXIX|BHA)$".
    payer_name_pattern: str = ""

    def payer_name_regex(self) -> Pattern[str] | None:
        """Compile ``payer_name_pattern`` (or return ``None`` if unset/blank)."""
        if not self.payer_name_pattern:
            return None
        return re.compile(self.payer_name_pattern)

    @classmethod
    def co_medicaid_835(cls) -> "PayerConfig835":
        """Defaults for Colorado Medical Assistance Program (835).

        Source: ``docs/companionguides/835.md``.
        """
        return cls(
            name="Colorado Medical Assistance Program (835)",
            expected_payer_tax_ids={
                "81-1725341",
                "811725341",
                "84-0644739",
                "840644739",
                "1811725341",
            },
            expected_payer_health_plan_id="7912900843",
            payer_name_pattern=r"^CO_(TXIX|BHA)$",
        )

    @classmethod
    def generic_835(cls) -> "PayerConfig835":
        """Relaxed defaults for an unknown payer. Structural rules only."""
        return cls(
            name="Generic 835",
            expected_payer_tax_ids=set(),
            expected_payer_health_plan_id="",
            payer_name_pattern="",
        )
