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

from __future__ import annotations

import os
from datetime import datetime
from pathlib import Path

from celery import Celery, chord, group
from kombu import Queue

from app.batch_processing.infrastructure.mongo_repositories import MongoBatchCaseRepository
from app.config import config
from app.core.logging import (
    bind_log_context,
    clear_log_context,
    get_log_context,
)
from app.core.services import build_services
from app.individual_ingestion.domain.models import INDIVIDUAL_QUEUE_NAME


BATCH_QUEUE_NAME = os.getenv("BATCH_CELERY_QUEUE", "batch_jobs").strip() or "batch_jobs"
INDIVIDUAL_CELERY_QUEUE_NAME = (
    os.getenv("INDIVIDUAL_CELERY_QUEUE", INDIVIDUAL_QUEUE_NAME).strip() or INDIVIDUAL_QUEUE_NAME
)


celery_app = Celery(
    "epicrisis_batch_processing",
    broker=config.REDIS_URL,
    backend=config.REDIS_URL,
)
celery_app.conf.task_create_missing_queues = True
celery_app.conf.task_default_queue = BATCH_QUEUE_NAME
celery_app.conf.task_queues = (
    Queue(BATCH_QUEUE_NAME),
    Queue(INDIVIDUAL_CELERY_QUEUE_NAME),
)
celery_app.conf.task_routes = {
    "batch.*": {"queue": BATCH_QUEUE_NAME},
    "case.*": {"queue": BATCH_QUEUE_NAME},
    "individual.precheck_upload_job": {"queue": INDIVIDUAL_CELERY_QUEUE_NAME},
    "individual.materialize_upload_job": {"queue": INDIVIDUAL_CELERY_QUEUE_NAME},
}


def _restore_audit_context(audit_context: dict[str, object] | None = None, **extra: object) -> None:
    from app.core.logging import set_log_context

    set_log_context(dict(audit_context or {}))
    bind_log_context(**extra)


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


def _build_individual_runtime():
    base_dir = Path(__file__).resolve().parents[2]
    services = build_services(base_dir)
    return services.individual_ingestion_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,
    *,
    job_id: str = "",
    audit_context: dict[str, object] | None = None,
) -> str:
    services = _build_services()
    return services.case_epicrisis_runtime_service.run_generation_job(
        username,
        case_key,
        regen=regen,
        job_id=job_id,
        audit_context=audit_context,
    )


def run_case_rda_job(
    username: str,
    case_key: str,
    artifact_type: str,
    force: bool = False,
    audit_context: dict[str, object] | None = None,
) -> str:
    _restore_audit_context(
        audit_context,
        username=username,
        case_key=case_key,
        document_type=f"rda:{artifact_type}",
    )
    services = _build_services()
    job_id = str(getattr(generate_rda_job.request, "id", "") or "")
    try:
        services.rda_service.run_generation_job(
            username,
            case_key,
            artifact_type,
            force=force,
            job_id=job_id,
            audit_context=get_log_context(),
        )
    finally:
        clear_log_context()
    return case_key


@celery_app.task(name="batch.prepare_batch_job")
def prepare_batch_job(batch_id: str, audit_context: dict[str, object] | None = None) -> list[str]:
    """Prepara lote y devuelve IDs de archivos válidos para fan-out."""
    _restore_audit_context(audit_context, batch_id=batch_id)
    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, audit_context: dict[str, object] | None = None) -> str:
    """Procesa un archivo individual del lote."""
    _restore_audit_context(audit_context, batch_id=batch_id, file_id=file_id)
    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,
    audit_context: dict[str, object] | None = None,
) -> list[str]:
    """Consolida asociación y estados del lote tras terminar subtareas."""
    _restore_audit_context(audit_context, batch_id=batch_id)
    runtime = _build_runtime()
    materialize_ids = runtime.finalize_batch.execute(batch_id)
    fanout_materialize_batch_files_job.delay(materialize_ids, batch_id, audit_context=get_log_context())
    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,
    audit_context: dict[str, object] | None = None,
) -> str:
    _restore_audit_context(audit_context, batch_id=batch_id, file_id=file_id)
    runtime = _build_runtime()
    result = materialize_batch_file_job.delay(batch_id, file_id, audit_context=get_log_context())
    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,
    audit_context: dict[str, object] | None = None,
) -> str:
    _restore_audit_context(audit_context, batch_id=batch_id, file_id=file_id)
    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,
    audit_context: dict[str, object] | None = None,
) -> None:
    _restore_audit_context(audit_context, batch_id=batch_id)
    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, audit_context=get_log_context())


@celery_app.task(name="case.generate_epicrisis_job")
def generate_epicrisis_job(
    username: str,
    case_key: str,
    regen: bool = False,
    audit_context: dict[str, object] | None = None,
) -> str:
    job_id = str(getattr(generate_epicrisis_job.request, "id", "") or "")
    try:
        return run_case_epicrisis_job(
            username,
            case_key,
            regen,
            job_id=job_id,
            audit_context=audit_context,
        )
    finally:
        clear_log_context()


@celery_app.task(name="case.generate_rda_job")
def generate_rda_job(
    username: str,
    case_key: str,
    artifact_type: str,
    force: bool = False,
    audit_context: dict[str, object] | None = None,
) -> str:
    return run_case_rda_job(
        username,
        case_key,
        artifact_type,
        force,
        audit_context=audit_context,
    )


@celery_app.task(name="batch.generate_all_epicrisis_job")
def generate_all_epicrisis_job(
    batch_id: str,
    username: str,
    case_keys: list[str],
    audit_context: dict[str, object] | None = None,
) -> dict[str, int | str]:
    _restore_audit_context(audit_context, batch_id=batch_id, username=username)
    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, audit_context=get_log_context())
        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 = "",
    audit_context: dict[str, object] | None = None,
) -> dict[str, int | str]:
    _restore_audit_context(audit_context, batch_id=batch_id, job_id=job_id)
    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,
    audit_context: dict[str, object] | None = None,
) -> None:
    """Dispara subtareas por archivo y agenda finalización del lote."""
    _restore_audit_context(audit_context, batch_id=batch_id)
    if not file_ids:
        finalize_batch_job.delay([], batch_id, audit_context=get_log_context())
        return

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


@celery_app.task(name="batch.process_batch_job")
def process_batch_job(batch_id: str, audit_context: dict[str, object] | None = None) -> None:
    """Punto de entrada del dispatcher Celery para el lote completo."""
    _restore_audit_context(audit_context, batch_id=batch_id)
    prepare_batch_job.apply_async(
        args=[batch_id, get_log_context()],
        link=fanout_batch_files_job.s(batch_id, get_log_context()),
    )


@celery_app.task(name="individual.precheck_upload_job")
def precheck_individual_upload_job(
    upload_id: str,
    audit_context: dict[str, object] | None = None,
) -> str:
    _restore_audit_context(audit_context, file_id=upload_id)
    runtime = _build_individual_runtime()
    runtime.run_precheck.execute(upload_id)
    return upload_id


@celery_app.task(name="individual.materialize_upload_job")
def materialize_individual_upload_job(
    upload_id: str,
    audit_context: dict[str, object] | None = None,
) -> str:
    _restore_audit_context(audit_context, file_id=upload_id)
    runtime = _build_individual_runtime()
    runtime.run_materialization.execute(upload_id)
    return upload_id
