"""Golden-parity: `scraper_kit.providers.avito.*` парсинг ≡ `app.services.scrapers.avito*`.

Strangler-инвариант (#2133): новая scraper_kit-копия avito-провайдера должна давать
БАЙТ-ИДЕНТИЧНЫЙ результат парсинга старому боевому коду на одинаковом входе.
Развязка (settings→ScraperConfig, get_scraper_delay→delay_provider,
geocoder→scraper_kit.geo, matcher→HouseMatcher) НЕ меняет распарсенные данные.

Гоняем ОБА модуля на одних фикстурах и сравниваем результат:
  - SERP `_parse_html` (DOM-карточки → list[ScrapedLot]) на avito_serp_sample.html,
    при ekb_only=False и ekb_only=True (фильтр padding-карточек);
  - `_extract_total_count` (счётчик выдачи);
  - `_build_sort_timestamp_map` + `_unix_to_date` (sortTimeStamp → date, MSK);
  - detail `parse_detail_html` (item-view HTML → DetailEnrichment);
  - чистые title/address-хелперы (_clean_address, _extract_rooms/area/floor_from_title).

Идентичность результата = kit-парсер верно скопирован (развязка не задела логику).
"""

from __future__ import annotations

import dataclasses
import os
from pathlib import Path
from types import SimpleNamespace
from typing import Any
from unittest.mock import patch

# Старый app.services.scrapers.avito импортирует app.core.config.settings=Settings(),
# которому нужен DATABASE_URL. Офлайн-парсинг БД не трогает — фиктивный DSN достаточен.
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")

# НОВЫЙ (scraper_kit) и СТАРЫЙ (app.services.scrapers) avito-парсеры — оба в одном
# тесте; ruff сортирует scraper_kit (first-party) выше app-импортов.
from scraper_kit.providers.avito.detail import parse_detail_html as new_parse_detail
from scraper_kit.providers.avito.serp import AvitoScraper as NewAvitoScraper
from scraper_kit.providers.avito.serp import (
    _build_sort_timestamp_map as new_build_ts_map,
)
from scraper_kit.providers.avito.serp import _clean_address as new_clean_address
from scraper_kit.providers.avito.serp import _extract_area_from_title as new_area
from scraper_kit.providers.avito.serp import _extract_floor_from_title as new_floor
from scraper_kit.providers.avito.serp import _extract_rooms_from_title as new_rooms
from scraper_kit.providers.avito.shared import _unix_to_date as new_unix_to_date

from app.services.scrapers.avito import AvitoScraper as OldAvitoScraper
from app.services.scrapers.avito import (
    _build_sort_timestamp_map as old_build_ts_map,
)
from app.services.scrapers.avito import _clean_address as old_clean_address
from app.services.scrapers.avito import _extract_area_from_title as old_area
from app.services.scrapers.avito import _extract_floor_from_title as old_floor
from app.services.scrapers.avito import _extract_rooms_from_title as old_rooms
from app.services.scrapers.avito import _unix_to_date as old_unix_to_date
from app.services.scrapers.avito_detail import parse_detail_html as old_parse_detail

_FIXTURES = Path(__file__).parent / "fixtures"
_SERP_HTML = (_FIXTURES / "avito_serp_sample.html").read_text(encoding="utf-8")

# Минимальный detail item-view HTML (зеркало test_avito_detail_parse.MINIMAL_HTML).
_DETAIL_HTML = """
<html><body>
<div data-marker="item-view/item-id">№ 7986882804 · 18 мая в 13:40 · 1899 просмотров (+ 12 сегодня)</div>
<span itemprop="price" content="11990000">11 990 000 ₽</span>
<div data-marker="item-map-wrapper" data-map-lat="56.797713" data-map-lon="60.609135" data-location-id="654070"></div>
<div data-marker="item-view/item-params">
  <ul>
    <li>Количество комнат: 3</li>
    <li>Общая площадь: 75 м²</li>
    <li>Площадь кухни: 15 м²</li>
    <li>Этаж: 20 из 26</li>
    <li>Балкон или лоджия: лоджия</li>
    <li>Ремонт: евро</li>
  </ul>
  <ul>
    <li>Тип дома: монолитный</li>
    <li>Этажей в доме: 26</li>
  </ul>
</div>
<a data-marker="nd-jk-details-button" href="/catalog/houses/ekaterinburg/ul_postovskogo_17a/3171365">Дом</a>
<div data-marker="item-view/item-description">Отличная квартира в новом ЖК.</div>
</body></html>
"""  # noqa: E501

_SOURCE_URL = "https://www.avito.ru/ekaterinburg/kvartiry/test_7986882804"


def _make_old(ekb_only: bool) -> tuple[OldAvitoScraper, SimpleNamespace]:
    """Старый AvitoScraper без DB: patch get_scraper_delay + settings.avito_serp_ekb_only."""
    with patch("app.services.scrapers.avito.get_scraper_delay", return_value=7.0):
        scraper = OldAvitoScraper()
    # _parse_html читает settings.avito_serp_ekb_only — подменяем на детерминированное.
    scraper_settings = SimpleNamespace(avito_serp_ekb_only=ekb_only)
    return scraper, scraper_settings


def _make_new(ekb_only: bool) -> NewAvitoScraper:
    """Новый AvitoScraper с инжектируемым ScraperConfig (duck-typed namespace)."""
    config = SimpleNamespace(avito_serp_ekb_only=ekb_only)
    return NewAvitoScraper(config=config)


def _dump_lots(lots: list[Any]) -> list[dict[str, Any]]:
    return [lot.model_dump() for lot in lots]


# ── SERP _parse_html parity ──────────────────────────────────────────────────


def _run_old_parse_html(html: str, base: str, ekb_only: bool) -> list[Any]:
    old, old_settings = _make_old(ekb_only)
    with patch("app.services.scrapers.avito.settings", old_settings):
        return old._parse_html(html, base)


def test_serp_parse_html_parity_ekb_only_false() -> None:
    """DOM-карточки → ScrapedLot идентичны (фильтр padding выключен)."""
    base = "https://www.avito.ru/ekaterinburg/kvartiry"
    old_lots = _run_old_parse_html(_SERP_HTML, base, ekb_only=False)
    new_lots = _make_new(ekb_only=False)._parse_html(_SERP_HTML, base)

    assert len(old_lots) == len(new_lots)
    assert len(new_lots) > 0, "фикстура должна содержать хотя бы одну карточку"
    assert _dump_lots(old_lots) == _dump_lots(new_lots)


def test_serp_parse_html_parity_ekb_only_true() -> None:
    """С включённым ekb_only-фильтром padding-дроп идентичен."""
    base = "https://www.avito.ru/ekaterinburg/kvartiry"
    old_lots = _run_old_parse_html(_SERP_HTML, base, ekb_only=True)
    new_lots = _make_new(ekb_only=True)._parse_html(_SERP_HTML, base)

    assert len(old_lots) == len(new_lots)
    assert _dump_lots(old_lots) == _dump_lots(new_lots)


def test_serp_extract_total_count_parity() -> None:
    """_extract_total_count на SERP-фикстуре идентичен."""
    old, _ = _make_old(ekb_only=False)
    new = _make_new(ekb_only=False)
    assert old._extract_total_count(_SERP_HTML) == new._extract_total_count(_SERP_HTML)


def test_build_sort_timestamp_map_parity() -> None:
    """_build_sort_timestamp_map (sortTimeStamp → date, MSK) идентичен."""
    assert old_build_ts_map(_SERP_HTML) == new_build_ts_map(_SERP_HTML)


def test_unix_to_date_parity() -> None:
    """_unix_to_date (MSK) идентичен на репрезентативных epoch-значениях."""
    for ts in [None, 0, 1756077885, 1735689600, 1700000000.0, 1_600_000_000]:
        assert old_unix_to_date(ts) == new_unix_to_date(ts)


# ── detail parse_detail_html parity ──────────────────────────────────────────


def test_parse_detail_html_parity() -> None:
    """parse_detail_html → DetailEnrichment идентичен (все поля)."""
    old_res = old_parse_detail(_DETAIL_HTML, _SOURCE_URL)
    new_res = new_parse_detail(_DETAIL_HTML, _SOURCE_URL)
    assert dataclasses.asdict(old_res) == dataclasses.asdict(new_res)


# ── чистые title/address-хелперы ──────────────────────────────────────────────


def test_pure_title_helpers_parity() -> None:
    titles = [
        "3-к. квартира, 75 м², 20/26 эт.",
        "Квартира-студия, 24,5 м², 12/25 эт.",
        "1-к. квартира, 38 м², 5/9 эт.",
        "2-к. квартира, 52,3 м², 7/16 эт.",
        "Комната, 18 м², 3/5 эт.",
    ]
    for t in titles:
        assert old_rooms(t) == new_rooms(t)
        assert old_area(t) == new_area(t)
        assert old_floor(t) == new_floor(t)


def test_clean_address_parity() -> None:
    addrs = [
        None,
        "Екатеринбург, ул. Постовского, 17А",
        "Свердловская область, Екатеринбург, Малышева, 51",
        "  Екатеринбург,   Ленина  1  ",
    ]
    for a in addrs:
        assert old_clean_address(a) == new_clean_address(a)


# ── strangler-guard: kit-провайдер не импортирует app.* ──────────────────────


def test_kit_avito_has_no_app_imports() -> None:
    """Ни один модуль scraper_kit.providers.avito.* не импортирует app.* (развязка)."""
    import inspect

    from scraper_kit.providers.avito import detail, houses, imv, serp, shared

    for mod in (serp, detail, houses, imv, shared):
        source = inspect.getsource(mod)
        for line in source.splitlines():
            stripped = line.strip()
            assert not stripped.startswith("from app"), f"{mod.__name__}: {line!r}"
            assert not stripped.startswith("import app"), f"{mod.__name__}: {line!r}"
