"""Unit tests for #1871 P1.2 — ghost-anchor invariant guard.

Инвариант: n_analogs == 0 МОЖЕТ означать только confidence='low'.
Гард `_enforce_zero_analog_low` ловит любой будущий путь (external-valuation
blend, rehydrate, миграция), который наполнит median/sources_used без единого
реального аналога ("ghost-anchor", напр. legacy-строка 5fcc1e99: n=0, high,
38.45M, yandex_valuation). Mainline уже честен — это belt-and-suspenders.
"""

from __future__ import annotations

import os
import sys
from unittest.mock import MagicMock

# pydantic Settings требует DATABASE_URL при инициализации.
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
# WeasyPrint stubbed in CI.
sys.modules.setdefault("weasyprint", MagicMock())

from app.services.estimator import _enforce_zero_analog_low  # noqa: E402

# Suffix-маркер из гарда — проверяем его наличие/отсутствие, не дублирование.
_CAVEAT_MARKER = "без сопоставимых аналогов рядом"


def test_zero_analogs_forces_low_confidence() -> None:
    """Ghost-симуляция: n_analogs=0 но confidence='high', median>0 → low + caveat.

    Воспроизводит legacy ghost 5fcc1e99 (n=0, high, 38.45M, yandex_valuation):
    confidence должен быть форсирован в 'low', caveat-suffix добавлен.
    """
    confidence, explanation = _enforce_zero_analog_low(
        "high",
        0,
        "Оценка по внешним сервисам.",
        median_price=38_450_000,
        sources_used=["yandex_valuation"],
    )

    assert confidence == "low"
    assert _CAVEAT_MARKER in explanation
    # Исходный текст explanation сохранён (caveat дописан, не затёр).
    assert "Оценка по внешним сервисам." in explanation


def test_zero_analogs_medium_also_forced_low() -> None:
    """n_analogs=0 + confidence='medium' → low + caveat (любой не-low форсится)."""
    confidence, explanation = _enforce_zero_analog_low(
        "medium",
        0,
        None,
        median_price=10_000_000,
        sources_used=["cian_valuation"],
    )

    assert confidence == "low"
    assert _CAVEAT_MARKER in explanation


def test_zero_analogs_already_low_no_duplicate_caveat() -> None:
    """n_analogs=0 + confidence='low' → остаётся low, caveat НЕ дублируется."""
    original = "Не найдено аналогов — попробуйте уточнить адрес."
    confidence, explanation = _enforce_zero_analog_low(
        "low",
        0,
        original,
        median_price=0,
        sources_used=[],
    )

    assert confidence == "low"
    assert explanation == original
    assert _CAVEAT_MARKER not in explanation


def test_nonzero_analogs_high_unchanged() -> None:
    """n_analogs=5 + confidence='high' → проходит без изменений (честный путь)."""
    original = "Найдено 5 аналогов из 4 разных адресов."
    confidence, explanation = _enforce_zero_analog_low(
        "high",
        5,
        original,
        median_price=12_000_000,
        sources_used=["avito", "cian"],
    )

    assert confidence == "high"
    assert explanation == original
    assert _CAVEAT_MARKER not in explanation


def test_none_explanation_returns_empty_string_not_none() -> None:
    """explanation=None должен нормализоваться в строку (контракт schema — str)."""
    confidence, explanation = _enforce_zero_analog_low(
        "high",
        7,
        None,
        median_price=9_000_000,
        sources_used=["avito"],
    )

    assert confidence == "high"
    assert explanation == ""
    assert isinstance(explanation, str)


def test_enforce_zero_analog_low_always_downgrades() -> None:
    """_enforce_zero_analog_low всегда понижает high→low при n_analogs=0 (гард всегда ON)."""
    confidence, _ = _enforce_zero_analog_low(
        "high",
        0,
        "Оценка.",
        median_price=38_000_000,
        sources_used=["yandex_valuation"],
    )
    assert confidence == "low"  # сама функция не смотрит на settings
