"""Celery task: собрать полный PDF-отчёт ПТИЦА в фоне (эпик #2259 PR-D).

Сборка полного PDF `/site-finder/analysis/{cad}` (§1–§7 + карты) занимает секунды-десятки
секунд (WeasyPrint-рендер + OSM-тайлы карт + server-side концепция), поэтому НЕ крутится
inline на синхронном эндпоинте — здесь фоновая on-demand таска, оркестрирующая
:func:`build_full_report` (full_report_pdf.py) и пишущая PDF-файл + метадата-ран.

ПОТОК: `SessionLocal()` → `build_full_report(db, cad)` → метадата-dict (pdf_path/…) →
close. Best-effort: нет analyze-рана → ValueError внутри оркестратора → ловим, warning,
return None (таска не валит воркер — отчётировать нечего). Прочий сбой → logger.exception
+ None (карты/концепция/форсайт сами best-effort внутри оркестратора).

Mirror conventions (forecast.py): `@celery_app.task`, `SessionLocal()` + try/finally close,
`logger` (не print), синхронный `def`. Очередь — дефолтная `celery` (как forecast-таска: у
неё нет `queue=` в celery_app routing → default; воркер слушает `--queues=celery,scrape_kn,geo`).

TIME LIMIT: WeasyPrint-рендер + карты могут занять до ~минуты на тяжёлом участке —
щедрый per-task лимит (time_limit=600 / soft_time_limit=540), как у forecast-таски.

REGISTRATION: добавлено в `include=[...]` celery_app.py (зеркало forecast). Beat НЕТ —
таска enqueue-on-demand (триггер из POST /{cad}/report).
"""

from __future__ import annotations

import logging
from typing import Any

from app.core.db import SessionLocal
from app.services.exporters.full_report_pdf import build_full_report
from app.workers.celery_app import celery_app

logger = logging.getLogger(__name__)


@celery_app.task(
    bind=True,
    name="tasks.full_report.build_full_report",
    time_limit=600,
    soft_time_limit=540,
)
def build_full_report_task(self: Any, cad: str) -> dict[str, Any] | None:
    """Собрать полный PDF-отчёт участка в фоне и записать файл + метадата-ран.

    Оркестрация целиком в :func:`build_full_report` (full_report_pdf.py): analyze-ран
    (нет → ValueError → None), forecast-ран (нет → Part B «нет данных»), connection-capacity
    и концепция best-effort, карты + WeasyPrint-рендер → файл на volume `/app/reports/` +
    метадата-ран `report-pdf-1.0`. Кэш по (analyze_run_id, forecast_run_id): тот же ключ +
    файл на месте → cache-hit, не пере-рендерит.

    Args:
        cad: кадастровый номер участка.

    Returns:
        Метадата отчёта {pdf_path, analyze_run_id, forecast_run_id, generated_at,
        size_bytes} (+ `cache_hit` при кэш-хите), либо None если нет analyze-рана /
        неожиданный сбой (таска failed gracefully, воркер не падает).
    """
    db = SessionLocal()
    try:
        logger.info("build_full_report_task: старт cad=%s", cad)
        result = build_full_report(db, cad)
        logger.info(
            "build_full_report_task: готово cad=%s path=%s cache_hit=%s",
            cad,
            result.get("pdf_path"),
            result.get("cache_hit", False),
        )
        return result
    except ValueError as exc:
        # Нет analyze-рана — отчётировать нечего (graceful, не crash воркера).
        logger.warning("build_full_report_task: нечего собирать cad=%s: %s", cad, exc)
        return None
    except Exception:
        # Неожиданный сбой рендера/записи: traceback + None (карты/концепция/форсайт
        # уже best-effort внутри оркестратора — сюда всплывает только фатальное).
        logger.exception("build_full_report_task failed for cad=%s (returning None)", cad)
        return None
    finally:
        db.close()
