"""Stage 2a (#1965, эпик #1953) — POST /api/v1/concepts/recompute endpoint tests.

LIVE-пересчёт экономики из агрегированной массинг-программы (пятно × этажность) для
дебаунс-слайдеров Stage 2b. Проверяем: 200 + связный вывод (ТЭП-математика, revenue>0,
NPV посчитан), FAST path с предрезолвленной ценой (БЕЗ обращения к БД) и DB-fallback по
центроиду (через dependency override get_db мок-сессией). compute_financial остаётся
чистой — её арифметика покрыта в tests/services/generative; тут — контракт эндпоинта.
"""

from __future__ import annotations

from typing import Any

from fastapi.testclient import TestClient

from app.core.db import get_db
from app.main import app


def _program(**overrides: Any) -> dict[str, Any]:
    base: dict[str, Any] = {
        "total_footprint_sqm": 3000.0,
        "floors": 12,
        "sections": 2,
        "site_area_sqm": 10_000.0,
        "housing_class": "comfort",
        "development_type": "mid_rise",
    }
    base.update(overrides)
    return base


def test_recompute_returns_200_and_coherent_output() -> None:
    client = TestClient(app)
    resp = client.post(
        "/api/v1/concepts/recompute",
        json=_program(market_price_per_sqm=150_000.0),
    )
    assert resp.status_code == 200, resp.text
    body = resp.json()
    teap = body["teap"]
    fin = body["financial"]
    # ТЭП-математика: GFA = пятно × этажность; пятно = total_footprint_sqm.
    assert teap["built_area_sqm"] == 3000.0
    assert teap["total_floor_area_sqm"] == 3000.0 * 12
    # Финмодель связна: выручка/затраты > 0, NPV посчитан (поле присутствует).
    assert fin["revenue_rub"] > 0
    assert fin["cost_rub"] > 0
    assert isinstance(fin["npv_rub"], float)


def test_recompute_market_price_passthrough_skips_db() -> None:
    # Предрезолвленная цена в теле → используется как есть, БЕЗ обращения к БД.
    # get_db переопределён сессией, которая ВЗРЫВАЕТСЯ при любом execute — если бы
    # эндпоинт пошёл в БД, тест бы упал. Зелёный тест доказывает skip DB lookup.
    class _ExplodingSession:
        def execute(self, *_a: object, **_k: object) -> None:
            raise AssertionError("DB lookup must be skipped when market_price_per_sqm given")

    app.dependency_overrides[get_db] = lambda: _ExplodingSession()
    try:
        client = TestClient(app)
        resp = client.post(
            "/api/v1/concepts/recompute",
            json=_program(market_price_per_sqm=180_000.0),
        )
        assert resp.status_code == 200, resp.text
        fin = resp.json()["financial"]
        # Цена прокинута в выручку жилья → price_per_sqm_used == переданная цена.
        assert fin["price_per_sqm_used"] == 180_000.0
        assert fin["price_is_calibrated"] is True
        # Без body price_source → дефолтный лейбл (фронт не форвардил подлинный source).
        assert fin["price_source"] == "objective_district_median"
    finally:
        app.dependency_overrides.pop(get_db, None)


def test_recompute_body_price_source_echoed_through() -> None:
    # Фронт форвардит ПОДЛИННЫЙ source цены (из financial_estimate, напр. geo_radius) —
    # эндпоинт НЕ подменяет его захардкоженным дефолтом; honest-флаг доходит до UI/PDF.
    # get_db взрывается → доказывает, что и source-passthrough идёт по FAST path без БД.
    class _ExplodingSession:
        def execute(self, *_a: object, **_k: object) -> None:
            raise AssertionError("DB lookup must be skipped when market_price_per_sqm given")

    app.dependency_overrides[get_db] = lambda: _ExplodingSession()
    try:
        client = TestClient(app)
        resp = client.post(
            "/api/v1/concepts/recompute",
            json=_program(market_price_per_sqm=160_000.0, price_source="objective_geo_radius"),
        )
        assert resp.status_code == 200, resp.text
        fin = resp.json()["financial"]
        assert fin["price_per_sqm_used"] == 160_000.0
        assert fin["price_is_calibrated"] is True
        # Подлинный source эхнут наружу, НЕ перетёрт дефолтом "objective_district_median".
        assert fin["price_source"] == "objective_geo_radius"
    finally:
        app.dependency_overrides.pop(get_db, None)


def test_recompute_db_fallback_when_no_price_but_centroid() -> None:
    # Нет market_price_per_sqm, но передан centroid → эндпоинт делает _lookup_market_price.
    # Мок-сессия отдаёт район + Objective-медиану (n>=10) → цена калибруется из БД.
    class _Result:
        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:
        def __init__(self, rows: list[dict[str, Any] | None]) -> None:
            self._rows = rows
            self.calls = 0

        def execute(self, *_a: object, **_k: object) -> _Result:
            row = self._rows[self.calls] if self.calls < len(self._rows) else None
            self.calls += 1
            return _Result(row)

    rows = [
        {"district_name": "Кировский", "median_price_per_m2": 130_000},
        {"median_ppm2": 175_000.0, "sample_size": 42},
    ]
    app.dependency_overrides[get_db] = lambda: _FakeSession(rows)
    try:
        client = TestClient(app)
        resp = client.post(
            "/api/v1/concepts/recompute",
            json=_program(parcel_centroid_wkt="POINT (60.6 56.83)"),
        )
        assert resp.status_code == 200, resp.text
        fin = resp.json()["financial"]
        # Цена пришла из Objective-медианы района (DB-fallback).
        assert fin["price_per_sqm_used"] == 175_000.0
        assert fin["price_source"] == "objective_district_median"
        assert fin["price_is_calibrated"] is True
    finally:
        app.dependency_overrides.pop(get_db, None)


def test_recompute_no_price_no_centroid_uses_class_norm() -> None:
    # Ни цены, ни центроида → норматив класса comfort (145_000), не калибровано.
    client = TestClient(app)
    resp = client.post("/api/v1/concepts/recompute", json=_program())
    assert resp.status_code == 200, resp.text
    fin = resp.json()["financial"]
    assert fin["price_per_sqm_used"] == 145_000.0
    assert fin["price_is_calibrated"] is False
    assert fin["price_source"] == "class_norm"


def test_recompute_validates_floors_bounds() -> None:
    # floors вне [1, 40] → 422 (Pydantic-валидация контракта).
    client = TestClient(app)
    resp = client.post("/api/v1/concepts/recompute", json=_program(floors=99))
    assert resp.status_code == 422
