"""Unit tests для WeightProfile Pydantic models + resolve_weights.

Mock-based — без реальной БД. Проверяет:
- WeightProfileCreate: отклонение неизвестных категорий
- WeightProfileCreate: отклонение весов за пределами MIN/MAX
- WeightProfileCreate: корректные веса проходят валидацию
- WeightProfileUpdate: weights=None не вызывает ошибку
- resolve_weights: нет user_id и profile_id → системные дефолты
- resolve_weights: user_id задан, default-профиль есть → его веса
- resolve_weights: profile_id задан → его веса
- resolve_weights: профиль не найден → системные дефолты (fallback)
"""

from __future__ import annotations

from unittest.mock import MagicMock

import pytest
from pydantic import ValidationError

from app.services.site_finder.weight_profiles import (
    _SYSTEM_POI_WEIGHTS,
    WeightProfile,
    WeightProfileCreate,
    WeightProfileUpdate,
    resolve_weights,
)

# ── Validation tests ───────────────────────────────────────────────────────────


def test_validate_unknown_category() -> None:
    """Неизвестная POI-категория → ValidationError."""
    with pytest.raises(ValidationError, match="Unknown POI categories"):
        WeightProfileCreate(
            user_id="user-1",
            profile_name="test",
            weights={"unknown": 1.0},
        )


def test_validate_weight_bounds_too_high() -> None:
    """Вес > MAX_WEIGHT → ValidationError."""
    with pytest.raises(ValidationError, match="out of bounds"):
        WeightProfileCreate(
            user_id="user-1",
            profile_name="test",
            weights={"school": 99.0},
        )


def test_validate_weight_negative_too_low() -> None:
    """Вес < MIN_WEIGHT → ValidationError."""
    with pytest.raises(ValidationError, match="out of bounds"):
        WeightProfileCreate(
            user_id="user-1",
            profile_name="test",
            weights={"tram_stop": -3.0},
        )


def test_validate_valid_weights() -> None:
    """Корректные веса проходят валидацию без ошибок."""
    profile = WeightProfileCreate(
        user_id="user-1",
        profile_name="my profile",
        weights={"school": 1.5, "tram_stop": -0.5, "park": 2.9},
    )
    assert profile.weights["school"] == 1.5
    assert profile.weights["tram_stop"] == -0.5


def test_validate_empty_weights() -> None:
    """Пустой dict weights допустим (используются системные дефолты)."""
    profile = WeightProfileCreate(
        user_id="user-1",
        profile_name="empty",
        weights={},
    )
    assert profile.weights == {}


def test_validate_boundary_values() -> None:
    """Граничные значения MIN_WEIGHT и MAX_WEIGHT включительно — допустимы."""
    profile = WeightProfileCreate(
        user_id="user-1",
        profile_name="boundary",
        weights={"school": -2.0, "park": 3.0},
    )
    assert profile.weights["school"] == -2.0
    assert profile.weights["park"] == 3.0


def test_validate_update_weights_none() -> None:
    """WeightProfileUpdate с weights=None не вызывает ошибку валидации."""
    upd = WeightProfileUpdate(weights=None)
    assert upd.weights is None


def test_validate_update_unknown_category() -> None:
    """WeightProfileUpdate с неизвестной категорией → ValidationError."""
    with pytest.raises(ValidationError, match="Unknown POI categories"):
        WeightProfileUpdate(weights={"bogus_category": 1.0})


# ── resolve_weights tests ──────────────────────────────────────────────────────


def test_resolve_weights_system_default() -> None:
    """Оба аргумента None → возвращаются системные веса."""
    db = MagicMock()
    result = resolve_weights(db, user_id=None, profile_id=None)
    assert result == _SYSTEM_POI_WEIGHTS
    # db не должен вызываться вообще
    db.execute.assert_not_called()


def test_resolve_weights_system_default_returns_copy() -> None:
    """Возвращается копия словаря, не ссылка на _SYSTEM_POI_WEIGHTS."""
    db = MagicMock()
    result = resolve_weights(db, user_id=None, profile_id=None)
    result["school"] = 999.0
    # Оригинал не изменён
    assert _SYSTEM_POI_WEIGHTS["school"] == 1.5


def _make_profile_mock(weights: dict[str, float]) -> WeightProfile:
    """Вспомогательная функция: создать WeightProfile с заданными weights."""
    from datetime import UTC, datetime

    return WeightProfile(
        id=1,
        user_id="user-1",
        profile_name="test",
        weights=weights,
        is_default=True,
        description=None,
        created_at=datetime.now(UTC),
        updated_at=datetime.now(UTC),
    )


def test_resolve_weights_uses_default_profile() -> None:
    """user_id задан, default-профиль найден → его веса."""
    custom_weights = {"school": 2.0, "park": 0.5}
    profile = _make_profile_mock(custom_weights)

    db = MagicMock()
    import app.services.site_finder.weight_profiles as wp_module

    original = wp_module.get_default_profile
    wp_module.get_default_profile = lambda _db, uid: profile

    try:
        result = resolve_weights(db, user_id="user-1", profile_id=None)
    finally:
        wp_module.get_default_profile = original

    assert result == custom_weights


def test_resolve_weights_uses_specific_profile() -> None:
    """profile_id задан → веса конкретного профиля."""
    custom_weights = {"kindergarten": 1.8, "metro_stop": 1.0}
    profile = _make_profile_mock(custom_weights)

    db = MagicMock()
    import app.services.site_finder.weight_profiles as wp_module

    original = wp_module.get_profile
    wp_module.get_profile = lambda _db, uid, pid: profile

    try:
        result = resolve_weights(db, user_id="user-1", profile_id=42)
    finally:
        wp_module.get_profile = original

    assert result == custom_weights


def test_resolve_weights_profile_not_found_fallback() -> None:
    """Профиль не найден (get_profile → None) → системные дефолты."""
    db = MagicMock()
    import app.services.site_finder.weight_profiles as wp_module

    original_get = wp_module.get_profile
    original_default = wp_module.get_default_profile
    wp_module.get_profile = lambda _db, uid, pid: None
    wp_module.get_default_profile = lambda _db, uid: None

    try:
        result = resolve_weights(db, user_id="user-1", profile_id=999)
    finally:
        wp_module.get_profile = original_get
        wp_module.get_default_profile = original_default

    assert result == _SYSTEM_POI_WEIGHTS


def test_resolve_weights_empty_profile_weights_fallback() -> None:
    """Профиль найден но weights пустой → системные дефолты."""
    profile = _make_profile_mock({})  # пустые weights

    db = MagicMock()
    import app.services.site_finder.weight_profiles as wp_module

    original_default = wp_module.get_default_profile
    wp_module.get_default_profile = lambda _db, uid: profile

    try:
        result = resolve_weights(db, user_id="user-1", profile_id=None)
    finally:
        wp_module.get_default_profile = original_default

    assert result == _SYSTEM_POI_WEIGHTS
