import json
import os
import re
from dataclasses import dataclass
from pathlib import Path
from time import perf_counter
from typing import Any, cast

import pandas as pd
from dotenv import load_dotenv
from groq import Groq
from langchain_community.vectorstores import FAISS
from unidecode import unidecode

from app.config import config
from app.core.logging import InstrumentedGroqClient, get_audit_logger, get_logger
from app.llm import LLMTask
from app.llm.task_text import generate_task_text
from modules.processing.embeddings import get_hf_embeddings


load_dotenv()
EMBEDDINGS_MODEL = os.getenv("EMBEDDINGS_MODEL", "intfloat/e5-small-v2")
GROQ_MODEL_CIE10 = os.getenv("GROQ_MODEL_CIE10", config.GROQ_MODEL_CIE10)
logger = get_logger(__name__)
audit_logger = get_audit_logger()


_CIE10_CODE_TOKEN = re.compile(r"\b[A-TV-Z]\d{2}[0-9A-Z]?(?:\.[0-9A-Z]{1,2})?\b", re.IGNORECASE)


@dataclass(frozen=True, slots=True)
class CIE10CatalogEntry:
    """Entrada canónica del catálogo local, incluida su posición jerárquica."""

    codigo: str
    codigo_canonico: str
    descripcion: str
    nivel: int
    padre: str = ""
    bloque: str = ""
    es_hoja: bool = False


class CIE10CatalogResolver:
    """Resuelve códigos contra la estructura CIE-10 antes de aceptar una sugerencia semántica."""

    _ALIASES: tuple[tuple[tuple[str, ...], str, str], ...] = (
        (("condroplastia", "lesion condral", "lesion del cartilago", "cartilago articular"), "M241", "alias clínico controlado: lesión condral/cartílago"),
        (("reparacion ligamentaria", "lesion ligamentaria", "lesion del ligamento", "ligamento lesionado"), "M242", "alias clínico controlado: lesión ligamentaria"),
        (("supraespinoso", "manguito rotatorio", "manguito rotador"), "M751", "alias clínico controlado: supraespinoso/manguito rotatorio"),
    )

    def __init__(self, estructura: dict[str, Any]) -> None:
        self.estructura = estructura
        self._entries: dict[str, CIE10CatalogEntry] = {}
        self._build_index()

    @staticmethod
    def normalizar_codigo(codigo: Any) -> str:
        return re.sub(r"[^A-Z0-9]", "", str(codigo or "").upper())

    @staticmethod
    def _normalizar_texto(texto: Any) -> str:
        return re.sub(r"\s+", " ", unidecode(str(texto or "")).upper()).strip()

    def _add(self, raw: dict[str, Any], *, nivel: int, padre: str = "", bloque: str = "", hoja: bool = False) -> None:
        codigo = str(raw.get("codigo") or "").strip()
        codigo_canonico = self.normalizar_codigo(codigo)
        if not codigo_canonico:
            return
        descripcion = str(raw.get("descripcion") or raw.get("nombre") or "").strip()
        entry = CIE10CatalogEntry(
            codigo=codigo_canonico,
            codigo_canonico=codigo_canonico,
            descripcion=descripcion,
            nivel=nivel,
            padre=self.normalizar_codigo(padre),
            bloque=self.normalizar_codigo(bloque),
            es_hoja=hoja,
        )
        # Si una categoría reaparece como subcategoría, la entrada hoja es la más específica.
        previous = self._entries.get(codigo_canonico)
        if previous is None or entry.es_hoja and not previous.es_hoja:
            self._entries[codigo_canonico] = entry

    def _build_index(self) -> None:
        for bloque in self.estructura.get("bloques", []):
            bloque_code = str(bloque.get("codigo") or "")
            self._add(bloque, nivel=1, bloque=bloque_code, hoja=False)
            for categoria in bloque.get("categorias", []):
                categoria_code = str(categoria.get("codigo") or "")
                self._add(categoria, nivel=2, padre=bloque_code, bloque=bloque_code, hoja=False)
                subcategorias = categoria.get("subcategorias") or []
                for subcategoria in subcategorias:
                    self._add(
                        subcategoria,
                        nivel=3,
                        padre=categoria_code,
                        bloque=bloque_code,
                        hoja=True,
                    )

    def obtener(self, codigo: Any) -> CIE10CatalogEntry | None:
        return self._entries.get(self.normalizar_codigo(codigo))

    def es_codigo_valido(self, codigo: Any) -> bool:
        return self.obtener(codigo) is not None

    def es_hoja(self, codigo: Any) -> bool:
        entry = self.obtener(codigo)
        return bool(entry and entry.es_hoja)

    def _candidate(self, entry: CIE10CatalogEntry, *, fuente: str, puntaje: float, razon: str) -> dict[str, Any]:
        return {
            "codigo": entry.codigo,
            "codigo_canonico": entry.codigo_canonico,
            "descripcion": entry.descripcion,
            "nivel": entry.nivel,
            "padre": entry.padre,
            "bloque": entry.bloque,
            "es_hoja": entry.es_hoja,
            "fuente": fuente,
            "puntaje": round(float(puntaje), 4),
            "razon": razon,
        }

    def candidato_por_codigo(self, codigo: Any, *, fuente: str, razon: str, puntaje: float = 1.0) -> dict[str, Any] | None:
        entry = self.obtener(codigo)
        return self._candidate(entry, fuente=fuente, puntaje=puntaje, razon=razon) if entry else None

    def buscar_texto(self, texto: Any) -> list[dict[str, Any]]:
        consulta = self._normalizar_texto(texto)
        if not consulta:
            return []
        tokens = set(re.findall(r"[A-Z0-9]+", consulta))
        matches: list[tuple[float, CIE10CatalogEntry, str]] = []
        for entry in self._entries.values():
            if not entry.es_hoja:
                continue
            descripcion = self._normalizar_texto(entry.descripcion)
            if consulta == descripcion:
                matches.append((1.0, entry, "coincidencia textual exacta con descripción de catálogo"))
                continue
            desc_tokens = set(re.findall(r"[A-Z0-9]+", descripcion))
            overlap = len(tokens & desc_tokens) / max(len(tokens), 1)
            if overlap >= 0.85 and len(tokens) <= 8:
                matches.append((0.9 + overlap / 10, entry, "coincidencia léxica clínica con descripción de catálogo"))
        return [
            self._candidate(entry, fuente="catalogo_textual", puntaje=score, razon=reason)
            for score, entry, reason in sorted(matches, key=lambda item: item[0], reverse=True)
        ]

    def buscar_alias(self, texto: Any) -> list[dict[str, Any]]:
        consulta = self._normalizar_texto(texto)
        result: list[dict[str, Any]] = []
        for terms, codigo, razon in self._ALIASES:
            if any(self._normalizar_texto(term) in consulta for term in terms):
                candidate = self.candidato_por_codigo(codigo, fuente="alias_clinico", razon=razon, puntaje=0.88)
                if candidate:
                    result.append(candidate)
        return result

    def resolver_texto(self, texto: Any) -> list[dict[str, Any]]:
        return [*self.buscar_texto(texto), *self.buscar_alias(texto)]

class CIE10Retriever:
    def __init__(
        self,
        faiss_index_path: str,
        estructura_json_path: str,
        groq_api_key: str | None = None,
        groq_model: str | None = GROQ_MODEL_CIE10,  # Compat legacy fuera del runtime principal.
        llm_router: Any | None = None,
    ) -> None:
        # Inicialización de embeddings e índice FAISS
        self.embeddings = get_hf_embeddings(EMBEDDINGS_MODEL)
        self.db = None
        self._index_load_error = None
        self.index_path = faiss_index_path
        self.db = self._cargar_indice(faiss_index_path)
        # Carga de la estructura jerárquica CIE-10
        self.estructura = self._cargar_estructura(estructura_json_path)
        self.catalogo = CIE10CatalogResolver(self.estructura)
        self.normalizador = self._crear_normalizador()
        # Cliente Groq para generación de respuestas
        self.llm_router = llm_router
        self.groq_model = groq_model
        self.groq_client = None
        if self.llm_router is None:
            if not config.LEGACY_LLM_PROVIDERS_ENABLED:
                raise RuntimeError(
                    "Los proveedores legacy Groq/OpenAI están deshabilitados. Configura Gemini/llm_router."
                )
            api_key = groq_api_key or os.getenv("GROQ_API_KEY")
            if not api_key:
                raise RuntimeError("Se requiere la variable de entorno GROQ_API_KEY o pasar groq_api_key.")
            self.groq_client = InstrumentedGroqClient(
                Groq(api_key=api_key),
                provider="groq",
                service="cie10_retriever",
            )

    def _cargar_indice(self, path: str):
        try:
            index_dir = Path(path)
            if not index_dir.exists():
                raise FileNotFoundError(f"Directorio no encontrado: {index_dir}")
            if not (index_dir / 'index.faiss').exists():
                raise FileNotFoundError(f"Archivo index.faiss no encontrado en: {index_dir}")
            return FAISS.load_local(str(index_dir), self.embeddings, allow_dangerous_deserialization=True)
        except Exception as e:
            self._index_load_error = e
            logger.error("Error cargando indice FAISS CIE-10: %s", str(e))
            return None

    def _require_db(self):
        if self.db is None:
            detalle = f"{self._index_load_error}" if self._index_load_error else 'desconocido'
            raise RuntimeError(
                'Indice FAISS CIE-10 no disponible. '
                f'Ruta: {self.index_path}. '
                f'Detalle: {detalle}. '
                'Genera el indice con: python modules/processing/cie10/Carga_estructura.py'
            )

    def _cargar_estructura(self, path: str) -> dict:
        with open(path, encoding='utf-8') as f:
            return json.load(f)

    def _crear_normalizador(self) -> dict:
        return {
            'sinonimos': {
                r"\b(trazo\s+sugestivo\b|fx|fract)": "fractura",
                r"\b(dcha|der)\b": "derecho",
                r"\b(izq|izqda)\b": "izquierdo",
                r"\b(tac)\b": "tomografía axial computarizada",
                r"\b(5to|quinto)\b": "5"
            },
            'reemplazos': {
                "QUEMADURA POR FRICCION": "QUEMADURA POR FRICCIÓN",
                "TRAZO SUGESTIVO DE FRACTURA": "FRACTURA"
            }
        }

    def normalizar_consulta(self, texto: str) -> str:
        texto = texto.upper()
        for patron, reemplazo in self.normalizador['reemplazos'].items():
            texto = texto.replace(patron, reemplazo)
        for patron, reemplazo in self.normalizador['sinonimos'].items():
            texto = re.sub(patron, reemplazo, texto, flags=re.IGNORECASE)
        return unidecode(texto).strip()

    def resolver_codigo(self, codigo: Any, *, fuente: str = "documento_explicito") -> dict[str, Any] | None:
        """Devuelve la clasificación jerárquica de un código sin consultar al LLM."""
        candidate = self.catalogo.candidato_por_codigo(
            codigo,
            fuente=fuente,
            razon="código validado contra cie10_estructura_completa.json",
        )
        if candidate:
            candidate["estado_codificacion"] = "codificado" if candidate["es_hoja"] else "provisional_grupo"
            if not candidate["es_hoja"]:
                candidate["razon_revision"] = "Falta seleccionar una subcategoría hoja del catálogo."
        return candidate

    def rerankear_candidatos(self, consulta: str, detalles: list[dict[str, Any]], top_n: int = 8) -> list[dict[str, Any]]:
        """Filtra códigos inexistentes y prioriza hojas con evidencia léxica de la consulta."""
        consulta_norm = self.catalogo._normalizar_texto(consulta)
        tokens = set(re.findall(r"[A-Z0-9]+", consulta_norm))
        ranked: list[dict[str, Any]] = []
        for raw in detalles:
            candidate = self.catalogo.candidato_por_codigo(
                raw.get("codigo"),
                fuente="faiss",
                razon="candidato recuperado y validado contra catálogo",
                puntaje=float(raw.get("score") or 0),
            )
            if not candidate:
                continue
            description_tokens = set(
                re.findall(r"[A-Z0-9]+", self.catalogo._normalizar_texto(candidate["descripcion"]))
            )
            overlap = len(tokens & description_tokens) / max(len(tokens), 1)
            candidate["puntaje"] = round(
                float(raw.get("score") or 0) + overlap * 0.7 + (0.25 if candidate["es_hoja"] else 0),
                4,
            )
            candidate["razon"] = (
                "candidato hoja FAISS con coincidencia léxica"
                if candidate["es_hoja"] and overlap
                else "candidato FAISS validado; requiere revisión por baja coincidencia textual"
            )
            ranked.append(candidate)
        return sorted(ranked, key=lambda item: item["puntaje"], reverse=True)[:top_n]

    def buscar(self, consulta: str, k: int = 50, lambda_param: float = 0.8, nivel_minimo: int = 2) -> dict:
        started_at = perf_counter()
        self._require_db()
        db = cast(Any, self.db)
        consulta_norm = self.normalizar_consulta(consulta)
        try:
            resultados = db.max_marginal_relevance_search(
                consulta_norm,
                k=k,
                lambda_param=lambda_param,
                filter={"nivel": {"$gte": nivel_minimo}}
            )
            jerarquia = self._procesar_resultados(resultados, consulta=consulta_norm)
            audit_logger.rag_event(
                action="cie10.buscar",
                outcome="success",
                service="cie10_retriever",
                resource={
                    "index_path": self.index_path,
                    "query_type": "diagnostico",
                    "index_name": "cie10",
                },
                metrics={
                    "duration_ms": round((perf_counter() - started_at) * 1000, 3),
                    "k": k,
                    "result_count": len(jerarquia.get("detalles", [])),
                },
            )
            return jerarquia
        except Exception as exc:
            audit_logger.rag_event(
                action="cie10.buscar",
                outcome="error",
                service="cie10_retriever",
                resource={
                    "index_path": self.index_path,
                    "query_type": "diagnostico",
                    "index_name": "cie10",
                },
                metrics={
                    "duration_ms": round((perf_counter() - started_at) * 1000, 3),
                    "k": k,
                },
                error={"class": exc.__class__.__name__, "message": str(exc)},
            )
            raise

    def _procesar_resultados(self, resultados, *, consulta: str = "") -> dict:
        jerarquia = {'bloques': [], 'categorias': [], 'subcategorias': [], 'detalles': []}
        for doc in resultados:
            metadata = doc.metadata
            catalog_entry = self.catalogo.obtener(metadata.get("codigo"))
            if catalog_entry is None:
                continue
            nivel = catalog_entry.nivel
            entrada = {
                'codigo': catalog_entry.codigo,
                'descripcion': catalog_entry.descripcion or doc.page_content.split(":")[-1].strip(),
                'nivel': nivel,
                'ruta': metadata.get('ruta', ''),
                'score': self._calcular_score(nivel),
                'es_hoja': catalog_entry.es_hoja,
                'padre': catalog_entry.padre,
            }
            if nivel == 1:
                jerarquia['bloques'].append(entrada)
            elif nivel == 2:
                jerarquia['categorias'].append(entrada)
            else:
                jerarquia['subcategorias'].append(entrada)
            jerarquia['detalles'].append(entrada)
        return jerarquia

    def _calcular_score(self, nivel: int) -> float:
        return {1: 0.3, 2: 0.6, 3: 1.0}.get(nivel, 0)

    def obtener_ruta_completa(self, codigo: str) -> list:
        for bloque in self.estructura['bloques']:
            if codigo == bloque['codigo']:
                return [bloque]
            for categoria in bloque['categorias']:
                if codigo == categoria['codigo']:
                    return [bloque, categoria]
                for subcat in categoria['subcategorias']:
                    if codigo == subcat['codigo']:
                        return [bloque, categoria, subcat]
        return []

    def validar_codigo(self, codigo: str) -> bool:
        return self.catalogo.es_codigo_valido(codigo)

    def mejores_resultados(self, resultados: dict, top_n: int = 3) -> list:
        return sorted(
            resultados.get("detalles", []),
            key=lambda item: (bool(item.get("es_hoja")), float(item.get("score") or 0)),
            reverse=True,
        )[:top_n]

    def resolver_diagnostico(self, consulta: str, *, codigo_explicito: str = "") -> dict[str, Any]:
        """Aplica la prioridad explícito > catálogo > alias > FAISS > LLM > grupo provisional."""
        texto = str(consulta or "").strip()
        candidatos: list[dict[str, Any]] = []
        rechazados: list[dict[str, Any]] = []
        codigo_documental = str(codigo_explicito or "").strip()
        if not codigo_documental:
            match = _CIE10_CODE_TOKEN.search(texto)
            codigo_documental = match.group(0) if match else ""
        if codigo_documental:
            explicit = self.resolver_codigo(codigo_documental)
            if explicit:
                candidatos.append(explicit)
                return self._resolution_payload(
                    explicit,
                    texto,
                    candidatos=candidatos,
                    rechazados=rechazados,
                    razon="código explícito del documento prevalece sobre candidatos semánticos",
                )
            rechazados.append({"codigo": codigo_documental, "razon": "código explícito ausente del catálogo local"})

        catalog_candidates = self.catalogo.resolver_texto(texto)
        if catalog_candidates:
            selected = catalog_candidates[0]
            candidatos.extend(catalog_candidates)
            rechazados.extend(
                {"codigo": item["codigo"], "razon": "alternativa no seleccionada por menor prioridad"}
                for item in catalog_candidates[1:]
            )
            return self._resolution_payload(
                selected,
                texto,
                candidatos=candidatos,
                rechazados=rechazados,
                razon=selected["razon"],
            )

        try:
            retrieval = self.buscar(texto, k=50)
            ranked = self.rerankear_candidatos(texto, retrieval.get("detalles", []))
        except Exception:
            ranked = []
        candidatos.extend(ranked)
        leaf = next((item for item in ranked if item["es_hoja"] and item["puntaje"] >= 0.45), None)
        if leaf:
            rechazados.extend(
                {"codigo": item["codigo"], "razon": "menor puntuación o código de grupo"}
                for item in ranked
                if item["codigo"] != leaf["codigo"]
            )
            return self._resolution_payload(
                leaf,
                texto,
                candidatos=candidatos,
                rechazados=rechazados,
                razon="candidato hoja FAISS rerankeado y validado contra catálogo",
            )

        # El LLM solo puede escoger entre códigos que luego pasan por la jerarquía local.
        try:
            raw_response = self.asignar_codigo_cie10(texto, k=50)
            parsed = _CIE10_CODE_TOKEN.search(raw_response or "")
            llm_candidate = self.resolver_codigo(parsed.group(0), fuente="llm_inferencia") if parsed else None
        except Exception:
            llm_candidate = None
        if llm_candidate:
            candidatos.append(llm_candidate)
            return self._resolution_payload(
                llm_candidate,
                texto,
                candidatos=candidatos,
                rechazados=rechazados,
                razon="inferencia LLM sustentada por código validado en catálogo",
            )

        group = next((item for item in ranked if not item["es_hoja"]), None)
        if group:
            return self._resolution_payload(
                group,
                texto,
                candidatos=candidatos,
                rechazados=rechazados,
                razon="solo existe un código de grupo compatible; requiere hoja específica y revisión manual",
            )
        return {
            "codigo": "",
            "descripcion": "",
            "estado_codificacion": "pendiente_validacion",
            "fuente": "sin_resolucion",
            "candidatos": candidatos,
            "candidatos_rechazados": rechazados,
            "razon_revision": "No se encontró un código CIE-10 compatible en el catálogo.",
            "texto_original": texto,
        }

    @staticmethod
    def _resolution_payload(
        selected: dict[str, Any],
        texto: str,
        *,
        candidatos: list[dict[str, Any]],
        rechazados: list[dict[str, Any]],
        razon: str,
    ) -> dict[str, Any]:
        return {
            **selected,
            "estado_codificacion": "codificado" if selected["es_hoja"] else "provisional_grupo",
            "candidatos": candidatos,
            "candidatos_rechazados": rechazados,
            "razon_seleccion": razon,
            "razon_revision": "" if selected["es_hoja"] else "Falta seleccionar una subcategoría hoja del catálogo.",
            "texto_original": texto,
        }

    # ---------------- Nuevas funciones integradas ----------------
    def generar_respuesta_groq(
        self,
        consulta: str,
        contexto: str,
        temperature: float = 0.0
    ) -> str:
        # prompt = (
        #     "Eres un asistente médico experto en codificación CIE-10. "
        #     "Tu tarea es asignar el código CIE-10 correcto basándote en la consulta del usuario y en el contexto proporcionado.\n\n"
        #     "El contexto consiste en fragmentos de un corpus que relaciona códigos y descripciones médicas.\n"
        #     "Responde únicamente en el siguiente formato:\n\n"
        #     "Código: <código> - Descripción: <descripción>\n\n"
        #     "Si no puedes determinar un código con certeza, responde exactamente: 'No se pudo determinar el código CIE-10.'\n\n"
        #     f"Consulta: {consulta}\n\n"
        #     f"Contexto:\n{contexto}"
        # )
        prompt = (
            "Eres un asistente médico experto en codificación CIE‑10. "
            "Tu tarea es asignar los códigos CIE‑10 correctos basándote en la consulta del usuario y en el contexto proporcionado.\n\n"
            "No conviertas códigos CUPS ni nombres de procedimientos en diagnósticos sin evidencia clínica cercana. "
            "No inventes códigos. Conserva cualquier código CIE‑10 explícito del documento. "
            "No devuelvas un código de grupo como definitivo si existe una subcategoría hoja compatible; "
            "distingue lesión traumática, diagnóstico histórico y diagnóstico actual.\n\n"
            "Si la consulta contiene múltiples condiciones separadas por '+', debes responder **un código y descripción por cada condición**, en líneas separadas.\n\n"
            "El contexto consiste en fragmentos de un corpus que relaciona códigos y descripciones médicas.\n"
            "Responde únicamente en el siguiente formato, una línea por código:\n\n"
            "Código: <código> - Descripción: <descripción>\n\n"
            "Si alguna condición no se puede codificar con certeza, responde exactamente: "
            "'No se pudo determinar el código CIE‑10 para: <texto de la condición>'.\n\n"
            f"Consulta: {consulta}\n\n"
            f"Contexto:\n{contexto}"
        )
        if self.llm_router is not None:
            response = generate_task_text(
                self.llm_router,
                task=LLMTask.CIE10_RESOLUTION,
                prompt=prompt,
                system_prompt="Eres un asistente médico experto.",
                metadata={"operation": "cie10_retriever_generate", "consulta": consulta[:200]},
                generation_config={"temperature": temperature, "top_p": 0.1},
            )
            return response.content.strip()

        if self.groq_client is None:
            raise RuntimeError("Cliente Groq no disponible para el flujo legacy de CIE-10.")
        groq_client = cast(Any, self.groq_client)
        response = groq_client.chat.completions.create(
            model=self.groq_model,
            temperature=temperature,
            top_p=0.1,
            messages=[
                {"role": "system", "content": "Eres un asistente médico experto."},
                {"role": "user", "content": prompt}
            ]
        )
        return response.choices[0].message.content.strip()

    def asignar_codigo_cie10(
        self,
        consulta: str,
        temperature: float = 0.0,
        k: int = 50,
        lambda_param: float = 0.8
    ) -> str:
        resultados = self.buscar(consulta, k=k, lambda_param=lambda_param)
        contexto = "".join(
            f"Código: {d['codigo']} - Descripción: {d['descripcion']}\n"
            for d in resultados['detalles']
        )
        return self.generar_respuesta_groq(consulta, contexto, temperature)


    def asignar_codigos_batch(
        self,
        texto_html: str,
        temperature: float = 0.0
    ) -> pd.DataFrame:
        """
        Extrae cada diagnóstico de los <li> en la sección Diagnósticos
        y les asigna un código CIE-10 usando RAG.
        """
        # 1. Extraer todos los <li> ... </li>
        items = re.findall(r"<b>Diagnósticos</b>.*?<ol>(.*?)</ol>", texto_html, flags=re.DOTALL)
        lista_html = items[0] if items else texto_html  # si no encuentra sección, procesa todo

        diagnosticos = re.findall(r"<li>(.*?)</li>", lista_html, flags=re.DOTALL)
        diagnosticos = [d.strip() for d in diagnosticos if d.strip()]

        # 2. Para cada diagnóstico, asignar código
        resultados = []
        for diag in diagnosticos:
            codigo = self.asignar_codigo_cie10(diag, temperature)
            resultados.append({
                'diagnostico': diag,
                'codigo_asignado': codigo
            })

        return pd.DataFrame(resultados)

    # def asignar_codigos_batch(
    #     self,
    #     texto_html: str,
    #     temperature: float = 0.0
    # ) -> pd.DataFrame:
    #     # Extraer diagnósticos numerados de un HTML simple
    #     items = re.findall(r"\d+\.\s*([^<]+)", texto_html)
    #     resultados = []
    #     for diag in items:
    #         codigo = self.asignar_codigo_cie10(diag.strip(), temperature)
    #         resultados.append({'diagnostico': diag.strip(), 'codigo_asignado': codigo})
    #     return pd.DataFrame(resultados)

# Ejemplo de uso si se ejecuta como script

if __name__ == "__main__":
    retriever = CIE10Retriever(
        faiss_index_path="faiss_principal_jerarquico",
        estructura_json_path="cie10_estructura_completa.json"
    )
    html_input = (
        "<p>1.Trauma en HOMBRO.<br>"
        "2. Trauma en pie izquierdo + herida.<br>"
        "3. Fractura en falange proximal de 1er dedo pie izquierdo.<br>"
        "4. Contusión del tobillo (diagnóstico presuntivo inicial).<br>"
        "5. Contusión de otras partes y de las no especificadas del pie (diagnóstico presuntivo inicial)."  
        "</p>"
    )
    #Trauma en hombro izquierdo
    #Trauma en tobillo izquierdo
    df_result = retriever.asignar_codigos_batch(html_input)
    print(df_result)
