"""One-shot backfill: denormalize all existing nspd_quarter_dumps → nspd_parcels/buildings.

Запускается вручную через admin endpoint POST /api/v1/admin/etl/nspd-denorm-backfill
или через Celery CLI:
    celery -A app.workers.celery_app call nspd_denorm.backfill_all_dumps

Идемпотентен: повторный запуск обновляет строки через ON CONFLICT DO UPDATE.
"""

from __future__ import annotations

import logging

from celery import shared_task
from sqlalchemy import text

from app.core.db import SessionLocal
from app.services.scrapers.nspd_denorm import denorm_dump

logger = logging.getLogger(__name__)


@shared_task(
    name="nspd_denorm.backfill_all_dumps",
    bind=True,
    soft_time_limit=3600,  # 1 час — много кварталов × ~10ms per parcel
    max_retries=0,  # One-shot, не retry
)
def backfill_all_dumps(self: object, *, limit: int | None = None) -> dict[str, int]:  # type: ignore[misc]
    """Backfill all existing nspd_quarter_dumps → nspd_parcels / nspd_buildings.

    Читает все строки nspd_quarter_dumps (или limit строк), и для каждой вызывает
    denorm_dump. Возвращает агрегированные счётчики по всем кварталам.

    Args:
        limit: если задан — обработать только первые N кварталов (для тестирования).

    Returns:
        dict {
            "parcels": суммарно вставлено/обновлено parcel строк,
            "buildings": суммарно вставлено/обновлено building строк,
            "errors": суммарно пропусков (нет cad_num, DB ошибки),
            "quarters_processed": количество обработанных кварталов,
        }
    """
    totals: dict[str, int] = {
        "parcels": 0,
        "buildings": 0,
        "errors": 0,
        "quarters_processed": 0,
    }

    db = SessionLocal()
    try:
        sql = (
            "SELECT quarter_cad, features_json "
            "FROM nspd_quarter_dumps "
            "WHERE total_features > 0 "
            "ORDER BY quarter_cad"
        )
        if limit is not None:
            # Используем параметр через text() чтобы избежать SQL-injection.
            rows = db.execute(
                text(sql + " LIMIT CAST(:lim AS integer)"), {"lim": int(limit)}
            ).fetchall()
        else:
            rows = db.execute(text(sql)).fetchall()

        logger.info("backfill_all_dumps: starting, quarters=%d limit=%s", len(rows), limit)

        for row in rows:
            quarter_cad: str = row[0]
            features_json = row[1]
            # features_json — уже list[dict] через psycopg v3 JSON decoding.
            features: list[dict] = features_json if isinstance(features_json, list) else []

            try:
                counts = denorm_dump(db, quarter_cad=quarter_cad, features=features)
                totals["parcels"] += counts["parcels"]
                totals["buildings"] += counts["buildings"]
                totals["errors"] += counts["errors"]
                totals["quarters_processed"] += 1
            except Exception as e:
                logger.warning("backfill_all_dumps: quarter=%s failed: %s", quarter_cad, e)
                totals["errors"] += 1
                # Продолжаем с остальными кварталами
                try:
                    db.rollback()
                except Exception:
                    pass

        logger.info("backfill_all_dumps: done totals=%s", totals)
    finally:
        db.close()

    return totals
