from __future__ import annotations

import re

from app.soat_crosswalk.domain.models import (
    CrosswalkCandidate,
    CrosswalkResolution,
    CrosswalkStatus,
)
from app.soat_crosswalk.domain.ports import CupsCatalogPort, CupsSoatCrosswalkPort
from app.soat_tariffs.domain.ports import SoatTariffCatalogPort


SUPPORTED_CROSSWALK_YEAR = 2026
CUPS_CODE_PATTERN = re.compile(r"^\d{6}$")


class CupsCodeNotFoundError(LookupError):
    pass


class InvalidCupsCodeError(ValueError):
    pass


class CupsSoatResolver:
    def __init__(
        self,
        *,
        cups_catalog: CupsCatalogPort,
        crosswalk: CupsSoatCrosswalkPort,
        tariff_catalog: SoatTariffCatalogPort,
    ) -> None:
        self.cups_catalog = cups_catalog
        self.crosswalk = crosswalk
        self.tariff_catalog = tariff_catalog

    def resolve(
        self,
        cups_code: str,
        year: int,
        *,
        billed_soat_code: str | None = None,
    ) -> CrosswalkResolution:
        normalized = str(cups_code or "").strip()
        if not CUPS_CODE_PATTERN.fullmatch(normalized):
            raise InvalidCupsCodeError("El código CUPS debe contener exactamente 6 dígitos.")
        self.cups_catalog.load()
        if not self.cups_catalog.contains(normalized):
            raise CupsCodeNotFoundError(f"El código CUPS {normalized} no existe en el catálogo 2026.")

        catalog = self.crosswalk.load()
        tariff_version = self._tariff_version(year)
        common = {
            "cups_code": normalized,
            "cups_description": self.cups_catalog.description(normalized) or "",
            "year": year,
            "crosswalk_version": catalog.version,
            "cups_catalog_version": self.cups_catalog.version,
            "soat_catalog_version": tariff_version,
        }
        if year != SUPPORTED_CROSSWALK_YEAR:
            return CrosswalkResolution(
                status=CrosswalkStatus.UNSUPPORTED_YEAR,
                evidence=["El cruce automático CUPS-SOAT solo está confirmado para la vigencia 2026."],
                **common,
            )

        candidates = [
            CrosswalkCandidate(
                soat_code=item.soat_code,
                soat_description=item.soat_description,
                surgical_group=item.surgical_group,
                relation_type=item.relation_type,
                reference=item.reference,
                observation=item.observation,
            )
            for item in self.crosswalk.find_by_cups(normalized)
        ]
        evidence = [
            f"Cruce exacto por CUPS={normalized}; no se usó similitud semántica.",
            f"Fuente: {catalog.source.reference}; sha256={catalog.source.sha256}.",
        ]
        if not candidates:
            if catalog.source.status == "pending_source":
                evidence.append("El archivo autoritativo de correlación aún no ha sido importado.")
            return CrosswalkResolution(
                status=CrosswalkStatus.UNMAPPED,
                evidence=evidence,
                candidates=[],
                **common,
            )
        if len(candidates) > 1:
            return CrosswalkResolution(
                status=CrosswalkStatus.AMBIGUOUS,
                evidence=[*evidence, "La relación uno-a-varios requiere selección humana."],
                candidates=candidates,
                **common,
            )

        selected = candidates[0].soat_code
        billed = str(billed_soat_code or "").strip().zfill(5) if billed_soat_code else None
        if billed and billed != selected:
            return CrosswalkResolution(
                status=CrosswalkStatus.CONFLICT,
                evidence=[*evidence, f"SOAT facturado={billed} contradice SOAT correlacionado={selected}."],
                candidates=candidates,
                selected_soat_code=billed,
                **common,
            )
        return CrosswalkResolution(
            status=CrosswalkStatus.RESOLVED,
            evidence=evidence,
            candidates=candidates,
            selected_soat_code=selected,
            **common,
        )

    def _tariff_version(self, year: int) -> str:
        try:
            return self.tariff_catalog.load(year).version
        except (FileNotFoundError, ValueError):
            return "unavailable"
