"""Тесты parcel_ppt_tep (#1136 follow-up #1133) — ТЭП ППТ/ПМТ overlap по участку.

БД не дёргается: фейковая сессия с .execute().mappings().all(). Проверяем graceful на
отсутствие участка/таблицы и проброс строк.
"""

from __future__ import annotations

import contextlib
from typing import Any

from sqlalchemy.exc import ProgrammingError

from app.services.site_finder.ppt_tep_lookup import parcel_ppt_tep


class _Result:
    def __init__(self, rows: list[dict[str, Any]]) -> None:
        self._rows = rows

    def mappings(self) -> _Result:
        return self

    def all(self) -> list[dict[str, Any]]:
        return self._rows


class _DB:
    def __init__(self, rows: list[dict[str, Any]] | Exception) -> None:
        self._rows = rows
        self.begin_nested_calls = 0

    @contextlib.contextmanager
    def begin_nested(self) -> Any:
        """Фейковый SAVEPOINT — не глотает исключение из execute (как реальный)."""
        self.begin_nested_calls += 1
        yield

    def execute(self, sql: Any, params: dict[str, Any]) -> _Result:
        if isinstance(self._rows, Exception):
            raise self._rows
        return _Result(self._rows)


_PPT_TEP_ROW: dict[str, Any] = {
    "project_type": "ppt",
    "doc_status_name": "действующий",
    "full_name": "ПАГЕ № 22823 от 12.10.2018",
    "doc_full_name": "ППТ ул. Челюскинцев",
    "source_key": 22823,
    "doc_ref": "ppt2018_22823",
    "zone_balance": [{"zone_name": "Зона Ж-1", "project_ha_num": 5.0}],
    "tep": [{"indicator": "Площадь территории", "target": "36,16"}],
    "phasing": [{"phase": "I этап", "zone": "Зона Ж-1", "area_ha_num": 5.0}],
    "source_pdf": "https://example.com/ppt22823.pdf",
    "fetched_at": "2026-06-07T11:55:00Z",
}


def test_returns_ppt_tep_overlap() -> None:
    """Один match по геометрии и doc_ref↔source_key → проброс строки."""
    out = parcel_ppt_tep(_DB([_PPT_TEP_ROW]), "POINT(60 56)")  # type: ignore[arg-type]
    assert len(out) == 1
    row = out[0]
    assert row["doc_ref"] == "ppt2018_22823"
    assert row["source_key"] == 22823
    assert row["zone_balance"] == [{"zone_name": "Зона Ж-1", "project_ha_num": 5.0}]
    assert row["tep"][0]["target"] == "36,16"
    assert row["phasing"][0]["zone"] == "Зона Ж-1"


def test_empty_wkt_returns_empty() -> None:
    """parcel_wkt=None → []."""
    assert parcel_ppt_tep(_DB([]), None) == []  # type: ignore[arg-type]


def test_empty_string_wkt_returns_empty() -> None:
    """parcel_wkt='' → []."""
    assert parcel_ppt_tep(_DB([]), "") == []  # type: ignore[arg-type]


def test_no_overlap_returns_empty() -> None:
    """Запрос отработал, но пересечений нет → []."""
    assert parcel_ppt_tep(_DB([]), "POINT(60 56)") == []  # type: ignore[arg-type]


def test_missing_table_is_graceful() -> None:
    """ekb_ppt_tep ещё не задеплоена → ProgrammingError → [] (analyze не падает)."""
    db = _DB(ProgrammingError("stmt", {}, Exception("relation ekb_ppt_tep does not exist")))
    assert parcel_ppt_tep(db, "POINT(60 56)") == []  # type: ignore[arg-type]


def test_unexpected_exception_is_graceful() -> None:
    """Любая неожиданная ошибка БД → [] (analyze не падает)."""
    db = _DB(RuntimeError("connection refused"))
    assert parcel_ppt_tep(db, "POINT(60 56)") == []  # type: ignore[arg-type]


def test_uses_savepoint_and_session_reusable_after_failure() -> None:
    """#2464 cluster A: db.execute обёрнут в begin_nested (SAVEPOINT) — сбой не
    отравляет сессию: follow-up вызов на ТОЙ ЖЕ db-сессии успевает."""
    db = _DB(RuntimeError("connection refused"))
    assert parcel_ppt_tep(db, "POINT(60 56)") == []  # type: ignore[arg-type]
    assert db.begin_nested_calls == 1

    db._rows = [_PPT_TEP_ROW]
    out = parcel_ppt_tep(db, "POINT(60 56)")  # type: ignore[arg-type]
    assert len(out) == 1
    assert db.begin_nested_calls == 2


def test_multiple_rows_preserved() -> None:
    """Если match'ит несколько ППТ — все возвращаются."""
    row2: dict[str, Any] = {
        **_PPT_TEP_ROW,
        "doc_ref": "ppt2020_99999",
        "source_key": 99999,
        "full_name": "ПАГЕ № 99999 от 01.06.2020",
    }
    out = parcel_ppt_tep(_DB([_PPT_TEP_ROW, row2]), "POINT(60 56)")  # type: ignore[arg-type]
    assert len(out) == 2
    assert {r["doc_ref"] for r in out} == {"ppt2018_22823", "ppt2020_99999"}
