"""Entrada de Celery para ejecutar procesamiento batch fuera del proceso web."""

from __future__ import annotations

from datetime import datetime
from pathlib import Path

from celery import Celery, chord, group

from app.batch_processing.infrastructure.mongo_repositories import MongoBatchCaseRepository
from app.config import config
from app.core.services import build_services


celery_app = Celery(
    "epicrisis_batch_processing",
    broker=config.REDIS_URL,
    backend=config.REDIS_URL,
)


def _build_runtime():
    base_dir = Path(__file__).resolve().parents[2]
    services = build_services(base_dir)
    return services.batch_runtime


def _build_services():
    base_dir = Path(__file__).resolve().parents[2]
    return build_services(base_dir)


def run_case_epicrisis_job(username: str, case_key: str, regen: bool = False) -> str:
    services = _build_services()
    runtime = services.batch_runtime
    case_repo = MongoBatchCaseRepository()
    case = case_repo.get_user_case(username, case_key)
    if not case:
        raise ValueError("Caso no encontrado para generar epicrisis.")

    batch_id = case.get("batch_id", "")
    if batch_id:
        case_repo.update_case(
            batch_id,
            case_key,
            {
                "epicrisis_status": "procesando",
                "updated_at": datetime.now(services.colombia_tz).isoformat(),
            },
        )
        runtime.recompute_batch_bulk_epicrisis.execute(batch_id)

    try:
        if not regen:
            cached = services.case_epicrisis_service.get_cached_case_context(username, case_key)
            if cached and isinstance(cached.get("contexto"), dict):
                if batch_id:
                    case_repo.update_case(
                        batch_id,
                        case_key,
                        {
                            "epicrisis_status": "completado",
                            "epicrisis_url": f"/epicrisis?case_key={case_key}",
                            "epicrisis_error": "",
                            "updated_at": datetime.now(services.colombia_tz).isoformat(),
                        },
                    )
                    runtime.recompute_batch_bulk_epicrisis.execute(batch_id)
                return case_key

        services.case_epicrisis_service.cache_case_context(
            username,
            case_key,
            regen=regen,
        )
        if batch_id:
            case_repo.update_case(
                batch_id,
                case_key,
                {
                    "epicrisis_status": "completado",
                    "epicrisis_url": f"/epicrisis?case_key={case_key}",
                    "epicrisis_error": "",
                    "updated_at": datetime.now(services.colombia_tz).isoformat(),
                },
            )
            runtime.recompute_batch_bulk_epicrisis.execute(batch_id)
    except Exception as exc:
        if batch_id:
            case_repo.update_case(
                batch_id,
                case_key,
                {
                    "epicrisis_status": "fallido",
                    "epicrisis_error": str(exc),
                    "updated_at": datetime.now(services.colombia_tz).isoformat(),
                },
            )
            runtime.recompute_batch_bulk_epicrisis.execute(batch_id)
        raise
    return case_key


@celery_app.task(name="batch.prepare_batch_job")
def prepare_batch_job(batch_id: str) -> list[str]:
    """Prepara lote y devuelve IDs de archivos válidos para fan-out."""

    runtime = _build_runtime()
    return runtime.prepare_batch.execute(batch_id)


@celery_app.task(name="batch.process_batch_file_job")
def process_batch_file_job(batch_id: str, file_id: str) -> str:
    """Procesa un archivo individual del lote."""

    runtime = _build_runtime()
    runtime.process_batch_file.execute(batch_id, file_id)
    return file_id


@celery_app.task(name="batch.finalize_batch_job")
def finalize_batch_job(_results: list[str], batch_id: str) -> list[str]:
    """Consolida asociación y estados del lote tras terminar subtareas."""

    runtime = _build_runtime()
    materialize_ids = runtime.finalize_batch.execute(batch_id)
    fanout_materialize_batch_files_job.delay(materialize_ids, batch_id)
    return materialize_ids


@celery_app.task(name="batch.queue_materialize_batch_file_job")
def queue_materialize_batch_file_job(batch_id: str, file_id: str) -> str:
    runtime = _build_runtime()
    result = materialize_batch_file_job.delay(batch_id, file_id)
    runtime.queue_batch_file_clinical.execute(batch_id, file_id, job_id=result.id)
    return result.id


@celery_app.task(name="batch.materialize_batch_file_job")
def materialize_batch_file_job(batch_id: str, file_id: str) -> str:
    runtime = _build_runtime()
    runtime.materialize_batch_file.execute(batch_id, file_id)
    return file_id


@celery_app.task(name="batch.fanout_materialize_batch_files_job")
def fanout_materialize_batch_files_job(file_ids: list[str], batch_id: str) -> None:
    if not file_ids:
        runtime = _build_runtime()
        runtime.refresh_cases.execute(batch_id)
        return
    for file_id in file_ids:
        queue_materialize_batch_file_job.delay(batch_id, file_id)


@celery_app.task(name="case.generate_epicrisis_job")
def generate_epicrisis_job(username: str, case_key: str, regen: bool = False) -> str:
    return run_case_epicrisis_job(username, case_key, regen)


@celery_app.task(name="batch.generate_all_epicrisis_job")
def generate_all_epicrisis_job(batch_id: str, username: str, case_keys: list[str]) -> dict[str, int | str]:
    runtime = _build_runtime()
    case_repo = MongoBatchCaseRepository()
    services = _build_services()
    normalized_case_keys = [
        str(case_key or "").strip()
        for case_key in case_keys or []
        if str(case_key or "").strip()
    ]
    if not normalized_case_keys:
        runtime.recompute_batch_bulk_epicrisis.execute(batch_id)
        return {"batch_id": batch_id, "queued_count": 0}

    for case_key in normalized_case_keys:
        result = generate_epicrisis_job.delay(username, case_key, False)
        case_repo.update_case(
            batch_id,
            case_key,
            {
                "epicrisis_status": "en_cola",
                "epicrisis_job_id": result.id,
                "epicrisis_error": "",
                "epicrisis_url": f"/epicrisis?case_key={case_key}",
                "updated_at": datetime.now(services.colombia_tz).isoformat(),
            },
        )

    runtime.recompute_batch_bulk_epicrisis.execute(batch_id)
    return {"batch_id": batch_id, "queued_count": len(normalized_case_keys)}


@celery_app.task(name="batch.generate_batch_epicrisis_excel_job")
def generate_batch_epicrisis_excel_job(batch_id: str, job_id: str = "") -> dict[str, int | str]:
    runtime = _build_runtime()
    return runtime.generate_batch_epicrisis_excel.execute(batch_id, job_id=job_id)


@celery_app.task(name="batch.fanout_batch_files_job")
def fanout_batch_files_job(file_ids: list[str], batch_id: str) -> None:
    """Dispara subtareas por archivo y agenda finalización del lote."""

    if not file_ids:
        finalize_batch_job.delay([], batch_id)
        return

    header = group(
        process_batch_file_job.s(batch_id, file_id)
        for file_id in file_ids
    )
    callback = finalize_batch_job.s(batch_id)
    chord(header)(callback)


@celery_app.task(name="batch.process_batch_job")
def process_batch_job(batch_id: str) -> None:
    """Punto de entrada del dispatcher Celery para el lote completo."""

    prepare_batch_job.apply_async(
        args=[batch_id],
        link=fanout_batch_files_job.s(batch_id),
    )
