from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from typing import Any

from pydantic import BaseModel, ConfigDict, Field

from app.services.clinical_document_projection import render_document_analysis_html


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


class EpicrisisRuleSeverity(StrEnum):
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"


class EpicrisisRuleStatus(StrEnum):
    PASSED = "passed"
    WARNING = "warning"
    BLOCKED = "blocked"


class EpicrisisRuleEvaluationMode(StrEnum):
    DETERMINISTIC_ONLY = "deterministic_only"
    DETERMINISTIC_PLUS_LLM = "deterministic_plus_llm"


class EpicrisisRuleFinding(CaseEpicrisisDomainModel):
    rule_code: str
    severity: EpicrisisRuleSeverity
    message: str
    document_types_checked: list[str] = Field(default_factory=list)
    observed_value: Any = None
    blocking: bool = False


class EpicrisisRuleEvaluation(CaseEpicrisisDomainModel):
    status: EpicrisisRuleStatus
    generation_allowed: bool
    ready_for_epicrisis: bool
    findings: list[EpicrisisRuleFinding] = Field(default_factory=list)
    required_documents: list[str] = Field(default_factory=list)
    missing_documents: list[str] = Field(default_factory=list)
    evaluated_at: datetime
    evaluation_mode: EpicrisisRuleEvaluationMode = EpicrisisRuleEvaluationMode.DETERMINISTIC_ONLY


class EpicrisisEvidenceSnapshot(CaseEpicrisisDomainModel):
    case_key: str
    patient_name: str = ""
    case_number: str = ""
    has_quirurgico_document: bool = False
    document_types_available: list[str] = Field(default_factory=list)
    historia_metadatos: dict[str, Any] = Field(default_factory=dict)
    procedimientos_factura: list[str] = Field(default_factory=list)
    procedimientos_historia: list[str] = Field(default_factory=list)
    historia_resumen: str = ""
    historia_motivo_consulta: str = ""
    generico_resumenes: list[str] = Field(default_factory=list)
    generico_referencias: list[str] = Field(default_factory=list)
    ayudas_diagnosticas: list[str] = Field(default_factory=list)
    hallazgos_quirurgicos: str = ""
    descripcion_procedimiento: str = ""
    strong_signals: list[str] = Field(default_factory=list)
    support_signals: list[str] = Field(default_factory=list)
    weak_signals: list[str] = Field(default_factory=list)


class EpicrisisGenerationBlockedError(ValueError):
    def __init__(
        self,
        message: str,
        *,
        evaluation: EpicrisisRuleEvaluation,
        context: dict[str, Any],
    ) -> None:
        super().__init__(message)
        self.evaluation = evaluation
        self.context = dict(context)


@dataclass(frozen=True)
class CaseEpicrisisDocuments:
    historia: dict[str, Any] | None
    quirurgico: dict[str, Any] | None
    factura: dict[str, Any] | None
    radiologia: list[dict[str, Any]]
    laboratorio: list[dict[str, Any]]
    generico: list[dict[str, Any]]

    @classmethod
    def from_payload(cls, payload: dict[str, Any]) -> CaseEpicrisisDocuments:
        return cls(
            historia=payload.get("historia"),
            quirurgico=payload.get("quirurgico"),
            factura=payload.get("factura"),
            radiologia=[doc for doc in (payload.get("radiologia") or []) if isinstance(doc, dict)],
            laboratorio=[doc for doc in (payload.get("laboratorio") or []) if isinstance(doc, dict)],
            generico=[doc for doc in (payload.get("generico") or []) if isinstance(doc, dict)],
        )

    def has_clinical_content(self) -> bool:
        return any((self.historia, self.quirurgico, self.factura, self.radiologia, self.laboratorio, self.generico))

    @property
    def nombre_paciente(self) -> str:
        associated_doc = next(iter([*self.radiologia, *self.laboratorio, *self.generico]), {})
        return (
            (self.historia or {}).get("nombre_paciente")
            or (self.quirurgico or {}).get("nombre_paciente")
            or (self.factura or {}).get("nombre_paciente")
            or associated_doc.get("nombre_paciente")
            or "desconocido"
        )

    @property
    def case_number(self) -> str:
        associated_doc = next(iter([*self.radiologia, *self.laboratorio, *self.generico]), {})
        return (
            (self.historia or {}).get("case_number")
            or (self.quirurgico or {}).get("case_number")
            or (self.factura or {}).get("case_number")
            or associated_doc.get("case_number")
            or ""
        )

    @property
    def historia_html(self) -> str | None:
        if not self.historia:
            return None
        return str(
            self.historia.get("analisis_html")
            or render_document_analysis_html(self.historia)
            or ""
        ) or None

    @property
    def quirurgico_html(self) -> str | None:
        if not self.quirurgico:
            return None
        return str(
            self.quirurgico.get("analisis_html")
            or render_document_analysis_html(self.quirurgico)
            or ""
        ) or None


@dataclass(frozen=True)
class PreservedEpicrisisState:
    manual_soat_results: list[dict[str, Any]]
    pdf_draft: dict[str, Any]


@dataclass(frozen=True)
class ClinicalSnapshot:
    metadatos_hc: dict[str, Any]
    procedimientos_hc: list[Any]
    medicamentos_hc: list[Any]
    medicamentos_hc_display: list[str]
    hallazgos_qx: str
    descripcion_qx: str
    procedimientos_factura: list[dict[str, str]]
    ayudas_diagnosticas: list[dict[str, Any]]
    imagenes_diagnosticas: list[str]


@dataclass(frozen=True)
class CodingSnapshot:
    soat_resultados: list[dict[str, Any]]
    glosa_html: str
    auto_soat_results: list[dict[str, Any]]
    qx_agent_results: list[dict[str, Any]]
