"""ТЭП ППТ/ПМТ-lookup для analyze_parcel (#1136 follow-up #1133, GG-форсайт #1067).

Читает ``ekb_ppt_tep`` (м.138, наполняется ``ekb_ppt_tep_sync`` Celery-воркером #1085) и
отдаёт ТЭП-контент пояснительных записок (zone_balance / tep / phasing) для проектов
планировки/межевания, пересекающих участок.

Связка ТЭП ↔ геометрия (best-effort, как описано в м.138):
    ``ekb_ppt_tep.doc_ref``  ↔  ``planning_projects.source_key`` (текстовое равенство)
                                ИЛИ  ``planning_projects.doc_full_name`` (LIKE).

Прямой FK не ставим: doc_ref пишется парсером по PDF-источнику (slug/seed), а в WFS
source_key — целое и не совпадает форматом. Сопоставление — две дешёвые ветки:
1) ``doc_ref = CAST(source_key AS text)`` (прямое равенство);
2) ``doc_full_name ILIKE '%' || doc_ref || '%'`` (fallback для slug-ов вида
   ppt2018_22823: длинное WFS-описание содержит короткий slug парсера; LIKE-метасимволы
   в doc_ref экранируются ESCAPE '\').

Дедуп: best-effort OR-JOIN может зацепить несколько строк ``ekb_ppt_tep`` на один
``planning_projects`` (точная ветка + ILIKE-fallback). ``DISTINCT ON (source_key)``
держит контракт «одна строка = один ППТ/ПМТ-документ», отдавая приоритет точной ветке.

Граф вызова: build_ird_analyze_block → parcel_ppt_tep → JOIN planning_projects ∩ ekb_ppt_tep.
Graceful: нет таблицы / пустой WKT / БД-ошибка → []. Зеркалит стиль ``planning_lookup.py``.
"""

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__)

# ТЭП-контент для проектов планировки, пересекающих участок.
# Join: planning_projects (геометрия) ⋈ ekb_ppt_tep (контент) по doc_ref↔source_key/doc_full_name.
# ST_Intersects через GIST idx_planning_projects_geom (4326 ↔ WKT 4326).
# CAST psycopg v3 — никогда :param::type (vault Pattern_CAST_AS_Type).
_PPT_TEP_OVERLAP_SQL = text(
    r"""
    SELECT
        project_type,
        doc_status_name,
        full_name,
        doc_full_name,
        source_key,
        doc_ref,
        zone_balance,
        tep,
        phasing,
        source_pdf,
        fetched_at
    FROM (
        -- DISTINCT ON (source_key): один ППТ/ПМТ-документ = одна строка (контракт docstring).
        -- Точная ветка (doc_ref = source_key) приоритетна над slug-fallback (ILIKE).
        SELECT DISTINCT ON (pp.source_key)
            pp.project_type,
            pp.doc_status_name,
            pp.full_name,
            pp.doc_full_name,
            pp.source_key,
            pp.dmd_actual_year,
            t.doc_ref,
            t.zone_balance,
            t.tep,
            t.phasing,
            t.source_pdf,
            t.fetched_at
        FROM planning_projects pp
        JOIN ekb_ppt_tep t ON (
            t.doc_ref = CAST(pp.source_key AS text)
            OR (
                pp.doc_full_name IS NOT NULL
                AND pp.doc_full_name ILIKE
                    '%' || replace(replace(replace(t.doc_ref, '\', '\\'), '%', '\%'), '_', '\_')
                    || '%' ESCAPE '\'
            )
        )
        WHERE ST_Intersects(pp.geom, ST_GeomFromText(CAST(:parcel_wkt AS text), 4326))
        ORDER BY pp.source_key, (t.doc_ref = CAST(pp.source_key AS text)) DESC
    ) deduped
    ORDER BY dmd_actual_year DESC NULLS LAST, project_type
    """
)


def parcel_ppt_tep(db: Session, parcel_wkt: str | None) -> list[dict[str, Any]]:
    """ТЭП-контент ППТ/ПМТ для участка (Табл.11/12/13 пояснительных записок).

    Args:
        db: SQLAlchemy-сессия.
        parcel_wkt: геометрия участка в WKT (EPSG:4326). None / пусто → [].

    Returns:
        list[{project_type, doc_status_name, full_name, doc_full_name, source_key,
              doc_ref, zone_balance, tep, phasing, source_pdf, fetched_at}].
        Каждая запись — один ППТ/ПМТ-документ, чья геометрия пересекает участок И
        для которого есть ТЭП-контент в ``ekb_ppt_tep``.

        Пустой список при:
          - parcel_wkt пуст / None;
          - таблица ``ekb_ppt_tep`` или ``planning_projects`` ещё не задеплоена;
          - нет пересечений по геометрии;
          - нет матча doc_ref ↔ source_key/doc_full_name (best-effort, graceful);
          - БД-ошибка (analyze не падает).
    """
    if not parcel_wkt:
        return []
    try:
        with db.begin_nested():
            rows = db.execute(_PPT_TEP_OVERLAP_SQL, {"parcel_wkt": parcel_wkt}).mappings().all()
    except (OperationalError, ProgrammingError) as exc:
        # Таблица ещё не задеплоена / БД-ошибка — graceful degrade.
        logger.warning("parcel_ppt_tep: ekb_ppt_tep / planning_projects недоступна, skip: %s", exc)
        return []
    except Exception as exc:
        logger.warning("parcel_ppt_tep: неожиданная ошибка, skip: %s", exc)
        return []
    return [dict(r) for r in rows]


__all__ = ["parcel_ppt_tep"]
