"""Unit-тесты для нормализаторов scrapers/domrf_kn (#1208).

Главный сценарий: детерминированный fallback flat_id через sha256 — НЕ
рандомизированный hash(). Раньше при resume упавшего sweep (новый процесс
воркера) та же квартира получала другой id → ON CONFLICT не срабатывал →
дубли в одном snapshot.
"""

from __future__ import annotations

import hashlib

from app.services.scrapers.domrf_kn import _norm_flat


def _expected_sha_fallback(elem: str) -> int:
    """Зеркало формулы из _norm_flat — для прямого сравнения в тестах."""
    digest = hashlib.sha256(str(elem).encode("utf-8")).digest()[:8]
    return int.from_bytes(digest, "big") % (2**63 - 1)


class TestFlatIdFallbackDeterministic:
    """flat_id fallback ОБЯЗАН быть детерминированным между процессами/перезапусками."""

    def test_fallback_matches_sha256_formula(self) -> None:
        row = {"elemId": "abc123"}
        out = _norm_flat(row, region_cd=66)
        assert out["id"] == _expected_sha_fallback("abc123")

    def test_fallback_stable_across_calls(self) -> None:
        # Тот же elemId → тот же id. (Раньше hash() мог дать другой id в новом процессе.)
        row = {"elemId": "stable-elem-id"}
        a = _norm_flat(row, region_cd=66)["id"]
        b = _norm_flat(row, region_cd=66)["id"]
        assert a == b
        assert a == _expected_sha_fallback("stable-elem-id")

    def test_different_elems_give_different_ids(self) -> None:
        a = _norm_flat({"elemId": "elem-A"}, region_cd=66)["id"]
        b = _norm_flat({"elemId": "elem-B"}, region_cd=66)["id"]
        assert a != b

    def test_flat_id_present_wins_over_fallback(self) -> None:
        # Если flatId есть — используем его, fallback не активируется.
        row = {"flatId": 12345, "elemId": "should-be-ignored"}
        out = _norm_flat(row, region_cd=66)
        assert out["id"] == 12345

    def test_no_flat_id_no_elem_id_returns_none(self) -> None:
        out = _norm_flat({}, region_cd=66)
        assert out["id"] is None

    def test_fallback_fits_bigint(self) -> None:
        # 8-байтовый digest усечён в pos-int, всегда < 2**63.
        out = _norm_flat({"elemId": "x" * 1000}, region_cd=66)
        assert out["id"] is not None
        assert 0 <= out["id"] < 2**63


class TestCatalogUrlHash:
    """catalog_url_hash ОБЯЗАН пробрасываться из elemId (#2442 Task 1).

    Ранее хардкодился в None → flat-catalog scraper выбирал 0 строк
    (WHERE catalog_url_hash IS NOT NULL). Теперь берётся из того же elemId,
    что и fallback flat_id, и живёт независимо от того, пришёл ли flatId.
    """

    def test_hash_populated_from_elem_id(self) -> None:
        row = {"elemId": "abc-hash-123"}
        out = _norm_flat(row, region_cd=66)
        assert out["catalog_url_hash"] == "abc-hash-123"

    def test_hash_populated_even_when_flat_id_present(self) -> None:
        # flatId присутствует → fallback-ветка flat_id НЕ выполняется, но hash
        # всё равно должен прочитаться (регрессия, которую чинит Task 1).
        row = {"flatId": 999, "elemId": "hash-with-flatid"}
        out = _norm_flat(row, region_cd=66)
        assert out["id"] == 999
        assert out["catalog_url_hash"] == "hash-with-flatid"

    def test_hash_none_when_elem_id_absent(self) -> None:
        out = _norm_flat({"flatId": 1}, region_cd=66)
        assert out["catalog_url_hash"] is None

    def test_hash_empty_string_normalized_to_none(self) -> None:
        # Пустую строку трактуем как отсутствие (не пишем "" в БД).
        out = _norm_flat({"flatId": 1, "elemId": ""}, region_cd=66)
        assert out["catalog_url_hash"] is None
