"""Unit tests for N1 floor/total_floors extraction (#795).

Tests _extract_floor_from_title() directly (pure function, no network/DB).
Also covers _card_to_lot integration via a minimal fake card HTML node.
"""

from __future__ import annotations

import pytest

from app.services.scrapers.n1 import _extract_floor_from_title

# ── _extract_floor_from_title: direct unit tests ────────────────────────────


@pytest.mark.parametrize(
    "text, expected_floor, expected_total",
    [
        # Typical N1 card: floor 1 of 11
        ("1/11 2-к, Репина, 75/2 стр.", 1, 11),
        # Floor in middle of building
        ("5/9 3-к, Ленина, 10 стр.", 5, 9),
        # Top floor
        ("17/17 1-к, Малышева, 12", 17, 17),
        # Leading whitespace tolerated
        ("  3/16 студия, Вайнера, 5", 3, 16),
        # Three-digit total floors (edge of range)
        ("2/100 2-к, Тверитина, 8", 2, 100),
    ],
)
def test_extract_floor_happy_path(text: str, expected_floor: int, expected_total: int) -> None:
    floor, total = _extract_floor_from_title(text)
    assert floor == expected_floor
    assert total == expected_total


@pytest.mark.parametrize(
    "text",
    [
        # No leading N/M at all — classic old format without floor token
        ("2-к, Репина, 75/2 стр."),
        # House number fraction in middle — must NOT be parsed as floor
        ("Репина, 75/2 стр."),
        # N/M not at string start (preceded by other text)
        ("квартира 3/9, Ленина, 1"),
        # floor=0 is invalid
        ("0/9 1-к, Тестовая, 1"),
        # total_floors < floor — sanity fail
        ("9/5 2-к, Тестовая, 1"),
        # total_floors > 100 — sanity fail
        ("2/101 1-к, Тестовая, 1"),
        # Empty string
        (""),
        # Only digits, no slash
        ("5 2-к, Тестовая, 1"),
    ],
)
def test_extract_floor_returns_none(text: str) -> None:
    floor, total = _extract_floor_from_title(text)
    assert floor is None
    assert total is None


def test_house_number_fraction_not_misparsed() -> None:
    """«75/2 стр.» appearing after rooms segment must not match floor regex."""
    # The fraction 75/2 comes AFTER the rooms prefix, not at string start.
    floor, total = _extract_floor_from_title("3-к, Репина, 75/2 стр.")
    assert floor is None
    assert total is None


def test_non_leading_fraction_after_comma() -> None:
    """Fraction embedded mid-string via comma-separated address token."""
    floor, total = _extract_floor_from_title("ул. 8 Марта, 51/3, 2-к")
    assert floor is None
    assert total is None


# ── Integration: _card_to_lot via fake selectolax node ──────────────────────


class _FakeAttr(dict):
    """Minimal attributes dict for a fake HTML node."""


class _FakeNode:
    """Minimal selectolax node stub for _card_to_lot tests."""

    def __init__(
        self,
        *,
        href: str = "/view/12345/",
        title_text: str = "1/11 2-к, Репина, 75/2 стр.",
        area_text: str = "52 м2",
        price_text: str = "8 500 000",
        img_src: str = "https://img.n1.ru/photo.jpg",
    ) -> None:
        self._href = href
        self._title_text = title_text
        self._area_text = area_text
        self._price_text = price_text
        self._img_src = img_src

    def css_first(self, selector: str) -> _FakeNode | None:
        if 'a[href*="/view/"]' in selector:
            return self
        if "total-area" in selector:
            return _FakeTextNode(self._area_text)
        if "param-price" in selector:
            return _FakeTextNode(self._price_text)
        return None

    def css(self, selector: str) -> list[_FakeNode]:
        if selector == "img":
            return [_FakeImgNode(self._img_src)]
        return []

    @property
    def attributes(self) -> dict:
        return {"href": self._href}

    def text(self, strip: bool = False) -> str:
        t = self._title_text
        return t.strip() if strip else t


class _FakeTextNode:
    def __init__(self, text: str) -> None:
        self._text = text

    def text(self, strip: bool = False) -> str:
        return self._text.strip() if strip else self._text


class _FakeImgNode:
    def __init__(self, src: str) -> None:
        self.attributes = {"src": src}


def _call_card_to_lot(card: _FakeNode):  # type: ignore[return]
    from app.services.scrapers.n1 import N1Scraper

    scraper = object.__new__(N1Scraper)
    scraper.base_url = "https://ekaterinburg.n1.ru"  # type: ignore[attr-defined]
    return scraper._card_to_lot(card)  # type: ignore[attr-defined]


def test_card_to_lot_parses_floor() -> None:
    card = _FakeNode(title_text="1/11 2-к, Репина, 75/2 стр.")
    lot = _call_card_to_lot(card)
    assert lot is not None
    assert lot.floor == 1
    assert lot.total_floors == 11


def test_card_to_lot_floor_none_when_no_token() -> None:
    card = _FakeNode(title_text="2-к, Репина, 75/2 стр.")
    lot = _call_card_to_lot(card)
    assert lot is not None
    assert lot.floor is None
    assert lot.total_floors is None


def test_card_to_lot_listing_date_is_none() -> None:
    """N1 SERP has no date — listing_date must remain None."""
    card = _FakeNode(title_text="3/9 1-к, Малышева, 12")
    lot = _call_card_to_lot(card)
    assert lot is not None
    assert lot.listing_date is None
