from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
import re
from typing import Any

from pymongo.errors import OperationFailure

from app.services.clinical_processing import (
    extraer_metadatos_historia,
    extraer_medicamentos_historia,
    extraer_procedimientos_historia,
    extraer_secciones_quirurgicas,
    formatear_medicamento_canonico,
    normalizar_lista_medicamentos,
)
from app.services.soat_processing import _construir_soat_y_glosa


def _build_medication_display_list(items) -> list[str]:
    return [formatear_medicamento_canonico(item) for item in normalizar_lista_medicamentos(items)]


def _strip_html_tags(value: str) -> str:
    return re.sub(r"<[^>]+>", "", value or "").strip()


def _normalize_factura_medicamentos(doc: dict[str, Any] | None) -> dict[str, Any] | None:
    if not isinstance(doc, dict):
        return doc
    factura_json = doc.get("factura_json")
    if not isinstance(factura_json, dict):
        return doc
    servicios = factura_json.get("servicios_procedimientos")
    if not isinstance(servicios, dict):
        return doc
    servicios["medicamentos"] = normalizar_lista_medicamentos(
        servicios.get("medicamentos"), fuente="factura"
    )
    return doc


def _extraer_procedimientos_factura(factura_doc: dict[str, Any] | None) -> list[dict[str, str]]:
    if not factura_doc:
        return []

    procedimientos: list[dict[str, str]] = []
    factura_json = factura_doc.get("factura_json") if isinstance(factura_doc, dict) else None

    if isinstance(factura_json, dict):
        servicios = factura_json.get("servicios_procedimientos") or {}
        for item in servicios.get("procedimientos_quirurgicos") or []:
            if not isinstance(item, dict):
                continue
            descripcion = (
                str(item.get("descripcion") or "").strip()
                or str(item.get("concepto") or "").strip()
            )
            codigo = str(item.get("codigo_cups") or "").strip()
            if descripcion and descripcion.lower() not in {"procedimiento", "[desc]", "no especificado"}:
                procedimientos.append({"codigo_soat": codigo, "descripcion": descripcion})
    if procedimientos:
        return procedimientos

    factura_html = str(factura_doc.get("analisis_html") or "")
    match = re.search(
        r"Procedimientos\s+quir[úu]rgicos.*?<table[^>]*>(.*?)</table>",
        factura_html,
        flags=re.IGNORECASE | re.DOTALL,
    )
    if not match:
        return []

    table_html = match.group(1)
    rows = re.findall(r"<tr[^>]*>(.*?)</tr>", table_html, flags=re.IGNORECASE | re.DOTALL)
    for row in rows:
        cells = re.findall(r"<td[^>]*>(.*?)</td>", row, flags=re.IGNORECASE | re.DOTALL)
        if len(cells) < 3:
            continue
        concepto = _strip_html_tags(cells[0])
        codigo = _strip_html_tags(cells[1])
        descripcion = _strip_html_tags(cells[2])
        descripcion_final = descripcion or concepto
        if not descripcion_final or descripcion_final.lower() in {"procedimiento", "[desc]", "no especificado"}:
            continue
        procedimientos.append({"codigo_soat": codigo, "descripcion": descripcion_final})
    return procedimientos


@dataclass
class CaseEpicrisisService:
    mongo_analyses: Any
    client_groq: Any
    soat_retriever: Any
    colombia_tz: Any

    def ensure_indexes(self) -> None:
        self._deduplicate_case_caches()
        try:
            self.mongo_analyses.collection.create_index(
                [("usuario", 1), ("tipo_documento", 1), ("case_key", 1)],
                unique=True,
                partialFilterExpression={
                    "tipo_documento": "epicrisis_case_cache",
                    "case_key": {"$exists": True, "$type": "string"},
                },
            )
        except OperationFailure as exc:
            if getattr(exc, "code", None) not in {85, 86}:
                raise

    def build_case_context(self, username: str, case_key: str) -> dict[str, Any]:
        historia = self._find_latest(username, case_key, "historia_clinica")
        quirurgico = self._find_latest(username, case_key, "quirurgico")
        factura = self._find_latest(username, case_key, "factura")
        docs = [doc for doc in (historia, quirurgico, factura) if doc]
        if not docs:
            raise ValueError("No se encontraron documentos clinicos para el caso.")

        nombre_paciente = (
            (historia or {}).get("nombre_paciente")
            or (quirurgico or {}).get("nombre_paciente")
            or (factura or {}).get("nombre_paciente")
            or "desconocido"
        )

        soat_resultados, glosa_html = _construir_soat_y_glosa(
            historia_html=(historia.get("analisis_html") if historia else None),
            qx_html=(quirurgico.get("analisis_html") if quirurgico else None),
            soat_retriever=self.soat_retriever,
            client_groq=self.client_groq,
        )

        metadatos_hc = extraer_metadatos_historia(
            historia.get("analisis_html") if historia else None
        )
        procedimientos_hc = extraer_procedimientos_historia(
            historia.get("analisis_html") if historia else None
        )
        medicamentos_hc = extraer_medicamentos_historia(
            historia.get("analisis_html") if historia else None
        )
        medicamentos_hc_display = _build_medication_display_list(medicamentos_hc)
        hallazgos_qx, descripcion_qx = extraer_secciones_quirurgicas(
            quirurgico.get("analisis_html") if quirurgico else None
        )

        return {
            "nombre_paciente": nombre_paciente,
            "case_key": case_key,
            "case_number": (
                (historia or {}).get("case_number")
                or (quirurgico or {}).get("case_number")
                or (factura or {}).get("case_number")
                or ""
            ),
            "historia": self._serialize_doc(historia),
            "quirurgico": self._serialize_doc(quirurgico),
            "factura": _normalize_factura_medicamentos(self._serialize_doc(factura)),
            "metadatos_hc": metadatos_hc,
            "procedimientos_hc": procedimientos_hc,
            "medicamentos_hc": medicamentos_hc,
            "medicamentos_hc_display": medicamentos_hc_display,
            "hallazgos_quirurgicos": hallazgos_qx,
            "descripcion_procedimiento": descripcion_qx,
            "soat_resultados": soat_resultados,
            "glosa_analisis": glosa_html,
            "codigos_desde_soat": [],
            "procedimientos_factura": _extraer_procedimientos_factura(factura),
            "regen_url": f"/epicrisis?case_key={case_key}&regen=1",
            "epicrisis_cached": False,
        }

    def get_cached_case_context(self, username: str, case_key: str) -> dict[str, Any] | None:
        cache = self.mongo_analyses.collection.find_one(
            {
                "usuario": username,
                "tipo_documento": "epicrisis_case_cache",
                "case_key": case_key,
            },
            sort=[("fecha_analisis", -1)],
        )
        if not cache or not isinstance(cache.get("contexto"), dict):
            return None
        return cache

    def cache_case_context(
        self,
        username: str,
        case_key: str,
        *,
        regen: bool = False,
    ) -> dict[str, Any]:
        if not regen:
            cached = self.get_cached_case_context(username, case_key)
            if cached and isinstance(cached.get("contexto"), dict):
                return dict(cached["contexto"])

        contexto = self.build_case_context(username, case_key)
        payload = {
            "usuario": username,
            "tipo_documento": "epicrisis_case_cache",
            "case_key": case_key,
            "nombre_paciente": contexto.get("nombre_paciente", "desconocido"),
            "historia_id": (contexto.get("historia") or {}).get("_id"),
            "quirurgico_id": (contexto.get("quirurgico") or {}).get("_id"),
            "factura_id": (contexto.get("factura") or {}).get("_id"),
            "fecha_analisis": datetime.now(self.colombia_tz),
            "regen_requested": bool(regen),
            "contexto": contexto,
        }
        self.mongo_analyses.collection.update_one(
            {
                "usuario": username,
                "tipo_documento": "epicrisis_case_cache",
                "case_key": case_key,
            },
            {"$set": payload},
            upsert=True,
        )
        return contexto

    def _deduplicate_case_caches(self) -> None:
        duplicates = self.mongo_analyses.collection.aggregate(
            [
                {
                    "$match": {
                        "tipo_documento": "epicrisis_case_cache",
                        "case_key": {"$exists": True, "$type": "string", "$ne": ""},
                    }
                },
                {
                    "$sort": {
                        "fecha_analisis": -1,
                        "_id": -1,
                    }
                },
                {
                    "$group": {
                        "_id": {
                            "usuario": "$usuario",
                            "case_key": "$case_key",
                            "tipo_documento": "$tipo_documento",
                        },
                        "ids": {"$push": "$_id"},
                        "count": {"$sum": 1},
                    }
                },
                {"$match": {"count": {"$gt": 1}}},
            ]
        )
        for item in duplicates:
            ids = list(item.get("ids") or [])
            if len(ids) < 2:
                continue
            self.mongo_analyses.collection.delete_many({"_id": {"$in": ids[1:]}})

    def _find_latest(self, username: str, case_key: str, tipo_documento: str) -> dict[str, Any] | None:
        return self.mongo_analyses.collection.find_one(
            {
                "usuario": username,
                "case_key": case_key,
                "tipo_documento": tipo_documento,
            },
            sort=[("fecha_analisis", -1)],
        )

    def _serialize_doc(self, doc: dict[str, Any] | None) -> dict[str, Any] | None:
        if not doc:
            return None
        serialized = dict(doc)
        if serialized.get("_id") is not None:
            serialized["_id"] = str(serialized["_id"])
        if "fecha_analisis" in serialized and isinstance(serialized["fecha_analisis"], datetime):
            serialized["fecha_analisis"] = serialized["fecha_analisis"].isoformat()
        return serialized
