import logging
import re
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Annotated, Any, cast

from bson import ObjectId
from fastapi import APIRouter, Depends, HTTPException, Path, Request
from fastapi.responses import JSONResponse
from starlette import status
from starlette.datastructures import UploadFile

from app.auth import get_current_user
from app.batch_processing.infrastructure.mongo_repositories import MongoBatchCaseRepository
from app.core.dependencies import get_services
from app.models import UserInDB
from app.schemas.history import Historia
from app.services.clinical_document_projection import (
    render_document_analysis_html,
    serialize_analysis_document,
)
from app.services.demo_identity_service import get_demo_identity_service


if TYPE_CHECKING:
    from app.core.services import AppServices
else:
    AppServices = Any


router = APIRouter()
logger = logging.getLogger(__name__)
CurrentUser = Annotated[UserInDB, Depends(get_current_user)]
ServicesDep = Annotated[AppServices, Depends(get_services)]
_CIE10_INLINE_PATTERN = re.compile(
    r"^\s*(?P<codigo>[A-TV-Z]\d{2}[0-9A-Z]?(?:\.[0-9A-Z]{1,2})?)\s*(?:[-:–—]\s*|\s+)(?P<descripcion>.+)$",
    flags=re.IGNORECASE,
)
LEGACY_DOCUMENT_ID_WARNING = (
    '299 - "The /historia/{id} document endpoint is deprecated; use /api/documentos/{documento_id} instead."'
)
_NON_CASE_DOCUMENT_TYPES = {"epicrisis", "epicrisis_case_cache", "llm_task_cache"}


def _build_case_group_key(doc: dict) -> str:
    case_key = str(doc.get("case_key") or "").strip()
    if case_key:
        return case_key
    nombre_paciente = str(doc.get("nombre_paciente") or "desconocido").strip() or "desconocido"
    return f"legacy::{nombre_paciente}"


def _has_meaningful_patient_name(value: Any) -> bool:
    normalized = str(value or "").strip()
    return bool(normalized and normalized.lower() != "desconocido")


def _pick_case_group_value(documents: list[dict[str, Any]], field_name: str) -> str:
    for document in documents:
        candidate = str(document.get(field_name) or "").strip()
        if candidate:
            return candidate
    return ""


def _coerce_text_form_value(value: Any) -> str:
    if isinstance(value, UploadFile):
        return ""
    return str(value or "").strip()


def _extraer_cie10_inline(diagnostico: str):
    if not diagnostico:
        return None
    match = _CIE10_INLINE_PATTERN.match(str(diagnostico).strip())
    if not match:
        return None
    return {
        "codigo": match.group("codigo").upper(),
        "descripcion": match.group("descripcion").strip(),
    }


def _sanitize_cie10_entries(entries):
    """
    Oculta mensajes técnicos de infraestructura para no mostrarlos en UI.
    """
    if not isinstance(entries, list):
        return entries

    sanitized = []
    for item in entries:
        if not isinstance(item, dict):
            sanitized.append(item)
            continue

        diagnostico = str(item.get("diagnostico", "")).strip()
        codigo = str(item.get("codigo", "")).strip()
        descripcion = str(item.get("descripcion", "")).strip()
        descripcion_low = descripcion.lower()
        codigo_low = codigo.lower()

        # Si el diagnostico ya trae codigo CIE-10, priorizarlo sobre estados técnicos.
        inline = _extraer_cie10_inline(diagnostico)
        if inline and codigo_low in {"", "error", "pendiente", "sin codigo", "sin código"}:
            item = {
                **item,
                "codigo": inline["codigo"],
                "descripcion": inline["descripcion"],
            }
            sanitized.append(item)
            continue

        if codigo_low == "error" or "indice faiss cie-10 no disponible" in descripcion_low:
            item = {
                **item,
                "codigo": "Pendiente",
                "descripcion": "Codificacion automatica no disponible para este diagnostico.",
            }

        sanitized.append(item)
    return sanitized


def _apply_legacy_document_deprecation_headers(response: JSONResponse) -> JSONResponse:
    response.headers["Deprecation"] = "true"
    response.headers["Warning"] = LEGACY_DOCUMENT_ID_WARNING
    return response


def _serialize_history_document(doc: dict) -> dict:
    serialized = serialize_analysis_document(doc) or {}
    if "codigos_cie10" in serialized:
        serialized["codigos_cie10"] = _sanitize_cie10_entries(serialized["codigos_cie10"])
    return serialized


def _get_owned_history_document(
    *,
    document_id: str,
    username: str,
    mongo,
    not_found_detail: str,
) -> dict:
    if not ObjectId.is_valid(document_id):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="ID no válido")

    doc = mongo.collection.find_one({"_id": ObjectId(document_id)})
    if not doc or doc.get("usuario") != username:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=not_found_detail)

    return _serialize_history_document(doc)


def build_user_histories_snapshot(*, username: str, services: AppServices) -> dict:
    """
    Construye una instantánea normalizada de documentos agrupados por paciente y por caso
    para un usuario autenticado.
    """
    mongo = services.mongo_analyses
    services.clinical_document_service.repair_existing_documents_for_user(username)
    resultados = mongo.collection.find(
        {
            "usuario": username,
            "tipo_documento": {"$nin": sorted(_NON_CASE_DOCUMENT_TYPES)},
        }
    )
    historias_por_paciente = {}
    historias_por_caso = {}
    case_repository = MongoBatchCaseRepository()
    case_lookup_cache = {}

    for doc in resultados:
        serialized_doc = _serialize_history_document(doc)
        nombre_paciente = serialized_doc.get("nombre_paciente", "desconocido")
        historias_por_paciente.setdefault(nombre_paciente, []).append(serialized_doc)

        case_group_key = _build_case_group_key(serialized_doc)
        normalized_case_key = str(serialized_doc.get("case_key") or "").strip()
        if case_group_key not in historias_por_caso:
            if normalized_case_key not in case_lookup_cache:
                case_lookup_cache[normalized_case_key] = (
                    case_repository.get_user_case(username, normalized_case_key)
                    if normalized_case_key
                    else {}
                ) or {}
            case_info = case_lookup_cache[normalized_case_key]
            historias_por_caso[case_group_key] = {
                "group_key": case_group_key,
                "case_key": normalized_case_key,
                "case_number": str(
                    serialized_doc.get("case_number") or case_info.get("case_number") or ""
                ).strip(),
                "patient_name": nombre_paciente,
                "patient_id": str(
                    serialized_doc.get("patient_id") or case_info.get("patient_id") or ""
                ).strip(),
                "documentos": [],
                "tipos_documento": [],
                "ready_for_epicrisis": bool(case_info.get("ready_for_epicrisis", False)),
                "epicrisis_status": str(case_info.get("epicrisis_status") or "pendiente"),
                "epicrisis_rule_status": str(case_info.get("epicrisis_rule_status") or ""),
                "epicrisis_blocking_reason": str(case_info.get("epicrisis_blocking_reason") or ""),
                "epicrisis_missing_documents": list(case_info.get("epicrisis_missing_documents") or []),
                "epicrisis_url": str(
                    case_info.get("epicrisis_url")
                    or (f"/epicrisis?case_key={normalized_case_key}" if normalized_case_key else "")
                ),
            }

        case_group = cast(dict[str, Any], historias_por_caso[case_group_key])
        documents = cast(list[dict[str, Any]], case_group["documentos"])
        documents.append(serialized_doc)

    for paciente in historias_por_paciente:
        historias_por_paciente[paciente].sort(key=lambda x: x.get("fecha_analisis", ""), reverse=True)

    for case_group in historias_por_caso.values():
        raw_documents = case_group.get("documentos")
        document_items = raw_documents if isinstance(raw_documents, list) else []
        documents = [item for item in document_items if isinstance(item, dict)]
        documents.sort(key=lambda x: x.get("fecha_analisis", ""), reverse=True)
        case_group["documentos"] = documents
        if not _has_meaningful_patient_name(case_group.get("patient_name")):
            case_group["patient_name"] = _pick_case_group_value(documents, "nombre_paciente") or "desconocido"
        if not str(case_group.get("patient_id") or "").strip():
            case_group["patient_id"] = _pick_case_group_value(documents, "patient_id")
        if not str(case_group.get("case_number") or "").strip():
            case_group["case_number"] = _pick_case_group_value(documents, "case_number")
        case_group["tipos_documento"] = sorted(
            {str(item.get("tipo_documento") or "sin_tipo") for item in documents}
        )

    return {
        "historias_por_paciente": historias_por_paciente,
        "historias_por_caso": historias_por_caso,
    }


@router.post("/analisis_historia")
async def realizar_analisis_historia(request: Request, services: ServicesDep):
    """Realiza un análisis de la historia clínica"""
    try:
        from modules.processing.resumen_google import HistoriaClinicaRequest

        content_type = request.headers.get("Content-Type")
        if content_type == "application/json":
            data = await request.json()
            descripcion = _coerce_text_form_value(data.get("descripcion"))
        elif content_type == "application/x-www-form-urlencoded":
            form_data = await request.form()
            descripcion = _coerce_text_form_value(form_data.get("descripcion"))
        else:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Content-Type no soportado")

        if not descripcion:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="El campo 'descripcion' es requerido"
            )

        analisis = HistoriaClinicaRequest.analizar_historiaclinica(descripcion)
        id_documento = HistoriaClinicaRequest.guardar_historia(descripcion, analisis)

        return {
            "mensaje": "Análisis completado exitosamente",
            "id_documento": id_documento,
            "fecha_analisis": datetime.now().isoformat(),
            "analisis": analisis,
        }
    except HTTPException as he:
        raise he
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))


@router.post("/asignar_codigos")
async def asignar_codigos(request: Request, services: ServicesDep):
    """Asigna códigos CIE-10 a diagnósticos extraídos desde HTML o estructura."""
    retriever = services.cie10_retriever
    try:
        data = await request.json()
        analysis_structured = data.get("analysis_structured")
        analisis_html = data.get("analisis")
        if not analisis_html and isinstance(analysis_structured, dict):
            analisis_html = render_document_analysis_html(
                {
                    "analysis_structured": analysis_structured,
                    "document_type": data.get("document_type") or "historia_clinica",
                }
            )
        if not analisis_html:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Se requiere 'analisis' HTML o 'analysis_structured' con diagnosticos extraídos.",
            )

        # Utilizar el retriever para asignar códigos batch
        df_codigos = retriever.asignar_codigos_batch(analisis_html)
        # Convertir a lista de dicts para la respuesta JSON
        registros = df_codigos.to_dict(orient="records")

        return {"mensaje": "Códigos asignados exitosamente", "codigos": registros}
    except HTTPException as he:
        raise he
    except Exception as e:
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))


@router.get("/analizar_ultimo_pdf")
def analizar_ultimo_pdf(services: ServicesDep):
    """
    Consulta el último documento con 'contenido_pdf' en MongoDB
    y realiza el análisis usando Groq.
    """
    mongo_storage = services.mongo_storage
    try:
        from modules.processing.resumen_google import HistoriaClinicaRequest

        # Buscar el documento más reciente con contenido_pdf
        ultimo_doc = mongo_storage.collection.find_one(
            {"contenido_pdf": {"$exists": True}}, sort=[("fecha_guardado", -1)]
        )

        if not ultimo_doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="No se encontró ningún documento con contenido_pdf.",
            )

        descripcion = ultimo_doc.get("contenido_pdf")
        nombre_archivo = ultimo_doc.get("nombre_archivo")
        usuario = ultimo_doc.get("usuario")

        if not descripcion:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="El campo 'contenido_pdf' está vacío."
            )

        # Ejecutar análisis con el contenido del PDF
        analisis = HistoriaClinicaRequest.analizar_historiaclinica(descripcion)

        # Guardar el resultado como un nuevo documento
        id_doc = HistoriaClinicaRequest.guardar_historia(descripcion=descripcion, analisis=analisis)

        return {
            "mensaje": "Análisis completado exitosamente",
            "id_documento": id_doc,
            "nombre_archivo": nombre_archivo,
            "usuario": usuario,
            "fecha_analisis": datetime.now().isoformat(),
            "analisis": analisis,
        }

    except HTTPException as he:
        raise he
    except Exception as e:
        logger.exception("Error en analizar_ultimo_pdf")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error interno: {str(e)}"
        )


@router.post("/guardar_historia")
async def guardar_historia(
    data: Historia,
    current_user: CurrentUser,
    services: ServicesDep,
):
    mongo = services.mongo_analyses
    documento = {
        "descripcion": data.descripcion,
        "analisis": data.analisis,
        "usuario": current_user.username,
        "fecha_guardado": datetime.now(UTC),
    }
    mongo.collection.insert_one(documento)
    return {"status": "guardado", "fecha": documento["fecha_guardado"]}


@router.get("/procesar_ultimo_pdf")
async def procesar_ultimo_pdf(current_user: CurrentUser, services: ServicesDep):
    """
    Procesa el ultimo PDF de historia clinica del usuario autenticado.
    Si falla el analisis principal (Gemini), usa fallback con Groq para evitar 500.
    """
    # Deprecated for the primary dashboard/cases upload flow.
    # Kept temporarily for legacy surfaces outside the mini-batch migration scope.
    mongo_storage = services.mongo_storage

    try:
        from app.services.clinical_document_service import ClinicalDocumentRequest

        doc = mongo_storage.collection.find_one(
            {"contenido_pdf": {"$exists": True}, "usuario": current_user.username},
            sort=[("fecha_guardado", -1)],
        )
        if not doc:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND, detail="No se encontró ningún PDF para este usuario."
            )

        desc = doc.get("contenido_pdf")
        if not desc:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="El campo 'contenido_pdf' está vacío."
            )

        nombre = doc.get("nombre_archivo", "desconocido")
        payload = services.clinical_document_service.process_and_persist(
            ClinicalDocumentRequest(
                raw_text=desc,
                detected_type="historia_clinica",
                username=current_user.username,
                original_name=nombre,
                ingestion_source="manual",
            )
        )
        return {
            **payload,
            "mensaje": payload.get("mensaje") or "Analisis y codificacion completados exitosamente",
        }

    except HTTPException:
        raise
    except Exception as e:
        logger.exception("Error en procesar_ultimo_pdf")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Error interno al procesar el PDF: {e}"
        )


@router.get("/mis_historias")
async def obtener_mis_historias(current_user: CurrentUser, services: ServicesDep):
    """
    Devuelve todas las historias analizadas asociadas al usuario autenticado,
    agrupadas por paciente.
    """
    try:
        snapshot = build_user_histories_snapshot(username=current_user.username, services=services)
        return get_demo_identity_service(services).project_snapshot(
            username=current_user.username,
            snapshot=snapshot,
        )

    except Exception:
        logger.exception("Error al obtener historias")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error al consultar las historias."
        )


@router.post("/documentos/eliminar_seleccion")
async def eliminar_documentos_seleccionados(
    payload: dict,
    current_user: CurrentUser,
    services: ServicesDep,
):
    """
    Elimina documentos seleccionados por ID del usuario autenticado.
    Nota: no elimina documentos tipo 'epicrisis' para preservar cache.
    """
    mongo = services.mongo_analyses
    try:
        document_ids = payload.get("document_ids") or []
        if not isinstance(document_ids, list) or not document_ids:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="'document_ids' debe ser una lista no vacía"
            )

        valid_ids = [
            doc_id for doc_id in document_ids if isinstance(doc_id, str) and ObjectId.is_valid(doc_id)
        ]
        if not valid_ids:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST, detail="No hay IDs válidos para eliminar"
            )

        object_ids = [ObjectId(doc_id) for doc_id in valid_ids]
        delete_result = mongo.collection.delete_many(
            {
                "_id": {"$in": object_ids},
                "usuario": current_user.username,
                "tipo_documento": {"$nin": ["epicrisis", "epicrisis_case_cache"]},
            }
        )

        return {
            "mensaje": "Documentos eliminados",
            "solicitados": len(document_ids),
            "ids_validos": len(valid_ids),
            "eliminados": int(delete_result.deleted_count),
        }
    except HTTPException:
        raise
    except Exception:
        logger.exception("Error eliminando documentos seleccionados")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Error al eliminar documentos seleccionados",
        )


@router.get("/api/documentos/{documento_id}")
async def obtener_documento_por_id(
    documento_id: Annotated[str, Path(..., description="ID del documento en MongoDB")],
    current_user: CurrentUser,
    services: ServicesDep,
):
    """Entrega un documento individual del usuario autenticado."""
    mongo = services.mongo_analyses
    try:
        document = _get_owned_history_document(
            document_id=documento_id,
            username=current_user.username,
            mongo=mongo,
            not_found_detail="Documento no encontrado o acceso denegado",
        )
        projected = get_demo_identity_service(services).project_document(
            username=current_user.username,
            document=document,
        )
        return projected if isinstance(projected, dict) else {}
    except HTTPException:
        raise
    except Exception:
        logger.exception("Error al obtener documento por ID")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Error interno al consultar el documento",
        )


@router.get("/historia/{id}")
async def obtener_historia_por_id(
    id: Annotated[str, Path(..., description="ID del documento en MongoDB")],
    current_user: CurrentUser,
    services: ServicesDep,
):
    """
    Devuelve los datos completos de una historia si pertenece al usuario autenticado.
    """
    mongo = services.mongo_analyses
    try:
        doc = _get_owned_history_document(
            document_id=id,
            username=current_user.username,
            mongo=mongo,
            not_found_detail="Historia no encontrada o acceso denegado",
        )
        projected = get_demo_identity_service(services).project_document(
            username=current_user.username,
            document=doc,
        )
        return _apply_legacy_document_deprecation_headers(JSONResponse(content=projected or {}))

    except HTTPException:
        # Re-lanzar excepciones HTTP manejadas
        raise
    except Exception:
        logger.exception("Error al obtener historia por ID")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno al consultar la historia"
        )


@router.get("/documentos_paciente/{nombre_paciente}")
async def obtener_documentos_paciente(
    nombre_paciente: Annotated[str, Path(..., description="Nombre del paciente")],
    current_user: CurrentUser,
    services: ServicesDep,
):
    """
    Devuelve todos los documentos de un paciente específico del usuario autenticado.
    """
    mongo = services.mongo_analyses
    try:
        real_patient_name = get_demo_identity_service(services).resolve_patient_name(
            username=current_user.username,
            visible_patient_name=nombre_paciente,
        )
        # Buscar documentos del paciente específico para este usuario
        resultados = mongo.collection.find(
            {
                "usuario": current_user.username,
                "nombre_paciente": real_patient_name,
                "tipo_documento": {"$nin": ["epicrisis", "epicrisis_case_cache"]},
            }
        ).sort("fecha_analisis", -1)  # Ordenar por fecha desc

        documentos = []
        for doc in resultados:
            serialized = _serialize_history_document(doc)
            projected = get_demo_identity_service(services).project_document(
                username=current_user.username,
                document=serialized,
            )
            if isinstance(projected, dict):
                documentos.append(projected)

        if not documentos:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"No se encontraron documentos para el paciente '{nombre_paciente}'",
            )

        # Agrupar por tipo de documento
        documentos_por_tipo = {}
        for doc in documentos:
            tipo = doc.get("tipo_documento", "sin_tipo")
            if tipo not in documentos_por_tipo:
                documentos_por_tipo[tipo] = []
            documentos_por_tipo[tipo].append(doc)

        return {
            "nombre_paciente": nombre_paciente,
            "total_documentos": len(documentos),
            "documentos_por_tipo": documentos_por_tipo,
            "todos_los_documentos": documentos,
        }

    except HTTPException:
        raise
    except Exception:
        logger.exception("Error al obtener documentos del paciente")
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Error interno al consultar los documentos",
        )
