"""Tests for gate_verdict aggregator — pure function, no DB."""

from app.services.site_finder.gate_verdict import (
    compute_gate_verdict,
    is_residential_zone,
)

# ── is_residential_zone unit tests ────────────────────────────────────────────


def test_residential_zone_code_zh2():
    assert is_residential_zone("Ж-2", None) is True


def test_residential_zone_code_zh5():
    assert is_residential_zone("Ж5", None) is True


def test_residential_zone_name_fallback():
    assert is_residential_zone(None, "Жилая зона смешанной застройки") is True


def test_non_residential_zone_code():
    assert is_residential_zone("П-1", "Производственная П-1") is False


def test_non_residential_zone_recreation():
    assert is_residential_zone("Р-2", "Рекреационная Р-2") is False


# ── compute_gate_verdict ──────────────────────────────────────────────────────


def test_no_dump_returns_unknown():
    """Если nspd_dump отсутствует или available=False → unknown + no_data."""
    verdict = compute_gate_verdict(None, None, None, None)
    assert verdict["can_build_mkd"] == "unknown"
    assert verdict["source"] == "no_data"
    assert verdict["verdict_label"] == "Нужна проверка"
    assert any(w["code"] == "NO_NSPD_DUMP" for w in verdict["warnings"])
    assert verdict["checks_performed"] == []


def test_no_dump_available_false():
    verdict = compute_gate_verdict(None, None, None, {"available": False})
    assert verdict["can_build_mkd"] == "unknown"
    assert verdict["source"] == "no_data"


def test_residential_zone_no_overlaps_can_build():
    """Жилая зона + нет ЗОУИТ + есть сети → Можно."""
    nspd_zoning = {"zone_code": "Ж-2", "zone_name": "Жилая зона Ж-2"}
    verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "ТП-5"}], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "Можно"
    assert verdict["blockers"] == []
    assert verdict["source"] == "nspd_dump"


def test_production_zone_blocks():
    """Производственная зона → blocker PZZ_NOT_RESIDENTIAL → Нельзя."""
    nspd_zoning = {"zone_code": "П-1", "zone_name": "Производственная П-1"}
    verdict = compute_gate_verdict(nspd_zoning, [], [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert verdict["verdict_label"] == "Нельзя"
    assert any(b["code"] == "PZZ_NOT_RESIDENTIAL" for b in verdict["blockers"])


def test_zouit_subcategory_17_blocks():
    """ЗОУИТ subcategory=17 (охранная зона ЛЭП) блокирует даже жилую зону."""
    nspd_zoning = {"zone_code": "Ж-3", "zone_name": "Жилая"}
    overlaps = [{"subcategory": 17, "name": "ЛЭП 110кВ", "layer": "37578"}]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert any("ZOUIT_OVERLAP_SUB17" == b["code"] for b in verdict["blockers"])
    assert "ЛЭП 110кВ" in verdict["blockers"][0]["detail"]


def test_zouit_non_blocker_subcategory_is_warning():
    """ЗОУИТ с subcategory != 17 → warning, не blocker."""
    nspd_zoning = {"zone_code": "Ж-2", "zone_name": "Жилая"}
    overlaps = [{"subcategory": 5, "name": "Придорожная полоса", "layer": "12345"}]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [{"name": "ТП-1"}], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "С ограничениями"
    assert any("ZOUIT_SUB5" == w["code"] for w in verdict["warnings"])
    assert verdict["blockers"] == []


def test_no_engineering_warning_not_blocker():
    """NO_ENGINEERING_NEARBY — информационное предупреждение, не блокер и не даунгрейдит вердикт.

    Чистый жилой участок (Ж-зона, нет ЗОУИТ) без сетей в 200м должен оставаться «Можно»
    — отсутствие сетей не является юридическим запретом на МКД (#1617).
    """
    nspd_zoning = {"zone_code": "Ж-2", "zone_name": "Жилая"}
    verdict = compute_gate_verdict(nspd_zoning, [], [], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "Можно"  # не «С ограничениями» (#1617)
    assert any(w["code"] == "NO_ENGINEERING_NEARBY" for w in verdict["warnings"])
    assert verdict["blockers"] == []


def test_no_engineering_plus_real_warning_still_downgrades():
    """NO_ENGINEERING_NEARBY совместно с реальным предупреждением (ЗОУИТ) → «С ограничениями».

    Реальный ЗОУИТ-warning должен продолжать даунгрейдить вердикт;
    NO_ENGINEERING_NEARBY при этом тоже присутствует в warnings[] (#1617).
    """
    nspd_zoning = {"zone_code": "Ж-2", "zone_name": "Жилая"}
    overlaps = [{"subcategory": 5, "name": "Придорожная полоса", "layer": "12345"}]
    # нет инженерных сетей рядом — пустой список
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "С ограничениями"
    assert any(w["code"] == "ZOUIT_SUB5" for w in verdict["warnings"])
    assert any(w["code"] == "NO_ENGINEERING_NEARBY" for w in verdict["warnings"])
    assert verdict["blockers"] == []


def test_no_engineering_real_blocker_still_rejects():
    """Реальный блокер (охранная зона ЛЭП) при отсутствии сетей → «Нельзя» (#1617)."""
    nspd_zoning = {"zone_code": "Ж-3", "zone_name": "Жилая"}
    overlaps = [{"subcategory": 17, "name": "ЛЭП 110кВ", "layer": "37578"}]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert verdict["verdict_label"] == "Нельзя"
    assert any(b["code"] == "ZOUIT_OVERLAP_SUB17" for b in verdict["blockers"])


def test_stale_dump_marks_source_partial():
    """stale=True → source='nspd_dump_partial'."""
    nspd_zoning = {"zone_code": "Ж-1", "zone_name": "Жилая"}
    dump = {"available": True, "stale": True}
    verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "ТП-3"}], dump)
    assert verdict["source"] == "nspd_dump_partial"


def test_checks_performed_list():
    """checks_performed содержит все 3 проверки при наличии данных."""
    nspd_zoning = {"zone_code": "Ж-1", "zone_name": "Жилая"}
    verdict = compute_gate_verdict(nspd_zoning, [], [], {"available": True})
    assert len(verdict["checks_performed"]) == 3
    assert "ПЗЗ зональность" in verdict["checks_performed"]
    assert "ЗОУИТ пересечения" in verdict["checks_performed"]


def test_pzz_unknown_warning_when_no_zoning():
    """Если nspd_zoning=None при наличии dump — PZZ_UNKNOWN warning."""
    verdict = compute_gate_verdict(None, [], [{"name": "ТП-1"}], {"available": True})
    assert any(w["code"] == "PZZ_UNKNOWN" for w in verdict["warnings"])
    assert verdict["can_build_mkd"] is True


# ── cad_zouit fallback path (#232) ────────────────────────────────────────────


def test_cad_zouit_ohranaya_zona_truboprovodov_blocks():
    """cad_zouit 'охранная зона трубопроводов' → сетевое обременение (газ) → Нельзя (#1070).

    После #1070 трубопроводы/газ классифицируются как ZOUIT_NETWORK_OBREMENENIE
    (более специфичный код, чем прежний ZOUIT_CAD_BLOCKER) — поведение «блокирует МКД»
    сохранено.
    """
    nspd_zoning = {"zone_code": "Ж-2", "zone_name": "Жилая"}
    overlaps = [
        {
            "source": "cad_zouit",
            "type_zone": "Охранная зона трубопроводов (газопроводов, нефтепроводов)",
            "layer": "Охранная зона трубопроводов",
            "name": "Газопровод высокого давления",
        }
    ]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert verdict["verdict_label"] == "Нельзя"
    assert any(b["code"] == "ZOUIT_NETWORK_OBREMENENIE" for b in verdict["blockers"])


def test_cad_zouit_elektr_blocks():
    """cad_zouit электросетевая охранная зона → сетевое обременение (электро) → blocker (#1070)."""
    nspd_zoning = {"zone_code": "Ж-3", "zone_name": "Жилая"}
    overlaps = [
        {
            "source": "cad_zouit",
            "type_zone": "Охранная зона электросетевого объекта",
            "layer": "Охранная зона электросетевого объекта",
            "name": "ЛЭП 110 кВ",
        }
    ]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert any(b["code"] == "ZOUIT_NETWORK_OBREMENENIE" for b in verdict["blockers"])


def test_cad_zouit_szz_is_warning_not_blocker():
    """cad_zouit overlap с 'СЗЗ' → ZOUIT_CAD_SZZ warning, не blocker."""
    nspd_zoning = {"zone_code": "Ж-2", "zone_name": "Жилая"}
    overlaps = [
        {
            "source": "cad_zouit",
            "type_zone": "Санитарно-защитная зона предприятия",
            "layer": "Санитарно-защитная зона предприятия",
            "name": "СЗЗ завода",
        }
    ]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [{"name": "ТП-1"}], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "С ограничениями"
    assert any(w["code"] == "ZOUIT_CAD_SZZ" for w in verdict["warnings"])
    assert verdict["blockers"] == []


def test_cad_zouit_other_is_warning():
    """cad_zouit overlap без blocker/СЗЗ keyword → ZOUIT_CAD_OTHER warning."""
    nspd_zoning = {"zone_code": "Ж-2", "zone_name": "Жилая"}
    overlaps = [
        {
            "source": "cad_zouit",
            "type_zone": "Зона публичного сервитута",
            "layer": "Зона публичного сервитута",
            "name": None,
        }
    ]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [{"name": "ТП-1"}], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert any(w["code"] == "ZOUIT_CAD_OTHER" for w in verdict["warnings"])
    assert verdict["blockers"] == []


def test_nspd_subcategory_path_backward_compat():
    """NSPD dump path (source != 'cad_zouit') продолжает работать через subcategory."""
    nspd_zoning = {"zone_code": "Ж-3", "zone_name": "Жилая"}
    overlaps = [
        {
            "source": "nspd-quarter-dump",
            "subcategory": 17,
            "name": "ЛЭП 110кВ",
            "layer": "37578",
        }
    ]
    verdict = compute_gate_verdict(nspd_zoning, overlaps, [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert any(b["code"] == "ZOUIT_OVERLAP_SUB17" for b in verdict["blockers"])


# ── is_residential_zone: raw_props / subcategory branch (#1353) ───────────────


def test_rawprops_subcategory_2_is_residential():
    """raw_props subcategory=2 (Жилые зоны МКД) → True (#1353)."""
    assert is_residential_zone(None, None, {"subcategory": 2}) is True


def test_rawprops_subcategory_3_is_residential():
    """raw_props subcategory=3 (Смешанное использование, МКД допустим по ПЗЗ ЕКБ) → True (#1353)."""
    assert is_residential_zone(None, None, {"subcategory": 3}) is True


def test_rawprops_subcategory_1_not_residential():
    """raw_props subcategory=1 (ИЖС/малоэтажка) → False: МКД не разрешён (#1353)."""
    assert is_residential_zone(None, None, {"subcategory": 1}) is False


def test_rawprops_subcategory_4_not_residential():
    """raw_props subcategory=4 (Общественно-деловые) → False."""
    assert is_residential_zone(None, None, {"subcategory": 4}) is False


def test_rawprops_subcategory_as_string_int_coercion():
    """raw_props subcategory='2' (строка из JSON) → int() coercion → True (#1353)."""
    assert is_residential_zone(None, None, {"subcategory": "2"}) is True


def test_rawprops_subcategory_absent_fallback_to_zone_code():
    """raw_props без subcategory, но zone_code ^Ж → True (tier-1 wins)."""
    assert is_residential_zone("Ж-2", None, {"other_field": "value"}) is True


def test_rawprops_absent_zone_code_zh_still_residential():
    """raw_props=None, zone_code ^Ж → tier-1 detects residential (no crash)."""
    assert is_residential_zone("Жс", None, None) is True


def test_rawprops_absent_no_zone_code_no_name_not_residential():
    """raw_props=None, zone_code=None, zone_name=None → False, no crash (#1353)."""
    assert is_residential_zone(None, None, None) is False


def test_rawprops_empty_dict_no_crash():
    """raw_props={} (пустой) → subcategory=None → переходит на tier-3 fallback, без исключения."""
    assert is_residential_zone(None, None, {}) is False


def test_rawprops_subcategory_invalid_value_no_crash():
    """raw_props subcategory='N/A' (нечисловое) → ValueError поглощается → False."""
    assert is_residential_zone(None, None, {"subcategory": "N/A"}) is False


# ── compute_gate_verdict: nspd_zoning with raw_props (#1353) ─────────────────


def test_verdict_nspd_zoning_rawprops_sub2_can_build():
    """nspd_zoning с raw_props subcategory=2 (без zone_code Ж) → жилая → Можно."""
    nspd_zoning = {
        "zone_code": "66:41-7.14",  # кадастровый номер NSPD quarter-dump (не ^Ж)
        "zone_name": None,
        "raw_props": {"subcategory": 2},
    }
    verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "ТП-5"}], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "Можно"
    assert verdict["blockers"] == []


def test_verdict_nspd_zoning_rawprops_sub3_can_build():
    """nspd_zoning с raw_props subcategory=3 (смешанное, без zone_code Ж) → Можно."""
    nspd_zoning = {
        "zone_code": "66:41-7.21",
        "zone_name": None,
        "raw_props": {"subcategory": 3},
    }
    verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "ТП-1"}], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "Можно"
    assert verdict["blockers"] == []


def test_verdict_nspd_zoning_rawprops_sub1_blocks():
    """nspd_zoning с raw_props subcategory=1 (ИЖС) → PZZ_NOT_RESIDENTIAL → Нельзя (#1353)."""
    nspd_zoning = {
        "zone_code": "66:41-7.05",
        "zone_name": "Малоэтажная застройка",
        "raw_props": {"subcategory": 1},
    }
    verdict = compute_gate_verdict(nspd_zoning, [], [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert verdict["verdict_label"] == "Нельзя"
    assert any(b["code"] == "PZZ_NOT_RESIDENTIAL" for b in verdict["blockers"])


def test_verdict_nspd_zoning_no_rawprops_zh_zone_code_can_build():
    """raw_props отсутствует, zone_code=^Ж → tier-1 → Можно (регрессия: no crash)."""
    nspd_zoning = {"zone_code": "Ж-1", "zone_name": None}
    verdict = compute_gate_verdict(nspd_zoning, [], [{"name": "КНС-1"}], {"available": True})
    assert verdict["can_build_mkd"] is True
    assert verdict["verdict_label"] == "Можно"
    assert verdict["blockers"] == []


def test_verdict_nspd_zoning_no_rawprops_no_zone_blocks():
    """raw_props=None, zone_code=кадастровый (не ^Ж), zone_name=None → PZZ_NOT_RESIDENTIAL."""
    nspd_zoning = {
        "zone_code": "66:41-7.99",
        "zone_name": None,
        "raw_props": None,
    }
    verdict = compute_gate_verdict(nspd_zoning, [], [], {"available": True})
    assert verdict["can_build_mkd"] is False
    assert verdict["verdict_label"] == "Нельзя"
    assert any(b["code"] == "PZZ_NOT_RESIDENTIAL" for b in verdict["blockers"])
