"""Тесты harvest_riasurt_sverdl_for_mo / harvest_all_riasurt_sverdl (#108).

Сеть/БД не дёргаются: мокаем NSPDClient + SessionLocal. Проверяем delete-per-MO,
insert-параметры (layer_topic-классификация, mo_name), skip битых фич, прогон по 5 МО.
"""

from __future__ import annotations

from contextlib import contextmanager
from typing import Any

from app.workers.tasks import riasurt_sverdl_harvest


class _Feat:
    def __init__(self, geometry: dict[str, Any] | None, properties: dict[str, Any]) -> None:
        self.geometry = geometry
        self.properties = properties


class _FakeClient:
    """NSPDClient-двойник: bbox → 1 фича на каждый запрошенный слой."""

    def __init__(self, *a: Any, **kw: Any) -> None:
        self.bbox_calls: list[Any] = []

    def get_riasurt_sverdl_in_bbox(
        self, bbox: Any, layers: list[int] | None = None, **kw: Any
    ) -> dict[int, list[_Feat]]:
        self.bbox_calls.append(bbox)
        layer_ids = layers or list(riasurt_sverdl_harvest.RIASURT_SVERDL_LAYERS.keys())
        return {
            lid: [
                _Feat(
                    {"type": "Polygon", "coordinates": [[[1, 1], [2, 1], [2, 2], [1, 1]]]},
                    {"label": f"zone-{lid}", "reg_numb_border": f"66:35-{lid}"},
                )
            ]
            for lid in layer_ids
        }


class _FakeDB:
    def __init__(self) -> None:
        self.executed: list[tuple[Any, dict[str, Any] | None]] = []
        self.commit_count = 0
        self.rollback_count = 0
        self.closed = False

    @contextmanager
    def begin_nested(self) -> Any:
        yield

    def execute(self, sql: Any, params: dict[str, Any] | None = None) -> Any:
        self.executed.append((sql, params))
        return None

    def commit(self) -> None:
        self.commit_count += 1

    def rollback(self) -> None:
        self.rollback_count += 1

    def close(self) -> None:
        self.closed = True


def _patch(monkeypatch: Any, db: _FakeDB) -> _FakeClient:
    client = _FakeClient()
    monkeypatch.setattr(riasurt_sverdl_harvest, "NSPDClient", lambda *a, **kw: client)
    monkeypatch.setattr(riasurt_sverdl_harvest, "SessionLocal", lambda: db)
    return client


def test_harvest_for_mo_deletes_then_inserts(monkeypatch: Any) -> None:
    """1 МО × 2 слоя → delete-MO + 2 insert; commit + close."""
    db = _FakeDB()
    _patch(monkeypatch, db)

    res = riasurt_sverdl_harvest.harvest_riasurt_sverdl_for_mo(
        "Берёзовский",
        (6770000.0, 7710000.0, 6782000.0, 7722000.0),
        [845274, 844478],
    )

    assert res == {"features": 2, "layers": 2}
    assert db.commit_count == 1
    assert db.closed is True
    # 1 delete + 2 insert
    assert len(db.executed) == 3
    delete_params = db.executed[0][1]
    assert delete_params == {"mo_name": "Берёзовский"}


def test_harvest_for_mo_inserts_correct_topic(monkeypatch: Any) -> None:
    """layer_topic денормализуется из riasurt_layer_topic; mo_name проставлен."""
    db = _FakeDB()
    _patch(monkeypatch, db)

    riasurt_sverdl_harvest.harvest_riasurt_sverdl_for_mo(
        "Сысерть", (0.0, 0.0, 100.0, 100.0), [845274, 845425]
    )

    insert_params = [p for _, p in db.executed if p and "layer_topic" in p]
    topics = {p["source_layer_id"]: p["layer_topic"] for p in insert_params}
    assert topics[845274] == "territorial_zone"
    assert topics[845425] == "flood_zone"
    assert all(p["mo_name"] == "Сысерть" for p in insert_params)
    assert all(p["obshnz"] == f"66:35-{p['source_layer_id']}" for p in insert_params)


def test_harvest_for_mo_skips_geomless_feature(monkeypatch: Any) -> None:
    """Фича без geometry не пишется (skip), счётчик не растёт."""
    db = _FakeDB()
    client = _FakeClient()

    def only_empty(bbox: Any, layers: list[int] | None = None, **kw: Any) -> dict[int, list[_Feat]]:
        return {845274: [_Feat(None, {"label": "broken"})]}

    client.get_riasurt_sverdl_in_bbox = only_empty  # type: ignore[method-assign]
    monkeypatch.setattr(riasurt_sverdl_harvest, "NSPDClient", lambda *a, **kw: client)
    monkeypatch.setattr(riasurt_sverdl_harvest, "SessionLocal", lambda: db)

    res = riasurt_sverdl_harvest.harvest_riasurt_sverdl_for_mo("Арамиль", (0.0, 0.0, 10.0, 10.0))
    assert res["features"] == 0
    # только delete-MO, никаких insert
    assert len(db.executed) == 1


def test_harvest_all_iterates_5_mo(monkeypatch: Any) -> None:
    """harvest_all прогоняет все 5 МО агломерации (при включённом флаге)."""
    db = _FakeDB()
    _patch(monkeypatch, db)
    monkeypatch.setattr(
        riasurt_sverdl_harvest.settings, "enable_riasurt_harvest", True, raising=False
    )

    res = riasurt_sverdl_harvest.harvest_all_riasurt_sverdl([845274])
    assert res["mo"] == 5
    # 5 МО × 1 фича каждая
    assert res["features"] == 5


def test_harvest_all_disabled_by_default(monkeypatch: Any) -> None:
    """Гейт #108: при выключенном флаге harvest_all возвращает early без WMS-вызовов."""
    db = _FakeDB()
    client = _patch(monkeypatch, db)
    monkeypatch.setattr(
        riasurt_sverdl_harvest.settings, "enable_riasurt_harvest", False, raising=False
    )

    res = riasurt_sverdl_harvest.harvest_all_riasurt_sverdl([845274])
    assert res == {"mo": 0, "features": 0}
    # ни одного WMS-запроса, ни одной записи в БД
    assert client.bbox_calls == []
    assert db.executed == []


def test_mo_bboxes_has_5_agglomeration_municipalities() -> None:
    """MO_BBOXES содержит ровно 5 МО окраин агломерации."""
    assert set(riasurt_sverdl_harvest.MO_BBOXES) == {
        "Берёзовский",
        "Верхняя Пышма",
        "Среднеуральск",
        "Арамиль",
        "Сысерть",
    }
