from __future__ import annotations

from datetime import date
from enum import StrEnum
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator


class CrosswalkModel(BaseModel):
    model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)


class CrosswalkStatus(StrEnum):
    RESOLVED = "resolved"
    AMBIGUOUS = "ambiguous"
    UNMAPPED = "unmapped"
    CONFLICT = "conflict"
    UNSUPPORTED_YEAR = "unsupported_year"


class CrosswalkSource(CrosswalkModel):
    filename: str
    sha256: str = Field(pattern=r"^[0-9a-f]{64}$")
    reference: str
    status: Literal["confirmed", "pending_source"] = "confirmed"
    cups_column: str = ""
    soat_column: str = ""


class CrosswalkRelationship(CrosswalkModel):
    cups_code: str = Field(pattern=r"^\d{6}$")
    cups_description: str = Field(min_length=1)
    soat_code: str = Field(pattern=r"^\d{5}$")
    soat_description: str = Field(min_length=1)
    surgical_group: int = Field(ge=2, le=23)
    relation_type: Literal["confirmed"] = "confirmed"
    reference: str
    observation: str = ""


class CrosswalkCatalog(CrosswalkModel):
    schema_version: int = 1
    version: str = Field(min_length=1)
    effective_from: date
    normative_version: str
    cups_catalog_version: str
    soat_catalog_version: str
    source: CrosswalkSource
    relationships: list[CrosswalkRelationship] = Field(default_factory=list)

    @field_validator("relationships")
    @classmethod
    def unique_pairs(cls, value: list[CrosswalkRelationship]) -> list[CrosswalkRelationship]:
        pairs = [(item.cups_code, item.soat_code) for item in value]
        if len(pairs) != len(set(pairs)):
            raise ValueError("El cruce contiene pares CUPS-SOAT duplicados")
        return value

    @model_validator(mode="after")
    def confirmed_source_for_relationships(self) -> CrosswalkCatalog:
        if self.relationships and self.source.status != "confirmed":
            raise ValueError("Una fuente pendiente no puede contener relaciones")
        return self


class CrosswalkCandidate(CrosswalkModel):
    soat_code: str
    soat_description: str
    surgical_group: int
    relation_type: str
    reference: str
    observation: str = ""


class CrosswalkResolution(CrosswalkModel):
    status: CrosswalkStatus
    cups_code: str
    cups_description: str = ""
    year: int
    crosswalk_version: str
    cups_catalog_version: str
    soat_catalog_version: str
    evidence: list[str] = Field(default_factory=list)
    candidates: list[CrosswalkCandidate] = Field(default_factory=list)
    selected_soat_code: str | None = None

