"""Тесты parcel_okn_objects (#1066, #1159) — ОКН-объекты рядом с участком.

БД не дёргается: фейковая сессия с .execute().fetchall() (позиционные колонки).
Проверяем happy path, graceful на отсутствие WKT/таблицы, и SAVEPOINT-регрессию
(#2464 cluster A): db.execute обёрнут в begin_nested, сбой не отравляет сессию.
"""

from __future__ import annotations

import contextlib
from typing import Any

from sqlalchemy.exc import OperationalError

from app.services.site_finder.okn_lookup import parcel_okn_objects

_WKT = "POLYGON((60.5 56.8, 60.6 56.8, 60.6 56.9, 60.5 56.9, 60.5 56.8))"

_OKN_ROW = (
    "66-001",  # source_id
    "федеральный",  # category
    42,  # distance_m
    "Дом купца Иванова",  # name
    "ул. Ленина, 1",  # address
)


class _FakeDB:
    """Фейковая сессия: execute().fetchall() (позиционные строки) + begin_nested()."""

    def __init__(self, rows: list[tuple[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] | None = None) -> Any:
        if isinstance(self._rows, Exception):
            raise self._rows

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

            def fetchall(self) -> list[tuple[Any, ...]]:
                return self._rows

        return _Result(self._rows)


def test_returns_okn_objects() -> None:
    """Один ОКН-объект в радиусе → проброс полей."""
    db = _FakeDB([_OKN_ROW])
    out = parcel_okn_objects(db, _WKT)
    assert len(out) == 1
    r = out[0]
    assert r["source_id"] == "66-001"
    assert r["category"] == "федеральный"
    assert r["distance_m"] == 42
    assert r["name"] == "Дом купца Иванова"
    assert r["address"] == "ул. Ленина, 1"


def test_empty_wkt_returns_empty() -> None:
    """parcel_wkt=None → [] без вызова DB."""
    db = _FakeDB([_OKN_ROW])
    assert parcel_okn_objects(db, None) == []
    assert db.begin_nested_calls == 0


def test_no_objects_in_radius_returns_empty() -> None:
    """Нет объектов в радиусе → []."""
    assert parcel_okn_objects(_FakeDB([]), _WKT) == []


def test_missing_table_is_graceful() -> None:
    """okn_objects ещё не задеплоена (OperationalError) → [] (analyze не падает)."""
    db = _FakeDB(OperationalError("stmt", {}, Exception("relation okn_objects does not exist")))
    assert parcel_okn_objects(db, _WKT) == []


def test_unexpected_exception_is_graceful() -> None:
    """Любая неожиданная ошибка БД → [] (analyze не падает)."""
    db = _FakeDB(RuntimeError("connection refused"))
    assert parcel_okn_objects(db, _WKT) == []


# ── #2464 cluster A: SAVEPOINT-регрессия (session-poisoning) ────────────────────


def test_uses_savepoint_and_session_reusable_after_failure() -> None:
    """db.execute обёрнут в begin_nested (SAVEPOINT) — сбой не отравляет сессию:
    follow-up вызов на ТОЙ ЖЕ db-сессии успевает (не "current transaction is aborted")."""
    db = _FakeDB(RuntimeError("connection refused"))
    result = parcel_okn_objects(db, _WKT)
    assert result == []
    assert db.begin_nested_calls == 1

    db._rows = [_OKN_ROW]
    result2 = parcel_okn_objects(db, _WKT)
    assert len(result2) == 1
    assert result2[0]["source_id"] == "66-001"
    assert db.begin_nested_calls == 2
