"""Тесты парсера реестра КРТ-площадок ЕКБ (#1060).

Все тесты — без живой сети.
"""

from __future__ import annotations

import textwrap

import pytest

from app.services.scrapers.ekb_krt_registry import parse_krt_page

# ── Фикстура: реальный фрагмент блока площадки 2 (Донбасская — Брусника) ────
# Вставлен из krt_page_raw.html (beksburg.rf/дляработы/гиз/крт).
# Охватывает обе цены аукциона, договор, застройщика, 2 постановления ПАГЕ.
_BRUSNIKA_BLOCK = textwrap.dedent(
    """\
    <h2><strong>2022 год</strong></h2>
    <p>&nbsp;<strong>2. КРТ жилой застройки в квартале улицы Донбасской &ndash; \
переулков Черниговского &ndash; Суворовского:</strong></p>
    <p><strong><img class="js-has-preview img-float-left" \
title="2.&Tab;КРТ жилой застройки" src="/media/gallery/fe.jpg" width="100%" /></strong></p>
    <p>Площадь территории: 2,79 га;<br />Градостроительный потенциал: 76 650 кв. м;<br />\
Всего многоквартирных домов: 10;<br />\
3. Издано Постановление Администрации города Екатеринбурга от 10.03.2022 № 621 \
&laquo;О комплексном развитии территории жилой застройки в квартале улицы Донбасской&raquo;;\
<br />\
4. Постановление Администрации города Екатеринбурга от 19.05.2022 № 1334 \
&laquo;О проведении аукциона&raquo;;<br />\
5. Аукцион:<br />Начальная стоимость: 641 018,64 рублей;<br />\
Цена по результатам торгов: 671 018 рублей.<br />\
6. Заключен договор о комплексном развитии территории жилой застройки от 10.08.2022 \
№ 1-2/21.7-14 (застройщик &ndash; ООО &laquo;Брусника. Специализированный застройщик&raquo;).\
<br />\
7. Постановление Администрации города Екатеринбурга от 28.12.2022 № 4051 \
&laquo;Об утверждении проекта планировки территории&raquo;.</p>
    <p><strong>Стадия реализации: строительство</strong></p>
    """
)


class TestParseBrusnikaBlock:
    """Тесты парсинга блока площадки Донбасская (Брусника)."""

    @pytest.fixture(scope="class")
    def site(self):
        sites = parse_krt_page(_BRUSNIKA_BLOCK)
        assert sites, "parse_krt_page вернул пустой список"
        return sites[0]

    def test_krt_kind(self, site):
        assert site.krt_kind == "жилой застройки"

    def test_developer(self, site):
        assert site.developer_name is not None
        assert "Брусника" in site.developer_name

    def test_auction_start_price(self, site):
        """641 018,64 → 641018.64"""
        assert site.auction_start_price == pytest.approx(641018.64, rel=1e-4)

    def test_auction_final_price(self, site):
        """671 018 → 671018.0"""
        assert site.auction_final_price == pytest.approx(671018.0, rel=1e-4)

    def test_contract_number(self, site):
        assert site.contract_number == "1-2/21.7-14"

    def test_contract_date(self, site):
        from datetime import date

        assert site.contract_date == date(2022, 8, 10)

    def test_paga_decisions_at_least_one(self, site):
        assert len(site.paga_decisions) >= 1
        # первое постановление — 10.03.2022 № 621
        dates = [d["date"] for d in site.paga_decisions]
        assert "10.03.2022" in dates

    def test_area_ha(self, site):
        assert site.area_ha == pytest.approx(2.79, rel=1e-3)

    def test_gradpotential(self, site):
        # «76 650 кв. м» → 76650.0
        assert site.gradpotential_sqm == pytest.approx(76650.0, rel=1e-3)

    def test_mkd_count(self, site):
        assert site.mkd_count == 10

    def test_site_name_contains_number(self, site):
        assert "2." in site.site_name
        assert "Донбасской" in site.site_name

    def test_raw_block_nonempty(self, site):
        assert len(site.raw_block) > 50


class TestNumericNormalization:
    """Тесты числовой нормализации (NBSP, запятая как десятичный разделитель)."""

    def test_nbsp_price(self):
        """«641\xa0018,64» → 641018.64"""
        from app.services.scrapers.ekb_krt_registry import _normalize_num

        assert _normalize_num("641\xa0018,64") == pytest.approx(641018.64, rel=1e-4)

    def test_space_price(self):
        """«671 018» → 671018.0"""
        from app.services.scrapers.ekb_krt_registry import _normalize_num

        assert _normalize_num("671 018") == pytest.approx(671018.0)

    def test_plain_decimal(self):
        from app.services.scrapers.ekb_krt_registry import _normalize_num

        assert _normalize_num("3,65") == pytest.approx(3.65)

    def test_invalid_returns_none(self):
        from app.services.scrapers.ekb_krt_registry import _normalize_num

        assert _normalize_num("нет данных") is None


class TestEmptyAndGarbage:
    """Пустой / мусорный HTML → []."""

    def test_empty_string(self):
        assert parse_krt_page("") == []

    def test_whitespace_only(self):
        assert parse_krt_page("   \n\t  ") == []

    def test_irrelevant_html(self):
        assert parse_krt_page("<html><body><p>Привет</p></body></html>") == []

    def test_no_krt_section(self):
        html = "<html><body><h2>Другой раздел</h2><p>Текст</p></body></html>"
        assert parse_krt_page(html) == []


# ── Синтетический блок: площадка без застройщика / договора / цен ─────────────
# Имитирует площадки «в работе» (только geography + технические параметры).
# Не требует локального дампа — всегда исполняется в CI.
_BABUSHKINA_BLOCK = textwrap.dedent(
    """\
    <h2><strong>2024 год</strong></h2>
    <p>&nbsp;<strong>1. КРТ жилой застройки в районе ул. Бабушкина:</strong></p>
    <p>Площадь территории: 5,10 га;<br />\
Градостроительный потенциал: 120 000 кв. м;<br />\
Всего многоквартирных домов: 18;<br />\
3. Издано Постановление Администрации города Екатеринбурга от 15.01.2024 № 42 \
&laquo;О комплексном развитии территории&raquo;.</p>
    <p><strong>Стадия реализации: проектирование</strong></p>
    """
)


class TestParseBabushkinaBlock:
    """Синтетический тест: площадка без застройщика, договора и цен аукциона.

    Не зависит от локального дампа — всегда запускается в CI.
    """

    @pytest.fixture(scope="class")
    def site(self):
        sites = parse_krt_page(_BABUSHKINA_BLOCK)
        assert sites, "parse_krt_page вернул пустой список для синтетического блока"
        return sites[0]

    def test_parses_exactly_one_site(self):
        """Синтетический блок содержит ровно одну площадку."""
        assert len(parse_krt_page(_BABUSHKINA_BLOCK)) == 1

    def test_developer_is_none(self, site):
        """Застройщик не указан → None."""
        assert site.developer_name is None

    def test_contract_number_is_none(self, site):
        """Договор не заключён → None."""
        assert site.contract_number is None

    def test_area_ha_filled(self, site):
        """Площадь территории заполнена."""
        assert site.area_ha == pytest.approx(5.10, rel=1e-3)

    def test_gradpotential_filled(self, site):
        """Градостроительный потенциал заполнен."""
        assert site.gradpotential_sqm == pytest.approx(120000.0, rel=1e-3)

    def test_district_filled(self, site):
        """Район извлечён из названия блока."""
        assert site.district is not None
        assert "Бабушкина" in site.district

    def test_krt_kind(self, site):
        assert site.krt_kind == "жилой застройки"


class TestMultiSiteCountFromDump:
    """Проверка количества площадок из реального дампа страницы."""

    @pytest.fixture(scope="class")
    def dump_html(self):
        """Загружает локальный дамп страницы.

        Тест пропускается если файл отсутствует (CI без дампа).
        """
        import os

        dump_path = os.path.join(
            os.path.dirname(__file__),
            "../../../../.playwright-mcp/ekb-docs/krt_page_raw.html",
        )
        if not os.path.exists(dump_path):
            pytest.skip("krt_page_raw.html dump not found — skip real-dump test")
        with open(dump_path, encoding="utf-8") as fh:
            return fh.read()

    def test_site_count_from_dump(self, dump_html):
        """Из дампа должно распарситься ≥ 14 площадок (реальных на странице 15)."""
        sites = parse_krt_page(dump_html)
        assert len(sites) >= 14, f"Expected ≥ 14 sites, got {len(sites)}"

    def test_no_duplicate_site_names(self, dump_html):
        sites = parse_krt_page(dump_html)
        names = [s.site_name for s in sites]
        assert len(names) == len(set(names)), "Дубликаты site_name в результате"
