"""#2002 #4: manual-review recommendation (производный ФЛАГ, НЕ цена).

Покрывает чистый helper `_manual_review`:
  - каждый триггер в изоляции (премиальный дом / высокая стоимость / низкая
    уверенность / широкий диапазон);
  - комбинированный случай (несколько причин сразу);
  - all-clear (нет причин → recommended False);
  - граничные значения порогов (>= high_value, >= wide_ratio);
  - div-guard на диапазон (range_low None/0 → без деления на ноль).

Плюс smoke-проверка дефолтов AggregatedEstimate (флаг — метаданные: сериализация
без изменений, когда ручная оценка не рекомендована).
"""

from __future__ import annotations

import os
from types import SimpleNamespace

os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")

import pytest

from app.services.estimator import _manual_review

PREMIUM_REASON = "премиальный дом — премия зависит от отделки/вида (не в данных сделок)"
HIGH_VALUE_REASON = "высокая стоимость (≥20 млн ₽)"
LOW_CONF_REASON = "низкая уверенность оценки"
WIDE_RANGE_REASON = "широкий диапазон цены"
ELITE_REASON = (
    "элитный/дорогой сегмент — авто-оценка консервативна: премия за отделку, "
    "вид и класс дома не отражена в данных сделок, фактическая цена может быть выше"
)


def _settings(
    *,
    high_value: int = 20_000_000,
    wide_ratio: float = 1.9,
    elite_ppm2: int = 250_000,
) -> SimpleNamespace:
    """Duck-typed заглушка Settings — helper читает только эти поля."""
    return SimpleNamespace(
        estimate_manual_review_high_value_rub=high_value,
        estimate_manual_review_wide_range_ratio=wide_ratio,
        estimate_manual_review_elite_ppm2=elite_ppm2,
    )


# ── Каждый триггер в изоляции ────────────────────────────────────────────────


def test_premium_building_alone() -> None:
    rec, reasons = _manual_review(
        premium_building=True,
        expected_sold_price=8_000_000,
        confidence="high",
        range_low=7_500_000,
        range_high=8_500_000,
        settings=_settings(),
    )
    assert rec is True
    assert reasons == [PREMIUM_REASON]


def test_high_value_alone() -> None:
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=25_000_000,
        confidence="high",
        range_low=24_000_000,
        range_high=26_000_000,
        settings=_settings(),
    )
    assert rec is True
    assert reasons == [HIGH_VALUE_REASON]


def test_low_confidence_alone() -> None:
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=5_000_000,
        confidence="low",
        range_low=4_500_000,
        range_high=5_500_000,
        settings=_settings(),
    )
    assert rec is True
    assert reasons == [LOW_CONF_REASON]


def test_wide_range_alone() -> None:
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=8_000_000,
        confidence="high",
        range_low=5_000_000,
        range_high=10_000_000,  # ratio 2.0 ≥ 1.9
        settings=_settings(),
    )
    assert rec is True
    assert reasons == [WIDE_RANGE_REASON]


def test_elite_ppm2_alone() -> None:
    """asking_ppm2 ≥ elite_ppm2 → только элит-причина, даже когда оценка < 20 млн.

    Юнит НЕ в premium-доме, expected_sold < high_value, confidence не low,
    диапазон узкий — без asking-триггера остался бы без предупреждения.
    """
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=12_000_000,
        confidence="high",
        range_low=11_000_000,
        range_high=13_000_000,  # ratio ~1.18 < 1.9
        settings=_settings(),
        asking_ppm2=300_000,  # ≥ 250_000
    )
    assert rec is True
    assert reasons == [ELITE_REASON]


def test_elite_ppm2_boundary_inclusive() -> None:
    """asking_ppm2 == elite_ppm2 → триггерит (>=)."""
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=12_000_000,
        confidence="high",
        range_low=11_000_000,
        range_high=13_000_000,
        settings=_settings(),
        asking_ppm2=250_000,
    )
    assert rec is True
    assert reasons == [ELITE_REASON]


def test_elite_ppm2_just_below_threshold() -> None:
    """asking_ppm2 на 1 ₽ ниже порога → без элит-причины."""
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=12_000_000,
        confidence="high",
        range_low=11_000_000,
        range_high=13_000_000,
        settings=_settings(),
        asking_ppm2=249_999,
    )
    assert rec is False
    assert reasons == []


def test_elite_ppm2_none_safe() -> None:
    """asking_ppm2 None (дефолт) → элит-триггер не срабатывает, без ошибок."""
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=12_000_000,
        confidence="high",
        range_low=11_000_000,
        range_high=13_000_000,
        settings=_settings(),
        asking_ppm2=None,
    )
    assert rec is False
    assert reasons == []
    assert ELITE_REASON not in reasons


def test_elite_ppm2_zero_safe() -> None:
    """asking_ppm2 == 0 (falsy) → без деления/триггера, без элит-причины."""
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=12_000_000,
        confidence="high",
        range_low=11_000_000,
        range_high=13_000_000,
        settings=_settings(),
        asking_ppm2=0,
    )
    assert rec is False
    assert ELITE_REASON not in reasons


def test_elite_ppm2_combined_with_other_triggers() -> None:
    """Элит-причина аддитивна: premium + high_value + low_conf + wide + elite."""
    rec, reasons = _manual_review(
        premium_building=True,
        expected_sold_price=30_000_000,
        confidence="low",
        range_low=10_000_000,
        range_high=25_000_000,  # ratio 2.5 ≥ 1.9
        settings=_settings(),
        asking_ppm2=400_000,
    )
    assert rec is True
    # порядок детерминирован: premium → high_value → low_conf → wide_range → elite
    assert reasons == [
        PREMIUM_REASON,
        HIGH_VALUE_REASON,
        LOW_CONF_REASON,
        WIDE_RANGE_REASON,
        ELITE_REASON,
    ]


# ── Комбинированный / all-clear ──────────────────────────────────────────────


def test_multiple_reasons_combined() -> None:
    rec, reasons = _manual_review(
        premium_building=True,
        expected_sold_price=30_000_000,
        confidence="low",
        range_low=10_000_000,
        range_high=25_000_000,  # ratio 2.5 ≥ 1.9
        settings=_settings(),
    )
    assert rec is True
    # порядок детерминирован: premium → high_value → low_conf → wide_range
    assert reasons == [PREMIUM_REASON, HIGH_VALUE_REASON, LOW_CONF_REASON, WIDE_RANGE_REASON]


def test_all_clear_no_reasons() -> None:
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=10_000_000,
        confidence="medium",
        range_low=9_000_000,
        range_high=11_000_000,  # ratio ~1.22 < 1.9
        settings=_settings(),
    )
    assert rec is False
    assert reasons == []


# ── Граничные значения порогов ───────────────────────────────────────────────


def test_high_value_boundary_inclusive() -> None:
    """expected_sold_price == high_value → триггерит (>=)."""
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=20_000_000,
        confidence="medium",
        range_low=19_000_000,
        range_high=21_000_000,
        settings=_settings(),
    )
    assert rec is True
    assert reasons == [HIGH_VALUE_REASON]


def test_high_value_just_below_threshold() -> None:
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=19_999_999,
        confidence="medium",
        range_low=19_000_000,
        range_high=21_000_000,
        settings=_settings(),
    )
    assert rec is False
    assert reasons == []


def test_wide_range_boundary_inclusive() -> None:
    """range_high/range_low == wide_ratio → триггерит (>=)."""
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=8_000_000,
        confidence="high",
        range_low=10_000_000,
        range_high=19_000_000,  # ratio == 1.9
        settings=_settings(),
    )
    assert rec is True
    assert reasons == [WIDE_RANGE_REASON]


def test_wide_range_just_below_threshold() -> None:
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=8_000_000,
        confidence="high",
        range_low=10_000_000,
        range_high=18_900_000,  # ratio 1.89 < 1.9
        settings=_settings(),
    )
    assert rec is False
    assert reasons == []


# ── Div-guard на диапазон ────────────────────────────────────────────────────


@pytest.mark.parametrize(
    ("range_low", "range_high"),
    [
        (None, 10_000_000),
        (0, 10_000_000),
        (5_000_000, None),
        (None, None),
    ],
)
def test_wide_range_div_guard(range_low: int | None, range_high: int | None) -> None:
    """range_low None/0 (или range_high None) → без ZeroDivisionError и без причины."""
    rec, reasons = _manual_review(
        premium_building=False,
        expected_sold_price=8_000_000,
        confidence="high",
        range_low=range_low,
        range_high=range_high,
        settings=_settings(),
    )
    assert rec is False
    assert WIDE_RANGE_REASON not in reasons


# ── invariant recommended == bool(reasons) ───────────────────────────────────


def test_recommended_equals_bool_reasons_invariant() -> None:
    for premium, esp, conf in [
        (False, None, "medium"),
        (True, 5_000_000, "medium"),
        (False, 25_000_000, "low"),
    ]:
        rec, reasons = _manual_review(
            premium_building=premium,
            expected_sold_price=esp,
            confidence=conf,
            range_low=9_000_000,
            range_high=11_000_000,
            settings=_settings(),
        )
        assert rec is bool(reasons)


# ── AggregatedEstimate default serialization smoke ───────────────────────────


def test_response_model_defaults_no_manual_review() -> None:
    """AggregatedEstimate по умолчанию: recommended=False, reasons=[]."""
    from datetime import UTC, datetime
    from uuid import uuid4

    from app.schemas.trade_in import AggregatedEstimate

    est = AggregatedEstimate(
        estimate_id=uuid4(),
        median_price_rub=10_000_000,
        range_low_rub=9_000_000,
        range_high_rub=11_000_000,
        median_price_per_m2=200_000,
        confidence="medium",
        n_analogs=12,
        period_months=24,
        analogs=[],
        actual_deals=[],
        expires_at=datetime.now(tz=UTC),
    )

    assert est.manual_review_recommended is False
    assert est.manual_review_reasons == []
    dumped = est.model_dump()
    assert dumped["manual_review_recommended"] is False
    assert dumped["manual_review_reasons"] == []


if __name__ == "__main__":  # pragma: no cover
    raise SystemExit(pytest.main([__file__, "-q"]))
