"""Тесты для parse_catalog_flat() из domrf_catalog.py (issue #1609).

Покрывает:
  - _classify_status_kw — классификация ключевых слов
  - Статус: sold (все морфоварианты: продан/продана/продано/реализована)
  - Статус: reserved (забронирована/забронировано/бронь)
  - Статус: free (в продаже/свободна)
  - Уровень 1: badge-класс CSS → sold берётся из badge, а не из nav "в продаже"
  - Уровень 2: proximity "Статус" → следующий блок
  - Уровень 3: sold/reserved побеждают free, даже если free встретился раньше в blocks
  - Нет статуса → result не содержит ключ "status"
"""

from __future__ import annotations

import pytest

from app.services.scrapers.domrf_catalog import (
    STATUS_FREE,
    STATUS_RESERVED,
    STATUS_SOLD,
    _classify_status_kw,
    parse_catalog_flat,
)

# ── _classify_status_kw ───────────────────────────────────────────────────────


@pytest.mark.parametrize(
    "text,expected",
    [
        # sold — все морфоварианты
        ("продан", STATUS_SOLD),
        ("продана", STATUS_SOLD),
        ("продано", STATUS_SOLD),
        ("Квартира продана", STATUS_SOLD),
        ("ПРОДАНО", STATUS_SOLD),
        ("реализована", STATUS_SOLD),
        ("реализовано", STATUS_SOLD),
        ("sold", STATUS_SOLD),
        # reserved
        ("забронирована", STATUS_RESERVED),
        ("забронировано", STATUS_RESERVED),
        ("бронь", STATUS_RESERVED),
        ("reserved", STATUS_RESERVED),
        # free
        ("в продаже", STATUS_FREE),
        ("свободна", STATUS_FREE),
        ("свободно", STATUS_FREE),
        ("free", STATUS_FREE),
        # нераспознанные
        ("какой-то текст", None),
        ("", None),
    ],
)
def test_classify_status_kw(text: str, expected: str | None) -> None:
    assert _classify_status_kw(text) == expected


# ── Вспомогательные конструкторы HTML-фрагментов ──────────────────────────────


def _html_with_badge(badge_text: str, nav_text: str = "другие квартиры в продаже") -> str:
    """HTML с типичной структурой DOM.РФ: nav-блок + статус-бейдж."""
    return (
        "<!doctype html><html><body>"
        # Навигационный блок — появляется раньше статуса в документе
        f'<nav><a href="/catalog">Все квартиры</a><span>{nav_text}</span></nav>'
        # Статус-бейдж с CSS-классом "status-badge"
        f'<div class="status-badge">{badge_text}</div>'
        "<p>Площадь 45 м²</p>"
        "</body></html>"
    )


def _html_with_label(label: str, value: str, nav_text: str = "") -> str:
    """HTML с лейблом «Статус» и значением в следующем блоке."""
    nav = f"<span>{nav_text}</span>" if nav_text else ""
    return (
        "<!doctype html><html><body>"
        f"{nav}"
        f"<span>{label}</span>"
        f"<span>{value}</span>"
        "</body></html>"
    )


def _html_blocks_only(blocks: list[tuple[str, str]]) -> str:
    """Генерирует HTML из списка (css_class, text) без badge-классов."""
    parts = ["<!doctype html><html><body>"]
    for cls, text in blocks:
        parts.append(f'<div class="{cls}">{text}</div>')
    parts.append("</body></html>")
    return "".join(parts)


# ── Уровень 1: badge CSS-класс ────────────────────────────────────────────────


def test_status_badge_sold_wins_over_nav_free() -> None:
    """Проданная квартира: badge=продана, nav=в продаже → status=sold (#1609 core bug)."""
    html = _html_with_badge("Продана", nav_text="другие квартиры в продаже")
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_SOLD


def test_status_badge_prodano() -> None:
    """Морфовариант 'продано' в badge → sold."""
    html = _html_with_badge("Продано")
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_SOLD


def test_status_badge_reserved() -> None:
    """Забронирована в badge → reserved."""
    html = _html_with_badge("Забронирована")
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_RESERVED


def test_status_badge_free() -> None:
    """В продаже в badge (нет nav-коллизии) → free."""
    html = _html_with_badge("В продаже", nav_text="")
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_FREE


def test_status_badge_realizovana() -> None:
    """'Реализована' (синоним sold) в badge → sold."""
    html = _html_with_badge("Реализована")
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_SOLD


# ── Уровень 2: proximity "Статус" ────────────────────────────────────────────


def test_status_label_proximity_sold() -> None:
    """Блок 'Статус' → следующий блок 'Продана' → sold."""
    html = _html_with_label("Статус", "Продана")
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_SOLD


def test_status_label_proximity_free() -> None:
    """Блок 'Статус' → следующий блок 'В продаже' → free."""
    html = _html_with_label("Статус", "В продаже")
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_FREE


# ── Уровень 3: blocks scan, sold/reserved > free ─────────────────────────────


def test_status_level3_sold_beats_earlier_free() -> None:
    """Без badge-класса и без label: free-блок раньше, sold-блок позже → sold побеждает."""
    # Нет badge-класса: используем нейтральные классы
    html = _html_blocks_only(
        [
            ("nav-link", "другие квартиры в продаже"),  # free — но это нав
            ("info-card", "Квартира продана"),  # sold — специфичнее
        ]
    )
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_SOLD


def test_status_level3_reserved_beats_earlier_free() -> None:
    """Без badge: free раньше, reserved позже → reserved побеждает."""
    html = _html_blocks_only(
        [
            ("nav", "в продаже"),
            ("info", "Забронировано"),
        ]
    )
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_RESERVED


def test_status_level3_free_only() -> None:
    """Без badge: только free-блок (без sold/reserved) → free."""
    html = _html_blocks_only(
        [
            ("info", "В продаже"),
        ]
    )
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_FREE


def test_status_no_keywords() -> None:
    """HTML без статусных слов → ключ 'status' отсутствует в результате."""
    html = "<!doctype html><html><body><p>Площадь 45 м²</p></body></html>"
    result = parse_catalog_flat(html)
    assert "status" not in result


# ── Регрессия: продана (ж.р.) не сбивает free-баг ────────────────────────────


def test_prodana_feminine_classified_as_sold() -> None:
    """'Квартира продана' (ж.р., feminine) → sold, а не free (issue #1609)."""
    # Симулируем реальную страницу: nav содержит "в продаже" раньше
    html = (
        "<!doctype html><html><body>"
        "<header><a>Все квартиры в продаже</a></header>"
        '<div class="flat-status-tag">Квартира продана</div>'
        "</body></html>"
    )
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_SOLD


def test_generic_label_tag_not_treated_as_badge() -> None:
    """Элемент с классом 'label' или 'tag' (без слова 'status') НЕ должен
    активировать Уровень 1 (#1686 follow-up: regex был слишком широк).

    Страница: generic <span class="label"> с текстом 'В продаже' в шапке,
    реальный статус-бейдж с классом 'status-badge' содержит 'Продана'.
    Уровень 1 должен выбрать только блок status-badge, а не label-шапку.
    """
    html = (
        "<!doctype html><html><body>"
        # generic label в шапке — НЕ должен активировать Level-1 badge match
        '<span class="label">В продаже</span>'
        '<span class="tag">Новостройка</span>'
        '<span class="chip">ЕКБ</span>'
        '<span class="badge">Акция</span>'
        # настоящий статус-бейдж — должен быть подхвачен Level-1
        '<div class="status-badge">Продана</div>'
        "</body></html>"
    )
    result = parse_catalog_flat(html)
    # Level-1 должен взять status-badge (Продана → sold),
    # generic label/tag/chip/badge не должны перебить правильный результат
    assert result.get("status") == STATUS_SOLD


def test_full_page_sold_flat_no_false_free() -> None:
    """Реалистичный HTML проданной квартиры: навигация 'в продаже', бейдж 'продано'."""
    html = (
        "<!doctype html><html><head><title>Квартира</title></head><body>"
        "<nav>"
        '  <a href="/catalog">Каталог квартир в продаже</a>'
        '  <span class="breadcrumb">Квартиры в продаже</span>'
        "</nav>"
        '<main class="flat-page">'
        '  <h1 class="flat-title">Квартира 2-комнатная</h1>'
        '  <div class="price-block">4 500 000 ₽</div>'
        '  <div class="status-label">Продано</div>'
        '  <div class="flat-info">Площадь 55 м² Подъезд 2</div>'
        "</main>"
        '<section class="similar">'
        "  <h2>Похожие квартиры в продаже</h2>"
        "</section>"
        "</body></html>"
    )
    result = parse_catalog_flat(html)
    assert result.get("status") == STATUS_SOLD
    # Цена тоже должна распознаться
    assert result.get("price_rub") == 4_500_000
