from __future__ import annotations

import re
from copy import deepcopy
from typing import Any

from app.case_epicrisis.application.utils.common import _normalize_ascii, _normalize_whitespace


_CLINICAL_SOURCES = frozenset(
    {
        "historia_clinica",
        "radiologia",
        "laboratorio",
        "quirurgico",
        "generico",
    }
)
_BLOCKING_ALERTS = frozenset(
    {
        "pending_canonical_review",
        "asociacion_canonica_no_confirmada",
        "asociacion_canonica_pendiente_revision",
        "evidencia_contradictoria",
        "contradiccion",
        "contradictorio",
    }
)
_RESULT_TEXT_MARKERS = re.compile(
    r"\b(resultado|resultados|hallazgo|hallazgos|conclusion|conclusión|interpretacion|interpretación|"
    r"sin\s+(lesion|lesión|alteraciones|evidencia)|descartad[oa]|fractura|consolidacion|consolidación)\b",
    re.IGNORECASE,
)
_RESULT_NUMBER_MARKER = re.compile(r"\b\d+(?:[.,]\d+)?\b")
_ORDER_ONLY_MARKERS = re.compile(
    r"\b(se\s+solicita|solicitud|ordenad[oa]|pendiente|toma\s+de|para\s+descartar)\b",
    re.IGNORECASE,
)
_PLACEHOLDERS = {"", "no interpretado", "no interpretada", "pendiente", "sin interpretación"}


def _evidence_sources(aid: dict[str, Any]) -> set[str]:
    sources = {_normalize_ascii(aid.get("fuente")).casefold()}
    sources.update(
        _normalize_ascii(item.get("fuente") or item.get("document_type")).casefold()
        for item in aid.get("evidencias") or []
        if isinstance(item, dict)
    )
    return {source for source in sources if source}


def _has_blocking_review(aid: dict[str, Any]) -> bool:
    alerts = {
        _normalize_ascii(value).casefold()
        for value in aid.get("alertas") or []
        if _normalize_whitespace(value)
    }
    if alerts.intersection(_BLOCKING_ALERTS):
        return True
    return any(isinstance(item, dict) for item in aid.get("incidencias") or [])


def _is_result_bearing(evidence: dict[str, Any]) -> bool:
    if evidence.get("interpretacion_estructurada"):
        return True
    excerpt = _normalize_whitespace(evidence.get("extracto") or evidence.get("excerpt"))
    if not excerpt:
        return False
    if _RESULT_TEXT_MARKERS.search(excerpt):
        return True
    if _ORDER_ONLY_MARKERS.search(excerpt):
        return False
    return bool(_RESULT_NUMBER_MARKER.search(excerpt))


def _clinical_evidence(aid: dict[str, Any]) -> list[dict[str, Any]]:
    evidence: list[dict[str, Any]] = []
    for item in aid.get("evidencias") or []:
        if not isinstance(item, dict):
            continue
        source = _normalize_ascii(item.get("fuente") or item.get("document_type")).casefold()
        evidence_id = _normalize_whitespace(item.get("evidencia_id") or item.get("evidence_id"))
        excerpt = _normalize_whitespace(item.get("extracto") or item.get("excerpt"))
        if source not in _CLINICAL_SOURCES or not evidence_id or not excerpt:
            continue
        evidence.append(
            {
                "id": evidence_id,
                "tipo": item.get("tipo_evidencia") or "referencia_narrativa",
                "fuente": source,
                "pagina": item.get("pagina") or item.get("page"),
                "seccion": item.get("seccion") or item.get("section") or "",
                "termino_original": item.get("termino_original") or item.get("original_term") or "",
                "extracto": excerpt,
                "puede_sustentar_resultado": _is_result_bearing(item),
            }
        )
    return evidence


def build_interpretation_payload(aid: dict[str, Any], *, max_chars: int) -> dict[str, Any] | None:
    """Project one aid and only its bounded clinical evidence to the LLM."""
    if not isinstance(aid, dict) or not aid.get("key"):
        return None
    state = _normalize_ascii(aid.get("estado_interpretacion")).casefold()
    sources = _evidence_sources(aid)
    if (
        state in {"interpretado", "excluido"}
        or not sources.intersection(_CLINICAL_SOURCES)
        or _has_blocking_review(aid)
    ):
        return None
    evidence = _clinical_evidence(aid)
    if not evidence:
        return None
    payload = {
        "key": str(aid.get("key") or ""),
        "tipo": str(aid.get("tipo") or ""),
        "nombre": str(aid.get("nombre") or ""),
        "termino_canonico": str(aid.get("canonical_term") or ""),
        "estado_deterministico": state or "no_interpretado",
        "interpretacion_actual": str(aid.get("concepto") or ""),
        "evidencias": evidence,
    }
    if max_chars > 0:
        while len(str(payload)) > max_chars and len(payload["evidencias"]) > 1:
            payload["evidencias"].pop()
        excerpt_limit = max(200, max_chars // max(1, len(payload["evidencias"])))
        for item in payload["evidencias"]:
            item["extracto"] = item["extracto"][:excerpt_limit]
    return payload


def _unique(values: list[Any]) -> list[str]:
    result: list[str] = []
    for value in values:
        text = _normalize_whitespace(value)
        if text and text not in result:
            result.append(text)
    return result


def apply_interpretation_proposal(
    aid: dict[str, Any],
    proposal: dict[str, Any],
    *,
    provider: str,
    model: str,
    prompt_version: str,
) -> dict[str, Any]:
    """Apply only a proposal that passes deterministic evidence checks."""
    updated = deepcopy(aid)
    evidence = _clinical_evidence(updated)
    evidence_by_id = {str(item["id"]): item for item in evidence}
    evidence_ids = _unique(proposal.get("evidence_ids") or proposal.get("e") or [])
    contradictions = _unique(proposal.get("contradictions") or proposal.get("c") or [])
    decision = _normalize_ascii(
        proposal.get("decision") or proposal.get("d") or "pendiente_revision"
    ).casefold()
    interpretation = _normalize_whitespace(proposal.get("interpretation") or proposal.get("i"))
    reason = _normalize_whitespace(proposal.get("reason") or proposal.get("r"))
    valid_ids = [item for item in evidence_ids if item in evidence_by_id]
    invalid_ids = [item for item in evidence_ids if item not in evidence_by_id]
    supporting_ids = [item for item in valid_ids if evidence_by_id[item]["puede_sustentar_resultado"]]
    valid = (
        decision == "interpretado"
        and bool(interpretation)
        and _normalize_ascii(interpretation).casefold() not in _PLACEHOLDERS
        and bool(valid_ids)
        and not invalid_ids
        and bool(supporting_ids)
        and not contradictions
    )
    if invalid_ids:
        reason = reason or "La propuesta cita evidencia que no pertenece a la ayuda."
    if not supporting_ids:
        reason = reason or "La evidencia no contiene un resultado clínico atribuible."
    if contradictions:
        reason = reason or "La propuesta contiene contradicciones clínicas."
    if not valid:
        decision = "pendiente_revision"
        interpretation = ""
        updated["estado_interpretacion"] = "pendiente_revision"
        updated["interpretado"] = False
        updated["concepto"] = "No interpretado"
        alerts = _unique(updated.get("alertas") or [])
        if "interpretacion_asistida_pendiente_revision" not in alerts:
            alerts.append("interpretacion_asistida_pendiente_revision")
        updated["alertas"] = alerts
    else:
        updated["estado_interpretacion"] = "interpretado"
        updated["interpretado"] = True
        updated["concepto"] = interpretation

    updated["interpretacion_asistida"] = {
        "decision": decision,
        "propuesta": interpretation,
        "evidencia_ids": valid_ids,
        "contradicciones": contradictions,
        "razon": reason,
        "proveedor": provider,
        "modelo": model,
        "version_prompt": prompt_version,
    }
    return updated
