"""Тесты для GET /{cad_num}/connection-points (issue #115).

Использует FastAPI TestClient с mock DB — без реального Postgres.
"""

from __future__ import annotations

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

import pytest
from fastapi.testclient import TestClient

from app.main import app

# ── Helpers ───────────────────────────────────────────────────────────────────

_VALID_CAD = "66:41:0204016:10"
_QUARTER = "66:41:0204016"


def _mock_get_db(db: Any):
    """FastAPI dependency override factory."""

    def _get_db_override():
        yield db

    return _get_db_override


def _make_structure(distance_m: float = 120.5) -> dict[str, Any]:
    return {
        "name": "ТП-101",
        "type": "Трансформаторная подстанция",
        "cad_num": None,
        "distance_to_boundary_m": distance_m,
        "geometry_geojson": {"type": "Point", "coordinates": [60.6, 56.8]},
        "readable_address": None,
        "raw_props": {"name": "ТП-101"},
        "source": "nspd_36328",
    }


def _make_zouit_overlap() -> dict[str, Any]:
    return {
        "reg_numb_border": "RN-001",
        "type_zone": "Охранная зона ЛЭП",
        "subcategory": 5,
        "intersects_parcel": True,
        "geometry_geojson": {"type": "Polygon", "coordinates": [[]]},
        "raw_props": {"type_zone": "Охранная зона ЛЭП"},
        "source": "nspd_37578",
    }


def _make_summary(
    nearest: float | None = 120.5,
    in_zone: bool = False,
    zones_count: int = 0,
    total: int = 1,
) -> dict[str, Any]:
    return {
        "nearest_structure_distance_m": nearest,
        "in_protection_zone": in_zone,
        "protection_zones_intersecting": zones_count,
        "total_structures_in_radius": total,
    }


def _make_full_response(
    structures: list[dict[str, Any]] | None = None,
    overlaps: list[dict[str, Any]] | None = None,
    summary: dict[str, Any] | None = None,
    dump_available: bool = True,
    dump_fetched_at: str | None = "2026-05-01T12:00:00+00:00",
) -> dict[str, Any]:
    if structures is None:
        structures = [_make_structure()]
    if overlaps is None:
        overlaps = []
    if summary is None:
        summary = _make_summary(total=len(structures))
    return {
        "engineering_structures": structures,
        "zouit_engineering_overlaps": overlaps,
        "summary": summary,
        "dump_available": dump_available,
        "dump_fetched_at": dump_fetched_at,
    }


# ── Tests ─────────────────────────────────────────────────────────────────────


def test_connection_points_no_dump_returns_empty() -> None:
    """Квартал без dump → dump_available=false, пустые массивы, 200 OK."""
    from app.core.db import get_db

    db = MagicMock()
    app.dependency_overrides[get_db] = _mock_get_db(db)

    empty_response = {
        "engineering_structures": [],
        "zouit_engineering_overlaps": [],
        "summary": {
            "nearest_structure_distance_m": None,
            "in_protection_zone": False,
            "protection_zones_intersecting": 0,
            "total_structures_in_radius": 0,
        },
        "dump_available": False,
        "dump_fetched_at": None,
    }

    try:
        with patch(
            "app.api.v1.parcels.get_connection_points",
            return_value=empty_response,
        ):
            client = TestClient(app)
            response = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-points")

        assert response.status_code == 200, response.text
        body = response.json()
        assert body["dump_available"] is False
        assert body["engineering_structures"] == []
        assert body["zouit_engineering_overlaps"] == []
        assert body["summary"]["total_structures_in_radius"] == 0
        assert body["summary"]["nearest_structure_distance_m"] is None
    finally:
        app.dependency_overrides.clear()


def test_connection_points_parcel_not_found_404() -> None:
    """cad_num не найден в БД (ValueError из сервиса) → 404."""
    from app.core.db import get_db

    db = MagicMock()
    app.dependency_overrides[get_db] = _mock_get_db(db)

    try:
        with patch(
            "app.api.v1.parcels.get_connection_points",
            side_effect=ValueError("Участок '66:41:9999999:1' не найден в БД"),
        ):
            client = TestClient(app)
            response = client.get("/api/v1/parcels/66:41:9999999:1/connection-points")

        assert response.status_code == 404
        assert "не найден" in response.json()["detail"]
    finally:
        app.dependency_overrides.clear()


def test_connection_points_filters_by_radius() -> None:
    """Фичи за radius_m не попадают в ответ (сервис фильтрует).

    Мокаем сервис: при radius_m=100 возвращаем только близкую структуру,
    при radius_m=500 — обе. Проверяем что endpoint передаёт radius_m в сервис.
    """
    from app.core.db import get_db

    db = MagicMock()
    app.dependency_overrides[get_db] = _mock_get_db(db)

    close_only = _make_full_response(structures=[_make_structure(distance_m=80.0)])
    far_included = _make_full_response(
        structures=[_make_structure(distance_m=80.0), _make_structure(distance_m=450.0)],
        summary=_make_summary(nearest=80.0, total=2),
    )

    try:
        with patch("app.api.v1.parcels.get_connection_points") as mock_svc:
            mock_svc.return_value = close_only
            client = TestClient(app)
            r100 = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-points?radius_m=100")
            assert r100.status_code == 200
            assert r100.json()["summary"]["total_structures_in_radius"] == 1
            # Проверяем что radius_m=100 передан в сервис
            call_kwargs = mock_svc.call_args
            assert call_kwargs[0][2] == 100 or call_kwargs[1].get("radius_m") == 100

            mock_svc.return_value = far_included
            r500 = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-points?radius_m=500")
            assert r500.status_code == 200
            assert r500.json()["summary"]["total_structures_in_radius"] == 2
    finally:
        app.dependency_overrides.clear()


def test_connection_points_zouit_intersects_flag() -> None:
    """zouit_engineering_overlaps содержат intersects_parcel=true и правильные поля."""
    from app.core.db import get_db

    db = MagicMock()
    app.dependency_overrides[get_db] = _mock_get_db(db)

    overlap = _make_zouit_overlap()
    full = _make_full_response(
        overlaps=[overlap],
        summary=_make_summary(in_zone=True, zones_count=1),
    )

    try:
        with patch(
            "app.api.v1.parcels.get_connection_points",
            return_value=full,
        ):
            client = TestClient(app)
            response = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-points")

        assert response.status_code == 200
        body = response.json()
        assert body["summary"]["in_protection_zone"] is True
        assert body["summary"]["protection_zones_intersecting"] == 1
        overlaps = body["zouit_engineering_overlaps"]
        assert len(overlaps) == 1
        assert overlaps[0]["intersects_parcel"] is True
        assert overlaps[0]["reg_numb_border"] == "RN-001"
        assert overlaps[0]["source"] == "nspd_37578"
    finally:
        app.dependency_overrides.clear()


def test_summary_nearest_distance() -> None:
    """summary.nearest_structure_distance_m = расстояние до ближайшей структуры."""
    from app.core.db import get_db

    db = MagicMock()
    app.dependency_overrides[get_db] = _mock_get_db(db)

    s1 = _make_structure(distance_m=42.7)
    s2 = _make_structure(distance_m=180.0)
    full = _make_full_response(
        structures=[s1, s2],
        summary=_make_summary(nearest=42.7, total=2),
    )

    try:
        with patch(
            "app.api.v1.parcels.get_connection_points",
            return_value=full,
        ):
            client = TestClient(app)
            response = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-points")

        assert response.status_code == 200
        body = response.json()
        assert body["summary"]["nearest_structure_distance_m"] == pytest.approx(42.7)
        assert body["summary"]["total_structures_in_radius"] == 2
        assert body["engineering_structures"][0]["distance_to_boundary_m"] == pytest.approx(42.7)
    finally:
        app.dependency_overrides.clear()


def test_connection_points_radius_out_of_range_422() -> None:
    """radius_m=10 (< min 50) → 422 Unprocessable Entity."""
    client = TestClient(app)
    response = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-points?radius_m=10")
    assert response.status_code == 422


def test_connection_points_radius_too_large_422() -> None:
    """radius_m=5000 (> max 2000) → 422 Unprocessable Entity."""
    client = TestClient(app)
    response = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-points?radius_m=5000")
    assert response.status_code == 422
