"""Celery application factory.

The broker and result backend are both Redis. We deliberately don't use
Postgres as the result backend — Celery's pg result-backend creates noisy
schema artefacts and we already store the structured result on the `jobs`
table.

Tasks register themselves by calling `celery_app.task` in `tasks.py`. Eager
mode (used in tests) is enabled by setting `CELERY_TASK_ALWAYS_EAGER=true`
in the environment.
"""

from __future__ import annotations

import os

from celery import Celery
from celery.signals import worker_ready

from ocr_sprint.config import get_settings
from ocr_sprint.utils.logging import get_logger

_logger = get_logger(__name__)


def build_celery_app() -> Celery:
    settings = get_settings()
    app = Celery(
        "ocr_sprint",
        broker=settings.redis_url,
        backend=settings.redis_url,
        include=["ocr_sprint.worker.tasks"],
    )
    app.conf.update(
        task_default_queue=settings.celery_task_default_queue,
        task_serializer="json",
        result_serializer="json",
        accept_content=["json"],
        timezone="UTC",
        enable_utc=True,
        task_acks_late=True,
        task_reject_on_worker_lost=True,
        worker_prefetch_multiplier=1,  # OCR is CPU-bound; one task at a time.
        broker_connection_retry_on_startup=True,
        result_expires=24 * 3600,  # results live in DB; redis is just a cache
    )
    if os.getenv("CELERY_TASK_ALWAYS_EAGER", "").lower() in {"1", "true", "yes"}:
        app.conf.task_always_eager = True
        app.conf.task_eager_propagates = True
    return app


celery_app = build_celery_app()


@worker_ready.connect
def preload_ocr_models(sender: object, **kwargs: object) -> None:
    """Warm up PaddleOCR and PP-Structure when the worker process is ready.

    With ``--pool=solo`` the worker runs tasks in the *same* process that
    receives this signal, so models loaded here are reused for every
    subsequent task — no fork overhead, no duplicate model loading, and
    RAM usage stays bounded (~1.5 GB instead of 1.5 GB × n_forks).
    """
    from ocr_sprint.config import get_settings as _gs
    from ocr_sprint.pipeline import ocr as _ocr
    from ocr_sprint.pipeline import table as _table

    _logger.info("celery.worker.warmup.start")
    s = _gs()
    try:
        _ocr.warmup()
    except Exception as exc:
        _logger.warning("celery.worker.paddleocr.warmup.failed", error=str(exc))

    if s.tables_enabled:
        try:
            _table.warmup()
        except Exception as exc:
            _logger.warning("celery.worker.pp_structure.warmup.failed", error=str(exc))

    _logger.info("celery.worker.warmup.done")
