"""Тесты harvest_planning_projects (#1085) — геопортал-WFS ППТ/ПМТ → planning_projects.

Сеть/БД не дёргаются: мокаем EKBGeoportalClient + SessionLocal, проверяем фетч обоих слоёв,
project_type-маппинг, skip битых фич, нормализацию года и upsert-параметры.
"""

from __future__ import annotations

from contextlib import contextmanager
from typing import Any

from app.workers.tasks import planning_harvest


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


class _FakeClient:
    """EKBGeoportalClient-двойник: layer → 1 фича на слой."""

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

    def features_in_bbox(self, layer: str, bbox: Any) -> list[_Feat]:
        self.bbox_calls.append(layer)
        return [
            _Feat(
                {"type": "Polygon", "coordinates": [[[60, 56], [60.1, 56], [60, 56.1], [60, 56]]]},
                {
                    "key": 1000010173321902 if layer == "ppt" else 1000010209297292,
                    "full_name": f"ПАГЕ № 1 ({layer})",
                    "doc_status": 1,
                    "doc_status_name": "действующий",
                    "project_name": "Тестовый проект",
                    "dmd_actual_year": "2021",
                },
            )
        ]


class _FakeDB:
    def __init__(self) -> None:
        self.executed: list[tuple[Any, dict[str, Any] | None]] = []
        self.committed = False
        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.committed = True

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


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


def test_harvest_both_layers(monkeypatch: Any) -> None:
    """ППТ + ПМТ → upsert по фиче на слой; project_type-маппинг; year нормализован в int."""
    db = _FakeDB()
    client = _patch(monkeypatch, db)

    result = planning_harvest.harvest_planning_projects()

    assert result == {"features": 2}
    assert sorted(client.bbox_calls) == ["pmt", "ppt"]
    assert len(db.executed) == 2
    types = {params["project_type"] for _, params in db.executed if params}
    assert types == {"ppt", "pmt"}
    _, p0 = db.executed[0]
    assert p0 is not None
    assert p0["dmd_actual_year"] == 2021  # строка "2021" → int
    assert p0["doc_status"] == "1"  # приведён к строке
    assert isinstance(p0["geojson"], str)


def test_skips_feature_without_key_or_geom() -> None:
    """Фича без key / geometry → не пишется (defensive)."""
    db = _FakeDB()
    no_key = _Feat({"type": "Polygon", "coordinates": []}, {"full_name": "x"})
    no_geom = _Feat(None, {"key": 5})
    assert (
        planning_harvest._upsert_feature(db, project_type="ppt", feature=no_key, fetched_at="t")
        is False
    )
    assert (
        planning_harvest._upsert_feature(db, project_type="pmt", feature=no_geom, fetched_at="t")
        is False
    )
    assert db.executed == []


def test_year_normalization() -> None:
    assert planning_harvest._to_int("2021") == 2021
    assert planning_harvest._to_int(2021) == 2021
    assert planning_harvest._to_int(None) is None
    assert planning_harvest._to_int("н/д") is None
