"""Celery task: еженедельный harvest КРТ-геометрий из геопортала ЕКБ (#1130).

Слой portal_geo_oks_krt («Здания в границах КРТ», ~48 WFS-фич) — UPSERT в ekb_krt_geometry.
После harvest analyze._krt_at читает из БД (ST_Intersects) вместо живого WFS-вызова.

UPSERT ON CONFLICT (feature_id): повторный запуск идемпотентен — обновляет геометрию/атрибуты.
SAVEPOINT per-row: одна упавшая фича не откатывает всю транзакцию.
"""

from __future__ import annotations

import json
import logging

from sqlalchemy import text

from app.core.db import SessionLocal
from app.services.scrapers.ekb_geoportal_client import EKBGeoportalClient
from app.workers.celery_app import celery_app

logger = logging.getLogger(__name__)

# Bbox ЕКБ-агломерации (EPSG:4326): minlon, minlat, maxlon, maxlat.
# Охватывает весь муниципальный округ Екатеринбург с запасом; все ~48 КРТ-фич попадают.
_EKB_BBOX: tuple[float, float, float, float] = (60.3, 56.6, 61.1, 57.1)

_UPSERT_SQL = text("""
    INSERT INTO ekb_krt_geometry (
        feature_id, num_oks_krt, full_name, status,
        geom, area, raw_props, fetched_at
    )
    VALUES (
        :feature_id,
        CAST(:num_oks_krt AS integer),
        :full_name,
        :status,
        ST_SetSRID(ST_GeomFromGeoJSON(CAST(:geom_json AS text)), 4326),
        CAST(:area AS numeric),
        CAST(:raw_props AS jsonb),
        now()
    )
    ON CONFLICT (feature_id) DO UPDATE SET
        num_oks_krt = EXCLUDED.num_oks_krt,
        full_name   = EXCLUDED.full_name,
        status      = EXCLUDED.status,
        geom        = EXCLUDED.geom,
        area        = EXCLUDED.area,
        raw_props   = EXCLUDED.raw_props,
        fetched_at  = now()
""")


@celery_app.task(name="tasks.krt_geometry_sync.sync_krt_geometry")
def sync_krt_geometry() -> dict[str, int]:
    """Fetch + UPSERT геометрий КРТ-слоя геопортала ЕКБ → ekb_krt_geometry.

    Один bbox-запрос по всей агломерации ЕКБ (~48 фич). ON CONFLICT idempotent.
    Returns: {"krt": N} — число обработанных фич.
    """
    logger.info("krt_geometry_sync: start (bbox=%s)", _EKB_BBOX)

    client = EKBGeoportalClient()
    try:
        features = client.features_in_bbox("krt", _EKB_BBOX)
    except Exception as exc:
        logger.error("krt_geometry_sync: WFS fetch failed: %s", exc)
        raise

    if not features:
        logger.warning(
            "krt_geometry_sync: WFS returned 0 features — слой пуст или геопортал недоступен"
        )
        return {"krt": 0}

    logger.info("krt_geometry_sync: fetched %d features from WFS", len(features))

    upserted = 0
    failed = 0

    db = SessionLocal()
    try:
        for feat in features:
            if feat.geometry is None:
                logger.warning(
                    "krt_geometry_sync: feature_id=%s has no geometry — пропускаем", feat.feature_id
                )
                failed += 1
                continue
            props = feat.properties or {}
            params = {
                "feature_id": feat.feature_id,
                "num_oks_krt": props.get("num_oks_krt"),
                "full_name": props.get("full_name"),
                "status": props.get("status"),
                "geom_json": json.dumps(feat.geometry, ensure_ascii=False),
                "area": props.get("area"),
                "raw_props": json.dumps(props, ensure_ascii=False),
            }
            try:
                with db.begin_nested():
                    db.execute(_UPSERT_SQL, params)
                upserted += 1
            except Exception as exc:
                failed += 1
                logger.warning(
                    "krt_geometry_sync: UPSERT failed for feature_id=%s: %s",
                    feat.feature_id,
                    exc,
                )
        db.commit()
    except Exception:
        db.rollback()
        raise
    finally:
        db.close()

    logger.info(
        "krt_geometry_sync: done — upserted=%d failed=%d",
        upserted,
        failed,
    )
    return {"krt": upserted}
