"""Тесты для query-параметра exclude_dev в GET /parcels/{cad}/snapshot.pdf.

Скрывает конкурентов конкретных застройщиков (dev_name) из блока «Конкуренты»
PDF-снимка. Client-кейс: убрать оффер девелопера X из отчёта для девелопера Y.

Юнит-тесты — над чистой функцией `_filter_competitors_by_dev`.
Плюс 1 endpoint smoke (mock DB + patched WeasyPrint-рендер).
"""

from __future__ import annotations

from typing import Any
from unittest.mock import MagicMock

from fastapi.testclient import TestClient

from app.api.v1.parcels import _filter_competitors_by_dev
from app.main import app


def _rows() -> list[dict[str, Any]]:
    return [
        {"dev_name": "Практика", "comm_name": "ЖК-А", "flat_count": 100, "distance_m": 200.0},
        {"dev_name": "Брусника", "comm_name": "ЖК-Б", "flat_count": 200, "distance_m": 400.0},
        {"dev_name": "Атлас", "comm_name": "ЖК-В", "flat_count": 50, "distance_m": 800.0},
        {"dev_name": None, "comm_name": "ЖК-Г", "flat_count": 30, "distance_m": 900.0},
    ]


# ── Unit: _filter_competitors_by_dev ──────────────────────────────────────────


def test_filter_none_is_noop() -> None:
    """exclude_dev=None → список идентичен исходному (zero behavior change)."""
    rows = _rows()
    assert _filter_competitors_by_dev(rows, None) == rows


def test_filter_empty_string_is_noop() -> None:
    """Пустая строка / только запятые → ничего не исключаем."""
    rows = _rows()
    assert _filter_competitors_by_dev(rows, "") == rows
    assert _filter_competitors_by_dev(rows, " , ,") == rows


def test_filter_single_dev() -> None:
    """exclude_dev=Практика → строки этого застройщика исключены, остальные есть."""
    kept = _filter_competitors_by_dev(_rows(), "Практика")
    names = [r["dev_name"] for r in kept]
    assert "Практика" not in names
    assert "Брусника" in names
    assert "Атлас" in names


def test_filter_case_insensitive() -> None:
    """Регистронезависимо: 'практика' / 'ПРАКТИКА' исключают 'Практика'."""
    for variant in ("практика", "ПРАКТИКА", "ПрАкТиКа"):
        kept = _filter_competitors_by_dev(_rows(), variant)
        assert "Практика" not in [r["dev_name"] for r in kept]


def test_filter_multi_dev() -> None:
    """exclude_dev=Практика,Брусника → обе исключены, прочие остаются."""
    kept = _filter_competitors_by_dev(_rows(), "Практика,Брусника")
    names = [r["dev_name"] for r in kept]
    assert "Практика" not in names
    assert "Брусника" not in names
    assert "Атлас" in names
    assert None in names  # dev_name=None не исключается


def test_filter_none_dev_name_not_excluded() -> None:
    """Строка с dev_name=None не падает и не исключается даже при фильтре."""
    kept = _filter_competitors_by_dev(_rows(), "Практика")
    assert any(r["dev_name"] is None for r in kept)


def test_filter_whitespace_in_csv() -> None:
    """Пробелы вокруг имён в CSV тримятся."""
    kept = _filter_competitors_by_dev(_rows(), " Практика , Брусника ")
    names = [r["dev_name"] for r in kept]
    assert "Практика" not in names
    assert "Брусника" not in names


# ── Endpoint smoke: mock DB + patched render ──────────────────────────────────


def _parcel_row() -> MagicMock:
    r = MagicMock()
    r.__getitem__ = lambda self, k: {
        "address": "ул. Тестовая, 1",
        "area_m2": 5000.0,
        "land_category": "земли населённых пунктов",
        "vri": "многоэтажная застройка",
        "cadastral_cost": 10_000_000.0,
        "last_update": None,
    }[k]
    return r


def _geom_row() -> MagicMock:
    r = MagicMock()
    r.__getitem__ = lambda self, k: {"wkt": "POINT(60.605 56.838)"}[k]
    return r


def _comp_mapping(dev_name: str | None, obj_id: int) -> dict[str, Any]:
    return {
        "obj_id": obj_id,
        "comm_name": f"ЖК-{obj_id}",
        "dev_name": dev_name,
        "obj_class": "comfort",
        "flat_count": 100,
        "distance_m": 100.0 * obj_id,
    }


def _make_snapshot_db() -> MagicMock:
    """Порядок execute в parcel_snapshot_pdf:
    1. parcel_row → .mappings().first()
    2. geom       → .mappings().first()
    3. poi        → .mappings().all()
    4. competitors→ .mappings().all()
    5. district   → .mappings().first()
    """
    db = MagicMock()

    res_parcel = MagicMock()
    res_parcel.mappings.return_value.first.return_value = _parcel_row()

    res_geom = MagicMock()
    res_geom.mappings.return_value.first.return_value = _geom_row()

    res_poi = MagicMock()
    res_poi.mappings.return_value.all.return_value = []

    res_comp = MagicMock()
    res_comp.mappings.return_value.all.return_value = [
        _comp_mapping("Практика", 1),
        _comp_mapping("Брусника", 2),
    ]

    res_district = MagicMock()
    res_district.mappings.return_value.first.return_value = None

    db.execute.side_effect = [res_parcel, res_geom, res_poi, res_comp, res_district]
    return db


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

    return _get_db_override


def test_snapshot_endpoint_exclude_dev_filters_render_input(monkeypatch) -> None:
    """С exclude_dev=Практика конкурент с dev_name='Практика' не доходит до рендера."""
    captured: dict[str, Any] = {}

    def _fake_render(**kwargs: Any) -> bytes:
        captured["competitor_rows"] = kwargs.get("competitor_rows")
        return b"%PDF-1.4 fake"

    monkeypatch.setattr("app.api.v1.parcels.generate_snapshot_pdf", _fake_render)

    from app.core.db import get_db

    app.dependency_overrides[get_db] = _override_db(_make_snapshot_db())
    try:
        client = TestClient(app)
        resp = client.get("/api/v1/parcels/66:41:0303161:5/snapshot.pdf?exclude_dev=Практика")
        assert resp.status_code == 200, resp.text
        devs = [r["dev_name"] for r in captured["competitor_rows"]]
        assert "Практика" not in devs
        assert "Брусника" in devs
    finally:
        app.dependency_overrides.clear()


def test_snapshot_endpoint_no_param_keeps_all(monkeypatch) -> None:
    """Без exclude_dev все конкуренты доходят до рендера (baseline)."""
    captured: dict[str, Any] = {}

    def _fake_render(**kwargs: Any) -> bytes:
        captured["competitor_rows"] = kwargs.get("competitor_rows")
        return b"%PDF-1.4 fake"

    monkeypatch.setattr("app.api.v1.parcels.generate_snapshot_pdf", _fake_render)

    from app.core.db import get_db

    app.dependency_overrides[get_db] = _override_db(_make_snapshot_db())
    try:
        client = TestClient(app)
        resp = client.get("/api/v1/parcels/66:41:0303161:5/snapshot.pdf")
        assert resp.status_code == 200, resp.text
        devs = [r["dev_name"] for r in captured["competitor_rows"]]
        assert "Практика" in devs
        assert "Брусника" in devs
    finally:
        app.dependency_overrides.clear()
