from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator

from app.rips.application.catalogs import (
    OTHER_SERVICE_SOURCE_SECTIONS,
    normalize_catalog_code,
    normalize_lst_si_no,
)
from app.rips.domain.models import RipsPayload


def _has_non_empty_value(value: Any) -> bool:
    if value is None:
        return False
    if isinstance(value, str):
        return bool(value.strip())
    return True


def _is_default_numeric(value: Any, default: int | float) -> bool:
    if value in (None, ""):
        return True
    try:
        return float(value) == float(default)
    except (TypeError, ValueError):
        return False


def _sanitize_medication_catalog_entries(value: Any) -> Any:
    if not isinstance(value, list):
        return value

    sanitized: list[Any] = []
    for item in value:
        if not isinstance(item, dict):
            sanitized.append(item)
            continue

        has_match_key = _has_non_empty_value(item.get("match_key"))
        has_code = _has_non_empty_value(item.get("cod_tecnologia_salud"))
        if has_match_key or has_code:
            sanitized.append(item)
            continue

        has_meaningful_text = any(
            _has_non_empty_value(item.get(key))
            for key in (
                "forma_farmaceutica",
                "num_autorizacion",
                "id_mipres",
                "tipo_documento_identificacion",
                "num_documento_identificacion",
            )
        )
        has_non_default_numbers = any(
            (
                not _is_default_numeric(item.get("unidad_medida"), 1),
                not _is_default_numeric(item.get("unidad_min_dispensa"), 1),
                not _is_default_numeric(item.get("dias_tratamiento"), 1),
                not _is_default_numeric(item.get("concentracion_medicamento"), 0),
                not _is_default_numeric(item.get("valor_pago_moderador"), 0),
            )
        )
        if has_meaningful_text or has_non_default_numbers:
            sanitized.append(item)

    return sanitized


def _sanitize_other_service_catalog_entries(value: Any) -> Any:
    if not isinstance(value, list):
        return value

    sanitized: list[Any] = []
    for item in value:
        if not isinstance(item, dict):
            sanitized.append(item)
            continue

        has_match_key = _has_non_empty_value(item.get("match_key"))
        has_code = _has_non_empty_value(item.get("cod_tecnologia_salud"))
        has_name = _has_non_empty_value(item.get("nom_tecnologia_salud"))

        if has_match_key or has_code or has_name:
            sanitized.append(item)

    return sanitized


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

    def to_document(self, *, exclude_none: bool = True) -> dict[str, Any]:
        return self.model_dump(mode="python", exclude_none=exclude_none)


class RipsTransactionConfig(ApplicationModel):
    tipo_nota: str | None = None
    num_nota: str | None = None


class RipsUserConfig(ApplicationModel):
    tipo_documento_identificacion: str | None = None
    num_documento_identificacion: str | None = None
    tipo_usuario: str
    fecha_nacimiento: str | None = None
    cod_sexo: str | None = None
    cod_pais_residencia: str = "170"
    cod_municipio_residencia: str
    cod_zona_territorial_residencia: str
    incapacidad: str
    cod_pais_origen: str = "170"

    @field_validator("tipo_usuario", "cod_zona_territorial_residencia", "cod_pais_origen", mode="before")
    @classmethod
    def normalize_upper_catalog_codes(cls, value: Any) -> str:
        return normalize_catalog_code(value)

    @field_validator("incapacidad", mode="before")
    @classmethod
    def normalize_incapacidad(cls, value: Any) -> str:
        return normalize_lst_si_no(value)


class RipsProcedureConfig(ApplicationModel):
    num_autorizacion: str | None = None
    id_mipres: str | None = None
    via_ingreso_servicio_salud: str
    modalidad_grupo_servicio_tec_sal: str
    grupo_servicios: str
    cod_servicio: int = Field(ge=1)
    finalidad_tecnologia_salud: str
    concepto_recaudo: str = "02"
    valor_pago_moderador: int = Field(default=0, ge=0)
    num_fev_pago_moderador: str | None = None
    cod_diagnostico_relacionado: str | None = None
    cod_complicacion: str | None = None

    @field_validator("concepto_recaudo", mode="before")
    @classmethod
    def normalize_concepto_recaudo(cls, value: Any) -> str:
        return normalize_catalog_code(value)


class RipsMedicationCatalogEntry(ApplicationModel):
    match_key: str
    cod_tecnologia_salud: str
    tipo_medicamento: str
    unidad_medida: int = Field(ge=0)
    forma_farmaceutica: str | None = None
    unidad_min_dispensa: int = Field(ge=1)
    dias_tratamiento: int = Field(ge=0)
    concentracion_medicamento: float = Field(default=0, ge=0)
    concepto_recaudo: str = "02"
    valor_pago_moderador: int = Field(default=0, ge=0)
    num_fev_pago_moderador: str | None = None
    num_autorizacion: str | None = None
    id_mipres: str | None = None
    tipo_documento_identificacion: str | None = None
    num_documento_identificacion: str | None = None

    @field_validator("match_key")
    @classmethod
    def validate_match_key(cls, value: str) -> str:
        normalized = str(value or "").strip()
        if not normalized:
            raise ValueError("match_key es obligatorio para mapear medicamentos al RIPS.")
        return normalized

    @field_validator("concepto_recaudo", mode="before")
    @classmethod
    def normalize_catalog_concepto_recaudo(cls, value: Any) -> str:
        return normalize_catalog_code(value)


class RipsHospitalizationConfig(ApplicationModel):
    via_ingreso_servicio_salud: str
    causa_motivo_atencion: str
    condicion_destino_usuario_egreso: str
    num_autorizacion: str | None = None
    cod_diagnostico_principal_egreso: str | None = None
    cod_diagnostico_relacionado_e1: str | None = None
    cod_diagnostico_relacionado_e2: str | None = None
    cod_diagnostico_relacionado_e3: str | None = None
    cod_complicacion: str | None = None
    cod_diagnostico_causa_muerte: str | None = None


class RipsOtherServiceSectionDefaults(ApplicationModel):
    source_section: Literal[
        "examenes_laboratorio",
        "imagenologia",
        "hospitalizacion",
        "honorarios_medicos",
        "otros_servicios",
    ]
    tipo_os: str
    concepto_recaudo: str = "02"
    valor_pago_moderador: int = Field(default=0, ge=0)
    num_fev_pago_moderador: str | None = None
    num_autorizacion: str | None = None
    id_mipres: str | None = None
    tipo_documento_identificacion: str | None = None
    num_documento_identificacion: str | None = None

    @field_validator("source_section")
    @classmethod
    def validate_default_source_section(cls, value: str) -> str:
        if value not in OTHER_SERVICE_SOURCE_SECTIONS:
            raise ValueError("source_section no está soportado para defaults de otrosServicios.")
        return value

    @field_validator("tipo_os", "concepto_recaudo", mode="before")
    @classmethod
    def normalize_other_service_default_codes(cls, value: Any) -> str:
        return normalize_catalog_code(value)


class RipsOtherServiceCatalogEntry(ApplicationModel):
    source_section: Literal[
        "examenes_laboratorio",
        "imagenologia",
        "hospitalizacion",
        "honorarios_medicos",
        "otros_servicios",
    ]
    match_key: str
    tipo_os: str | None = None
    cod_tecnologia_salud: str | None = None
    nom_tecnologia_salud: str | None = None
    cantidad_os: float | None = Field(default=None, gt=0)
    vr_unit_os: int | None = Field(default=None, ge=0)
    concepto_recaudo: str = "02"
    valor_pago_moderador: int = Field(default=0, ge=0)
    num_fev_pago_moderador: str | None = None
    num_autorizacion: str | None = None
    id_mipres: str | None = None
    tipo_documento_identificacion: str | None = None
    num_documento_identificacion: str | None = None

    @field_validator("source_section")
    @classmethod
    def validate_source_section(cls, value: str) -> str:
        if value not in OTHER_SERVICE_SOURCE_SECTIONS:
            raise ValueError("source_section no está soportado para otrosServicios.")
        return value

    @field_validator("match_key")
    @classmethod
    def validate_other_service_match_key(cls, value: str) -> str:
        normalized = str(value or "").strip()
        if not normalized:
            raise ValueError("match_key es obligatorio para mapear otros servicios al RIPS.")
        return normalized

    @field_validator("tipo_os", "concepto_recaudo", mode="before")
    @classmethod
    def normalize_other_service_catalog_codes(cls, value: Any) -> str:
        return normalize_catalog_code(value)


class RipsGenerationOptions(ApplicationModel):
    transaction: RipsTransactionConfig = Field(default_factory=RipsTransactionConfig)
    user: RipsUserConfig
    procedure: RipsProcedureConfig
    hospitalization: RipsHospitalizationConfig | None = None
    medication_catalog: list[RipsMedicationCatalogEntry] = Field(default_factory=list)
    other_service_defaults: list[RipsOtherServiceSectionDefaults] = Field(default_factory=list)
    other_service_catalog: list[RipsOtherServiceCatalogEntry] = Field(default_factory=list)

    @field_validator("medication_catalog", mode="before")
    @classmethod
    def sanitize_medication_catalog(cls, value: Any) -> Any:
        return _sanitize_medication_catalog_entries(value)

    @field_validator("other_service_catalog", mode="before")
    @classmethod
    def sanitize_other_service_catalog(cls, value: Any) -> Any:
        return _sanitize_other_service_catalog_entries(value)


class GenerateCaseRipsCommand(ApplicationModel):
    username: str
    case_key: str
    options: RipsGenerationOptions
    regen: bool = False
    persist: bool = True


class RipsFieldTrace(ApplicationModel):
    field_path: str
    source_path: str
    transform: str


class RipsValidationFinding(ApplicationModel):
    rule_code: str
    severity: Literal["reject", "notify"]
    message: str
    field_path: str | None = None
    source_path: str | None = None


class RipsValidationReport(ApplicationModel):
    status: Literal["approved", "rejected", "notified"]
    ready_for_cuv: bool
    findings: list[RipsValidationFinding] = Field(default_factory=list)

    @property
    def has_rejects(self) -> bool:
        return any(item.severity == "reject" for item in self.findings)

    @property
    def has_notifications(self) -> bool:
        return any(item.severity == "notify" for item in self.findings)


class BuiltRipsPayload(ApplicationModel):
    payload: RipsPayload
    field_trace_map: list[RipsFieldTrace] = Field(default_factory=list)


class GeneratedRipsPayload(ApplicationModel):
    case_key: str
    version: int | None = None
    stored: bool
    regenerated: bool
    source: str
    export_status: Literal["ready", "preliminary"]
    export_message: str
    payload: dict[str, Any]
    validation_report: RipsValidationReport
    field_trace_map: list[RipsFieldTrace] = Field(default_factory=list)


class RipsTemplateScope(ApplicationModel):
    level: Literal["global", "provider", "provider_payer", "provider_payer_site"]
    provider_nit: str | None = None
    provider_name: str | None = None
    payer_nit: str | None = None
    payer_name: str | None = None
    site_name: str | None = None


class RipsOperationalTemplate(ApplicationModel):
    scope: RipsTemplateScope
    options: dict[str, Any] = Field(default_factory=dict)
    version: int | None = None
    stored: bool = True
    source: Literal["case_scope", "global_default"] = "case_scope"
    resolved_from_level: Literal["global", "provider", "provider_payer", "provider_payer_site"] | None = None


RipsGenerationOptions.model_rebuild()
RipsOperationalTemplate.model_rebuild()
