"""Tests for validate_setting() edge cases.

Covers:
- validate_setting() min/max constraints, select edge cases, unknown ui_element (12 tests)
"""

from unittest.mock import patch

from local_deep_research.web.routes.settings_routes import (
    validate_setting,
    DYNAMIC_SETTINGS,
)
from local_deep_research.web.models.settings import BaseSetting, SettingType


# ── validate_setting() edge case tests ──────────────────────────────


def _make_setting(ui_element, **kwargs):
    """Helper to build a BaseSetting with minimal boilerplate."""
    defaults = dict(
        key="test.key",
        value=0,
        type=SettingType.APP,
        name="Test",
    )
    defaults.update(kwargs)
    defaults["ui_element"] = ui_element
    return BaseSetting(**defaults)


class TestValidateSettingMinMax:
    """Tests for number/slider/range min/max constraint validation."""

    def test_number_below_min(self):
        """Number below min_value → (False, error)."""
        setting = _make_setting("number", min_value=0)
        valid, msg = validate_setting(setting, -1)
        assert valid is False
        assert "at least" in msg

    def test_number_above_max(self):
        """Number above max_value → (False, error)."""
        setting = _make_setting("number", max_value=100)
        valid, msg = validate_setting(setting, 150)
        assert valid is False
        assert "at most" in msg

    def test_number_no_constraints(self):
        """Number with no min/max → (True, None)."""
        setting = _make_setting("number")
        valid, msg = validate_setting(setting, 999)
        assert valid is True
        assert msg is None

    def test_number_only_min_passes(self):
        """Number with only min_value, value above → (True, None)."""
        setting = _make_setting("number", min_value=0)
        valid, msg = validate_setting(setting, 5)
        assert valid is True

    def test_number_only_max_passes(self):
        """Number with only max_value, value below → (True, None)."""
        setting = _make_setting("number", max_value=100)
        valid, msg = validate_setting(setting, 50)
        assert valid is True

    def test_slider_below_min(self):
        """Slider below min_value → (False, error).

        'slider' is not in UI_ELEMENT_TO_SETTING_TYPE, so get_typed_setting_value
        returns default=None. We patch it to pass through the numeric value so
        we can test the min/max constraint logic in validate_setting.
        """
        setting = _make_setting("slider", min_value=5)
        with patch(
            "local_deep_research.web.routes.settings_routes.get_typed_setting_value",
            return_value=2,
        ):
            valid, msg = validate_setting(setting, 2)
            assert valid is False
            assert "at least" in msg

    def test_range_above_max(self):
        """Range above max_value → (False, error)."""
        setting = _make_setting("range", max_value=10)
        valid, msg = validate_setting(setting, 15)
        assert valid is False
        assert "at most" in msg


class TestValidateSettingSelect:
    """Tests for select ui_element edge cases."""

    def test_select_invalid_option(self):
        """Select with value not in options → (False, error)."""
        setting = _make_setting(
            "select",
            options=[{"value": "a"}, {"value": "b"}],
        )
        valid, msg = validate_setting(setting, "x")
        assert valid is False
        assert "must be one of" in msg

    def test_select_plain_string_options(self):
        """Select with plain string options list → valid option passes.

        The Pydantic model enforces List[Dict], but at runtime the
        validate_setting code handles both dict and non-dict options
        (line 199). We use a mock to bypass Pydantic validation.
        """
        from unittest.mock import MagicMock

        setting = MagicMock()
        setting.key = "test.select"
        setting.ui_element = "select"
        setting.options = ["a", "b", "c"]

        # Patch get_typed_setting_value to pass through the value unchanged
        with patch(
            "local_deep_research.web.routes.settings_routes.get_typed_setting_value",
            return_value="b",
        ):
            valid, msg = validate_setting(setting, "b")
            assert valid is True

    def test_select_empty_options_skips_validation(self):
        """Select with empty options list → validation skipped → (True, None)."""
        setting = _make_setting("select", options=[])
        valid, msg = validate_setting(setting, "anything")
        assert valid is True

    def test_select_dynamic_setting_skips_validation(self):
        """Select with key in DYNAMIC_SETTINGS → validation skipped."""
        # Use a real DYNAMIC_SETTINGS key
        dynamic_key = DYNAMIC_SETTINGS[0]  # "llm.provider"
        setting = _make_setting(
            "select",
            key=dynamic_key,
            options=[{"value": "a"}, {"value": "b"}],
        )
        # Value not in options, but should pass because it's dynamic
        valid, msg = validate_setting(setting, "nonexistent_provider")
        assert valid is True


class TestValidateSettingUnknownElement:
    """Test that unknown ui_element types pass through."""

    def test_unknown_ui_element_passes(self):
        """Unknown ui_element like 'custom_widget' → (True, None)."""
        setting = _make_setting("custom_widget")
        valid, msg = validate_setting(setting, "any value")
        assert valid is True
        assert msg is None
