"""Тесты GET /parcels/by-bbox (SF-B1).

Проверяют:
1. Валидный bbox → 200 + корректная структура ответа.
2. Некорректный bbox (min_lat >= max_lat) → 400.
3. user_id передан → status заполняется из mock DB; без user_id → status=null.
4. last_analysis_date заполняется из v_analysis_runs_latest (#994).

Механика моков: get_db переопределяем через app.dependency_overrides (НЕ
unittest.mock.patch — FastAPI Depends держит оригинальную ссылку, patch неэффективен).
latest_run_dates — обычная функция, вызывается напрямую → её патчим mock.patch.
Дефолтный X-Authenticated-User проставляет tests/conftest.py (RBAC-гейт). Очистку
dependency_overrides делает autouse-фикстура в conftest.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any
from unittest.mock import MagicMock, patch

import pytest
from fastapi.testclient import TestClient

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

# RBAC-гейт требует X-Authenticated-User; admin есть в auth/roles.yaml (см. test_rbac).
_AUTH = {"X-Authenticated-User": "admin"}


def _make_db_row(
    cad_num: str = "66:41:0204016:10",
    centroid_lat: float = 56.83,
    centroid_lon: float = 60.64,
    area_m2: float = 1200.0,
    land_category: str | None = "land_residential",
    user_status: str | None = None,
) -> dict[str, Any]:
    return {
        "cad_num": cad_num,
        "centroid_lat": centroid_lat,
        "centroid_lon": centroid_lon,
        "area_m2": area_m2,
        "land_category": land_category,
        "user_status": user_status,
    }


def _build_mock_db(rows: list[dict[str, Any]]) -> MagicMock:
    """Mock Session, возвращающий rows при execute().mappings().all()."""
    mock_db = MagicMock()
    mappings_mock = MagicMock()
    mappings_mock.all.return_value = rows
    execute_result = MagicMock()
    execute_result.mappings.return_value = mappings_mock
    mock_db.execute.return_value = execute_result
    return mock_db


def _override_db(db: MagicMock):
    def _get_db_override():
        yield db

    return _get_db_override


@pytest.fixture()
def client() -> TestClient:
    return TestClient(app)


# ── Test 1: валидный bbox возвращает корректную структуру ──────────────────


def test_by_bbox_valid_returns_structure(client: TestClient) -> None:
    rows = [_make_db_row()]
    app.dependency_overrides[get_db] = _override_db(_build_mock_db(rows))

    # latest_run_dates патчим (mock_db.execute() отдаёт те же rows на любой запрос,
    # без патча latest_run_dates получил бы parcel-rows без created_at). Пустой dict
    # = «участок не анализировался» → last_analysis_date None.
    with patch("app.api.v1.parcels.latest_run_dates", return_value={}):
        resp = client.get(
            "/api/v1/parcels/by-bbox",
            headers=_AUTH,
            params={
                "min_lat": 56.80,
                "min_lon": 60.60,
                "max_lat": 56.90,
                "max_lon": 60.70,
                "limit": 200,
            },
        )

    assert resp.status_code == 200, resp.text
    body = resp.json()
    assert "parcels" in body
    assert "count" in body
    assert "limit" in body
    assert "bbox_area_km2" in body
    assert body["count"] == 1
    assert body["limit"] == 200

    parcel = body["parcels"][0]
    assert parcel["cad_num"] == "66:41:0204016:10"
    assert parcel["centroid_lat"] == pytest.approx(56.83, abs=0.01)
    assert parcel["centroid_lon"] == pytest.approx(60.64, abs=0.01)
    assert parcel["area_m2"] == pytest.approx(1200.0)
    assert parcel["land_category"] == "land_residential"
    assert parcel["status"] is None  # user_id не передан
    assert parcel["last_analysis_date"] is None


# ── Test 2: некорректный bbox → 400 ───────────────────────────────────────


def test_by_bbox_invalid_bbox_returns_400(client: TestClient) -> None:
    resp = client.get(
        "/api/v1/parcels/by-bbox",
        headers=_AUTH,
        params={
            "min_lat": 56.90,  # min >= max → ошибка
            "min_lon": 60.60,
            "max_lat": 56.80,
            "max_lon": 60.70,
        },
    )
    assert resp.status_code == 400
    assert "bbox" in resp.json()["detail"].lower()


# ── Test 3: user_id → status из БД; без user_id → status null ─────────────


def test_by_bbox_status_overlay_with_user_id(client: TestClient) -> None:
    rows = [_make_db_row(user_status="in_work")]
    app.dependency_overrides[get_db] = _override_db(_build_mock_db(rows))

    with patch("app.api.v1.parcels.latest_run_dates", return_value={}):
        resp = client.get(
            "/api/v1/parcels/by-bbox",
            headers=_AUTH,
            params={
                "min_lat": 56.80,
                "min_lon": 60.60,
                "max_lat": 56.90,
                "max_lon": 60.70,
                "user_id": "user-abc-123",
            },
        )

    assert resp.status_code == 200, resp.text
    parcel = resp.json()["parcels"][0]
    assert parcel["status"] == "in_work"


def test_by_bbox_no_user_id_status_is_null(client: TestClient) -> None:
    rows = [_make_db_row(user_status="favorite")]  # DB вернёт статус
    app.dependency_overrides[get_db] = _override_db(_build_mock_db(rows))

    with patch("app.api.v1.parcels.latest_run_dates", return_value={}):
        resp = client.get(
            "/api/v1/parcels/by-bbox",
            headers=_AUTH,
            params={
                "min_lat": 56.80,
                "min_lon": 60.60,
                "max_lat": 56.90,
                "max_lon": 60.70,
                # user_id не передан
            },
        )

    assert resp.status_code == 200, resp.text
    parcel = resp.json()["parcels"][0]
    # Без user_id статус принудительно null (endpoint не раскрывает чужие статусы)
    assert parcel["status"] is None


# ── Test 5: last_analysis_date заполняется из v_analysis_runs_latest (#994) ──


def test_by_bbox_last_analysis_date_from_latest_run(client: TestClient) -> None:
    """#994: участок с раном в v_analysis_runs_latest → last_analysis_date = ISO-дата."""
    rows = [_make_db_row()]
    app.dependency_overrides[get_db] = _override_db(_build_mock_db(rows))
    latest = {"66:41:0204016:10": datetime(2026, 6, 1, 14, 30, 0)}

    with patch("app.api.v1.parcels.latest_run_dates", return_value=latest):
        resp = client.get(
            "/api/v1/parcels/by-bbox",
            headers=_AUTH,
            params={
                "min_lat": 56.80,
                "min_lon": 60.60,
                "max_lat": 56.90,
                "max_lon": 60.70,
            },
        )

    assert resp.status_code == 200, resp.text
    parcel = resp.json()["parcels"][0]
    # created_at → .date().isoformat() (только дата, без времени), str|None per parcel.py:387
    assert parcel["last_analysis_date"] == "2026-06-01"
