"""Rutas HTTP del flujo de carga masiva por ZIP."""

from __future__ import annotations

import os
from pathlib import Path as FilePath
from threading import Thread
from typing import Annotated

from fastapi import (
    APIRouter,
    Depends,
    File,
    Form,
    HTTPException,
    Path,
    Query,
    Request,
    UploadFile,
)
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
from pydantic import BaseModel, Field
from starlette import status

from app.auth import get_current_user
from app.batch_processing.application.command_use_cases import BatchDeletionError
from app.batch_processing.domain.models import BATCH_STATUS_ELIMINANDO
from app.core.dependencies import get_services
from app.core.logging import get_log_context, set_log_context
from app.core.services import AppServices
from app.models import UserInDB
from app.routes.shell_navigation import build_shell_context
from app.services.demo_identity_service import get_demo_identity_service


router = APIRouter()
CurrentUser = Annotated[UserInDB, Depends(get_current_user)]
ServicesDep = Annotated[AppServices, Depends(get_services)]
_BATCH_DELETING_MESSAGE = "El lote está siendo eliminado y no admite más operaciones."


def _project_batch_payload(
    *,
    services: AppServices,
    username: str,
    payload: dict | None,
) -> dict:
    if not isinstance(payload, dict):
        return {}
    projected = get_demo_identity_service(services).project_document(
        username=username,
        document=payload,
    )
    return dict(projected or payload)


def _project_batch_payload_list(
    *,
    services: AppServices,
    username: str,
    items: list[dict],
) -> list[dict]:
    return [
        _project_batch_payload(services=services, username=username, payload=item)
        for item in items
        if isinstance(item, dict)
    ]


def _enqueue_batch_file_clinical_materialization(
    *,
    services: AppServices,
    batch_id: str,
    file_id: str,
) -> str:
    dispatcher_mode = os.getenv("BATCH_DISPATCHER", "inprocess").strip().lower()
    if dispatcher_mode == "celery":
        from app.batch_processing.celery_app import materialize_batch_file_job

        result = materialize_batch_file_job.delay(batch_id, file_id, audit_context=get_log_context())
        services.batch_runtime.queue_batch_file_clinical.execute(
            batch_id,
            file_id,
            job_id=result.id,
        )
        return result.id

    job_id = f"inprocess-{file_id}"
    audit_context = get_log_context()
    services.batch_runtime.queue_batch_file_clinical.execute(
        batch_id,
        file_id,
        job_id=job_id,
    )
    def _run_materialize() -> None:
        set_log_context(audit_context)
        services.batch_runtime.materialize_batch_file.execute(batch_id, file_id)

    Thread(
        target=_run_materialize,
        daemon=True,
    ).start()
    return job_id


def _enqueue_batch_epicrisis_generation(
    *,
    services: AppServices,
    batch_id: str,
    username: str,
    case_keys: list[str],
) -> str:
    normalized_case_keys = [
        str(case_key or "").strip()
        for case_key in case_keys or []
        if str(case_key or "").strip()
    ]
    dispatcher_mode = os.getenv("BATCH_DISPATCHER", "inprocess").strip().lower()
    if dispatcher_mode == "celery":
        from app.batch_processing.celery_app import generate_all_epicrisis_job

        result = generate_all_epicrisis_job.delay(
            batch_id,
            username,
            normalized_case_keys,
            audit_context=get_log_context(),
        )
        return result.id

    if not normalized_case_keys:
        return ""

    from app.batch_processing.celery_app import run_case_epicrisis_job
    audit_context = get_log_context()

    def _run_inprocess_bulk() -> None:
        set_log_context(audit_context)
        for case_key in normalized_case_keys:
            try:
                run_case_epicrisis_job(username, case_key, False, audit_context=audit_context)
            except Exception:
                continue
        services.batch_runtime.recompute_batch_bulk_epicrisis.execute(batch_id)

    job_id = f"inprocess-bulk-epicrisis-{batch_id}"
    Thread(target=_run_inprocess_bulk, daemon=True).start()
    return job_id


def _enqueue_batch_epicrisis_excel_generation(
    *,
    services: AppServices,
    batch_id: str,
) -> str:
    dispatcher_mode = os.getenv("BATCH_DISPATCHER", "inprocess").strip().lower()
    if dispatcher_mode == "celery":
        from app.batch_processing.celery_app import generate_batch_epicrisis_excel_job

        result = generate_batch_epicrisis_excel_job.delay(batch_id, "", audit_context=get_log_context())
        return result.id

    job_id = f"inprocess-batch-epicrisis-excel-{batch_id}"
    audit_context = get_log_context()
    def _run_generate_excel() -> None:
        set_log_context(audit_context)
        services.batch_runtime.generate_batch_epicrisis_excel.execute(batch_id=batch_id, job_id=job_id)
    Thread(
        target=_run_generate_excel,
        daemon=True,
    ).start()
    return job_id


class ResolveAssociationPayload(BaseModel):
    """Payload mínimo de resolución manual para documentos ambiguos."""

    case_key: str = ""
    patient_name: str = ""
    patient_id: str = ""
    procedure_code: str = ""
    reason: str = Field(..., min_length=1, max_length=500)


class DeleteBatchPayload(BaseModel):
    confirmation_batch_id: str = Field(..., min_length=1, max_length=200)
    confirmation_phrase: str = Field(..., min_length=1, max_length=200)


def _get_owned_batch(
    *,
    services: AppServices,
    batch_id: str,
    username: str,
) -> dict:
    batch = services.batch_runtime.get_batch_status.execute(batch_id)
    if not batch or batch.get("usuario") != username:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Lote no encontrado"
        )
    if (
        str(batch.get("status") or "").strip() == BATCH_STATUS_ELIMINANDO
        or str(batch.get("deletion_status") or "").strip() == "requested"
    ):
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=_BATCH_DELETING_MESSAGE)
    return batch


def _get_owned_batch_record(
    *,
    services: AppServices,
    batch_id: str,
    username: str,
) -> dict:
    batch_repository = services.batch_runtime.get_batch_status.batch_repository
    batch = batch_repository.get_batch(batch_id)
    if not batch or batch.get("usuario") != username:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Lote no encontrado"
        )
    if (
        str(batch.get("status") or "").strip() == BATCH_STATUS_ELIMINANDO
        or str(batch.get("deletion_status") or "").strip() == "requested"
    ):
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=_BATCH_DELETING_MESSAGE)
    return batch


def _serialize_excel_epicrisis_meta(batch: dict) -> dict:
    return {
        "batch_id": batch.get("batch_id") or batch.get("_id") or "",
        "export_scope": "batch",
        "export_kind": "case_workbook",
        "status": batch.get("excel_epicrisis_status", "pendiente"),
        "job_id": batch.get("excel_epicrisis_job_id", ""),
        "requested_at": batch.get("excel_epicrisis_requested_at", ""),
        "generated_at": batch.get("excel_epicrisis_generated_at", ""),
        "error": batch.get("excel_epicrisis_error", ""),
        "filename": batch.get("excel_epicrisis_filename", ""),
        "download_url": batch.get("excel_epicrisis_download_url", ""),
        "included_count": batch.get("excel_epicrisis_included_count", 0),
        "omitted_count": batch.get("excel_epicrisis_omitted_count", 0),
    }


@router.get("/subir_lote", response_class=HTMLResponse)
async def subir_lote_form(
    request: Request,
    services: ServicesDep,
    current_user: CurrentUser,
    batch_id: Annotated[str | None, Query()] = None,
):
    """Renderiza la vista Jinja del cargue masivo y su seguimiento."""

    return services.templates.TemplateResponse(
        request,
        "subir_lote.html",
        {
            "request": request,
            "user": current_user,
            "batch_id": batch_id or "",
            "dispatcher_name": services.batch_runtime.dispatcher_name,
            **build_shell_context(request=request, user=current_user),
        },
    )


@router.post("/api/lotes")
async def crear_lote(
    file: Annotated[UploadFile, File(...)],
    services: ServicesDep,
    current_user: CurrentUser,
):
    """Crea un lote nuevo y responde 202 para seguimiento por polling."""

    try:
        contents = await file.read()
        result = services.batch_runtime.create_batch_upload.execute(
            filename=file.filename or "",
            contents=contents,
            username=current_user.username,
        )
        return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=result)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"No se pudo crear el lote: {exc}",
        )


@router.post("/api/lotes/manual/historia")
async def crear_lote_manual_historia(
    file: Annotated[UploadFile, File(...)],
    services: ServicesDep,
    current_user: CurrentUser,
):
    """Compatibilidad legacy: redirige la historia manual al flujo individual aislado."""

    try:
        contents = await file.read()
        if hasattr(services, "individual_ingestion_runtime"):
            result = services.individual_ingestion_runtime.create_upload.execute(
                filename=file.filename or "",
                contents=contents,
                username=current_user.username,
                selected_document_type="historia_clinica",
            )
        else:
            result = services.batch_runtime.create_manual_batch_upload.execute(
                filename=file.filename or "",
                contents=contents,
                username=current_user.username,
                ingestion_mode="manual_historia",
                detected_type="historia_clinica",
            )
        return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=result)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"No se pudo encolar la historia clínica: {exc}",
        )


@router.post("/api/lotes/manual/soporte")
async def crear_lote_manual_soporte(
    file: Annotated[UploadFile, File(...)],
    tipo_documento: Annotated[str, Form(...)],
    services: ServicesDep,
    current_user: CurrentUser,
    case_key: Annotated[str | None, Form()] = None,
):
    """Compatibilidad legacy: redirige el soporte manual al flujo individual aislado."""

    try:
        contents = await file.read()
        if hasattr(services, "individual_ingestion_runtime"):
            result = services.individual_ingestion_runtime.create_upload.execute(
                filename=file.filename or "",
                contents=contents,
                username=current_user.username,
                selected_document_type=str(tipo_documento or "").strip(),
                provided_case_key=str(case_key or "").strip(),
            )
        else:
            result = services.batch_runtime.create_manual_batch_upload.execute(
                filename=file.filename or "",
                contents=contents,
                username=current_user.username,
                ingestion_mode="manual_soporte",
                detected_type=str(tipo_documento or "").strip(),
                case_key=str(case_key or "").strip(),
            )
        return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=result)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"No se pudo encolar el soporte: {exc}",
        )


@router.post("/api/lotes/prefactura")
async def crear_lote_prefactura(
    file: Annotated[UploadFile, File(...)],
    services: ServicesDep,
    current_user: CurrentUser,
):
    """Crea un lote desde un PDF compuesto de prefactura y soportes del caso."""

    try:
        contents = await file.read()
        result = services.batch_runtime.create_prefactura_batch_upload.execute(
            filename=file.filename or "",
            contents=contents,
            username=current_user.username,
        )
        return JSONResponse(status_code=status.HTTP_202_ACCEPTED, content=result)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"No se pudo encolar la prefactura: {exc}",
        )


@router.get("/api/lotes")
async def listar_lotes_usuario(
    services: ServicesDep,
    current_user: CurrentUser,
):
    """Devuelve historial resumido de lotes del usuario autenticado."""

    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content={
            "lotes": services.batch_runtime.list_user_batches.execute(
                current_user.username,
                limit=20,
            )
        },
    )


@router.get("/api/lotes/{batch_id}")
async def obtener_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
):
    """Expone el resumen agregado del lote solo para su propietario."""

    batch = _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    batch.pop("usuario", None)
    return batch


@router.post("/api/lotes/{batch_id}/eliminar")
async def eliminar_lote(
    batch_id: Annotated[str, Path(...)],
    payload: DeleteBatchPayload,
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    try:
        result = services.batch_runtime.delete_batch.execute(
            batch_id=batch_id,
            username=current_user.username,
            confirmation_batch_id=payload.confirmation_batch_id,
            confirmation_phrase=payload.confirmation_phrase,
        )
    except BatchDeletionError as exc:
        raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
    return JSONResponse(status_code=status.HTTP_200_OK, content=result)


@router.post("/api/lotes/{batch_id}/cancelar")
async def cancelar_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    try:
        result = services.batch_runtime.cancel_batch_processing.execute(
            batch_id=batch_id,
            username=current_user.username,
        )
    except BatchDeletionError as exc:
        raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
    return JSONResponse(status_code=status.HTTP_200_OK, content=result)


@router.post("/api/lotes/{batch_id}/reintentar")
async def reintentar_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    try:
        result = services.batch_runtime.retry_batch_processing.execute(
            batch_id=batch_id,
            username=current_user.username,
        )
    except BatchDeletionError as exc:
        raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc
    return JSONResponse(status_code=status.HTTP_200_OK, content=result)


@router.get("/api/lotes/{batch_id}/documentos")
async def obtener_documentos_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    """Lista el detalle por archivo del lote autenticado."""

    _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content={
            "batch_id": batch_id,
            "documentos": _project_batch_payload_list(
                services=services,
                username=current_user.username,
                items=services.batch_runtime.list_batch_files.execute(batch_id),
            ),
        },
    )


@router.get("/api/lotes/{batch_id}/pendientes")
async def obtener_pendientes_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    """Lista archivos ambiguos pendientes de validación manual."""

    _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content={
            "batch_id": batch_id,
            "pendientes": _project_batch_payload_list(
                services=services,
                username=current_user.username,
                items=services.batch_runtime.list_pending_associations.execute(batch_id),
            ),
        },
    )


@router.get("/api/lotes/{batch_id}/casos")
async def obtener_casos_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    """Expone casos consolidados para apoyar resolución manual."""

    _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content={
            "batch_id": batch_id,
            "casos": _project_batch_payload_list(
                services=services,
                username=current_user.username,
                items=services.batch_runtime.list_batch_cases.execute(batch_id),
            ),
        },
    )


@router.post("/api/lotes/{batch_id}/epicrisis")
async def generar_epicrisis_masivas_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    """Encola la generación de todas las epicrisis elegibles del lote."""

    _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    try:
        preview = services.batch_runtime.queue_batch_epicrisis.execute(
            batch_id,
            job_id="",
            persist=False,
        )
        job_id = _enqueue_batch_epicrisis_generation(
            services=services,
            batch_id=batch_id,
            username=current_user.username,
            case_keys=preview.get("case_keys", []),
        )
        result = services.batch_runtime.queue_batch_epicrisis.execute(
            batch_id,
            job_id=job_id,
            selected_case_keys=preview.get("case_keys", []),
        )
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))
    return JSONResponse(
        status_code=status.HTTP_202_ACCEPTED,
        content={
            "batch_id": batch_id,
            "job_id": result.get("job_id", ""),
            "status": result.get("status", "pendiente"),
            "queued_count": result.get("queued_count", 0),
            "skipped_completed_count": result.get("skipped_completed_count", 0),
            "skipped_inflight_count": result.get("skipped_inflight_count", 0),
            "skipped_not_ready_count": result.get("skipped_not_ready_count", 0),
        },
    )


@router.post("/api/lotes/{batch_id}/excel-epicrisis")
async def generar_excel_epicrisis_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    """Encola la generación del workbook Excel para las epicrisis del lote."""

    _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    try:
        services.batch_runtime.queue_batch_epicrisis_excel.execute(
            batch_id,
            job_id="",
            persist=False,
        )
        job_id = _enqueue_batch_epicrisis_excel_generation(
            services=services,
            batch_id=batch_id,
        )
        result = services.batch_runtime.queue_batch_epicrisis_excel.execute(
            batch_id,
            job_id=job_id,
            persist=True,
        )
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))

    return JSONResponse(
        status_code=status.HTTP_202_ACCEPTED,
        content={
            "batch_id": batch_id,
            "job_id": result.get("job_id", ""),
            "status": result.get("status", "pendiente"),
            "eligible_count": result.get("eligible_count", 0),
            "filename": result.get("filename", ""),
        },
    )


@router.get("/api/lotes/{batch_id}/excel-epicrisis")
async def obtener_excel_epicrisis_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    """Devuelve metadatos del workbook Excel del lote."""

    batch = _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content=_serialize_excel_epicrisis_meta(batch),
    )


@router.get("/api/lotes/{batch_id}/excel-epicrisis/descarga")
async def descargar_excel_epicrisis_lote(
    batch_id: Annotated[str, Path(...)],
    services: ServicesDep,
    current_user: CurrentUser,
) -> FileResponse:
    """Descarga el workbook Excel ya generado para el lote."""

    _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    batch = _get_owned_batch_record(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    if str(batch.get("excel_epicrisis_status") or "") not in {
        "completado",
        "completado_con_errores",
    }:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="El Excel del lote aún no está listo para descarga.",
        )
    report_path = str(batch.get("excel_epicrisis_path") or "").strip()
    if not report_path:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="El Excel del lote aún no está disponible.",
        )
    report_file = FilePath(report_path)
    if not report_file.exists() or not report_file.is_file():
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="No se encontró el archivo Excel del lote.",
        )
    filename = str(batch.get("excel_epicrisis_filename") or report_file.name)
    return FileResponse(
        path=report_file,
        media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        filename=filename,
    )


@router.patch("/api/lotes/{batch_id}/documentos/{file_id}/asociacion")
async def resolver_asociacion_documento(
    batch_id: Annotated[str, Path(...)],
    file_id: Annotated[str, Path(...)],
    payload: ResolveAssociationPayload,
    services: ServicesDep,
    current_user: CurrentUser,
) -> JSONResponse:
    """Resuelve manualmente un documento pendiente y recalcula estado del lote."""

    _get_owned_batch(
        services=services,
        batch_id=batch_id,
        username=current_user.username,
    )
    demo_service = get_demo_identity_service(services)
    try:
        result = services.batch_runtime.resolve_file_association.execute(
            batch_id=batch_id,
            file_id=file_id,
            resolved_by=current_user.username,
            case_key=demo_service.resolve_case_key(
                username=current_user.username,
                visible_case_key=payload.case_key,
            ),
            patient_name=demo_service.resolve_patient_name(
                username=current_user.username,
                visible_patient_name=payload.patient_name,
            ),
            patient_id=demo_service.resolve_patient_id(
                username=current_user.username,
                visible_patient_id=payload.patient_id,
            ),
            procedure_code=payload.procedure_code,
            reason=payload.reason,
        )
        job_id = _enqueue_batch_file_clinical_materialization(
            services=services,
            batch_id=batch_id,
            file_id=file_id,
        )
        batch = services.batch_runtime.get_batch_status.execute(batch_id) or {}
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc))

    batch.pop("usuario", None)
    return JSONResponse(
        status_code=status.HTTP_200_OK,
        content={
            "batch_id": batch_id,
            "file": result,
            "job_id": job_id,
            "batch": batch,
        },
    )
