"""ОКН-lookup для analyze_parcel (#1066, #1159).

Читает ``okn_objects`` (м.141) и отдаёт объекты культурного наследия
в заданном радиусе от участка — [{source_id, category, distance_m, name,
address}].

ST_DWithin по geography (метрическое расстояние в метрах) через GIST-индекс
idx_okn_objects_geom. Участок передаётся как WKT в EPSG:4326.

``name``/``address`` извлекаются из ``raw_props`` (обогащаются воркером через
detail-карточку ЕГРКН). Если detail недоступен — возвращается None для этих
полей, остальные поля сохраняются. Без detail-фикса (#1159 acceptance 2)
analyze-таблица показывала «ОКН рядом» без указания КАКОГО — критично для ИРД.

Graceful: если таблица ``okn_objects`` ещё не задеплоена (pre-migration)
или БД-ошибка — возвращает пустой список (analyze не падает).
"""

from __future__ import annotations

import logging
from typing import Any

from sqlalchemy import text
from sqlalchemy.exc import OperationalError, ProgrammingError
from sqlalchemy.orm import Session

logger = logging.getLogger(__name__)

# Пространственный запрос: ST_DWithin по geography даёт расстояние в метрах.
# CAST psycopg v3 (никогда :param::type — vault Pattern_CAST_AS_Type).
# raw_props->>'name'/'address' — JSON-string accessor (NULL если ключа нет
# или raw_props=NULL). Безопасно для строк без detail-обогащения.
_OKN_DWITHIN_SQL = text("""
    SELECT
        source_id,
        category,
        ROUND(
            ST_Distance(
                geom::geography,
                ST_GeomFromText(CAST(:parcel_wkt AS text), 4326)::geography
            )
        )::integer AS distance_m,
        raw_props->>'name'    AS name,
        raw_props->>'address' AS address
    FROM okn_objects
    WHERE ST_DWithin(
        geom::geography,
        ST_GeomFromText(CAST(:parcel_wkt AS text), 4326)::geography,
        CAST(:radius_m AS double precision)
    )
    ORDER BY distance_m ASC
""")


def parcel_okn_objects(
    db: Session,
    parcel_wkt: str | None,
    radius_m: float = 100.0,
) -> list[dict[str, Any]]:
    """ОКН-объекты в радиусе radius_m от участка.

    Args:
        db: SQLAlchemy-сессия.
        parcel_wkt: геометрия участка в WKT (EPSG:4326). None → пустой список.
        radius_m: радиус поиска в метрах (по умолчанию 100 м).

    Returns:
        Список dict [{source_id, category, distance_m, name, address}],
        отсортированный по расстоянию ASC. ``name``/``address`` могут быть
        None если detail-карточка не догружена. Пустой список при
        отсутствии участка / таблицы / объектов в радиусе (analyze не падает).
    """
    if not parcel_wkt:
        return []
    try:
        with db.begin_nested():
            rows = db.execute(
                _OKN_DWITHIN_SQL,
                {"parcel_wkt": parcel_wkt, "radius_m": radius_m},
            ).fetchall()
    except (OperationalError, ProgrammingError) as exc:
        logger.warning("parcel_okn_objects: okn_objects недоступна, skip: %s", exc)
        return []
    except Exception as exc:
        logger.warning("parcel_okn_objects: ошибка запроса okn_objects: %s", exc)
        return []

    result: list[dict[str, Any]] = []
    for row in rows:
        result.append(
            {
                "source_id": row[0],
                "category": row[1],
                "distance_m": row[2],
                "name": row[3],
                "address": row[4],
            }
        )
    return result


__all__ = ["parcel_okn_objects"]
