"""PR-2 (эпик #1881) — unit tests for the market-price lookup in the concept API.

``_lookup_market_price`` is the only DB-touching piece of the price-calibration
chain (``compute_financial`` itself stays pure). We mock the SQLAlchemy session so
the tests assert the SELECTION LOGIC — objective median vs district reference vs
class norm — and the graceful-degradation contract (never raise), without a live DB.
"""

from __future__ import annotations

from typing import Any

from app.api.v1 import concepts


class _Result:
    """Мини-заглушка результата execute(): .mappings().first() → заданный row."""

    def __init__(self, row: dict[str, Any] | None) -> None:
        self._row = row

    def mappings(self) -> _Result:
        return self

    def first(self) -> dict[str, Any] | None:
        return self._row


class _FakeSession:
    """Сессия, отдающая заранее заданные строки в порядке вызовов execute().

    ``rows`` — список (row-dict | None | Exception). Exception → execute поднимает её
    (моделируем сбой конкретного запроса). Порядок: [district_lookup, objective_median].
    """

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

    def execute(self, *_args: object, **_kwargs: object) -> _Result:
        item = self._rows[self.calls] if self.calls < len(self._rows) else None
        self.calls += 1
        if isinstance(item, Exception):
            raise item
        return _Result(item)


_WKT = "POINT (60.6 56.83)"


def test_objective_median_sql_dedups_inline_not_via_view() -> None:
    """#1964: _OBJECTIVE_MEDIAN_SQL дедупит INLINE (DISTINCT ON physflat-ключ), НЕ
    через v_objective_lots_latest.

    Это REQUEST-PATH запрос (внутри analyze/концепт-генерации). View не несёт
    district-фильтр внутри → qual не проталкивается ниже DISTINCT ON → view сортирует
    ВСЮ таблицу 1.76M (~5.8 s, прод-EXPLAIN deep-review #1964). Inline DISTINCT ON с
    district В CTE → bitmap по району + sort (~1.7 s). Регресс к
    `FROM v_objective_lots_latest` (whole-table materialize) → fail.
    """
    sql = str(concepts._OBJECTIVE_MEDIAN_SQL)
    assert "DISTINCT ON (" in sql, "должен дедупить физлоты inline"
    assert "snapshot_date DESC" in sql, "берём последний снапшот физлота"
    assert (
        "v_objective_lots_latest" not in sql
    ), "request-path: view материализует всю таблицу — нужен inline DISTINCT ON (#1964)"


def test_objective_median_selected_when_sample_large_enough() -> None:
    # Район найден + Objective n>=10 → выбирается медиана Objective.
    db = _FakeSession(
        [
            {"district_name": "Кировский", "median_price_per_m2": 130_000},
            {"median_ppm2": 175_000.0, "sample_size": 42},
        ]
    )
    price, source = concepts._lookup_market_price(db, _WKT)  # type: ignore[arg-type]
    assert price == 175_000.0
    assert source == "objective_district_median"


def test_district_reference_when_objective_sample_too_small() -> None:
    # Район найден, но Objective n<10 → справочная медиана района.
    db = _FakeSession(
        [
            {"district_name": "Кировский", "median_price_per_m2": 130_000},
            {"median_ppm2": 175_000.0, "sample_size": 4},
        ]
    )
    price, source = concepts._lookup_market_price(db, _WKT)  # type: ignore[arg-type]
    assert price == 130_000.0
    assert source == "district_reference"


def test_district_reference_when_no_objective_rows() -> None:
    # Район найден, Objective вернул пусто (n=0/None) → справочная медиана.
    db = _FakeSession(
        [
            {"district_name": "Кировский", "median_price_per_m2": 128_500},
            {"median_ppm2": None, "sample_size": 0},
        ]
    )
    price, source = concepts._lookup_market_price(db, _WKT)  # type: ignore[arg-type]
    assert price == 128_500.0
    assert source == "district_reference"


def test_class_norm_when_district_has_no_reference_and_no_objective() -> None:
    # Район найден, но ни Objective (n<порог), ни справочной цены нет → class_norm.
    db = _FakeSession(
        [
            {"district_name": "Кировский", "median_price_per_m2": None},
            {"median_ppm2": None, "sample_size": 2},
        ]
    )
    price, source = concepts._lookup_market_price(db, _WKT)  # type: ignore[arg-type]
    assert price is None
    assert source == "class_norm"


def test_class_norm_when_parcel_outside_ekb() -> None:
    # Центроид вне 8 полигонов ЕКБ (район не найден) → class_norm, без обращения к Objective.
    db = _FakeSession([None])
    price, source = concepts._lookup_market_price(db, _WKT)  # type: ignore[arg-type]
    assert price is None
    assert source == "class_norm"


def test_class_norm_when_district_query_raises() -> None:
    # SQL-ошибка на district-lookup (нет PostGIS/таблицы) → graceful class_norm, без краха.
    db = _FakeSession([RuntimeError("relation ekb_districts does not exist")])
    price, source = concepts._lookup_market_price(db, _WKT)  # type: ignore[arg-type]
    assert price is None
    assert source == "class_norm"


def test_district_reference_when_objective_query_raises() -> None:
    # District найден, но objective-median падает → откат на справочную цену района.
    db = _FakeSession(
        [
            {"district_name": "Кировский", "median_price_per_m2": 140_000},
            RuntimeError("objective_lots query failed"),
        ]
    )
    price, source = concepts._lookup_market_price(db, _WKT)  # type: ignore[arg-type]
    assert price == 140_000.0
    assert source == "district_reference"
