"""Tests for settings_routes module - Settings API endpoints."""

import pytest
from unittest.mock import patch, MagicMock, Mock

SETTINGS_PREFIX = "/settings"


class TestValidateSetting:
    """Tests for validate_setting function."""

    def test_validate_string_setting(self):
        """Test validating string setting."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        # Create a proper Setting object for text input
        setting = BaseSetting(
            key="test_string",
            value="default",
            type=SettingType.APP,
            name="Test String",
            ui_element="text",
        )

        # Test valid string
        valid, msg = validate_setting(setting, "hello")
        assert valid is True

    def test_validate_integer_setting(self):
        """Test validating integer setting."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        # Create a proper Setting object for number input
        setting = BaseSetting(
            key="test_int",
            value=0,
            type=SettingType.APP,
            name="Test Int",
            ui_element="number",
        )

        # Test valid integer
        valid, msg = validate_setting(setting, 42)
        assert valid is True

    def test_validate_float_setting(self):
        """Test validating float setting."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        # Create a proper Setting object for number input
        setting = BaseSetting(
            key="test_float",
            value=0.0,
            type=SettingType.APP,
            name="Test Float",
            ui_element="number",
        )

        # Test valid float
        valid, msg = validate_setting(setting, 3.14)
        assert valid is True

    def test_validate_bool_setting(self):
        """Test validating boolean setting."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        # Create a proper Setting object for checkbox input
        setting = BaseSetting(
            key="test_bool",
            value=False,
            type=SettingType.APP,
            name="Test Bool",
            ui_element="checkbox",
        )

        # Test valid boolean
        valid, msg = validate_setting(setting, True)
        assert valid is True

    def test_validate_invalid_type(self):
        """Test validating setting with wrong type."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        # Create a proper Setting object for number input
        setting = BaseSetting(
            key="test_int",
            value=0,
            type=SettingType.APP,
            name="Test Int",
            ui_element="number",
        )

        # Test invalid type (string where int expected)
        valid, msg = validate_setting(setting, "not an int")
        assert valid is False


class TestCalculateWarnings:
    """Tests for calculate_warnings function."""

    def test_calculate_warnings_returns_list(self):
        """Test that calculate_warnings returns a list."""
        from local_deep_research.web.warning_checks import calculate_warnings

        with patch(
            "local_deep_research.web.warning_checks.get_user_db_session"
        ) as mock_session:
            mock_ctx = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_ctx)
            mock_session.return_value.__exit__ = Mock(return_value=False)

            with patch(
                "local_deep_research.web.warning_checks.get_settings_manager"
            ) as mock_gsm:
                mock_instance = MagicMock()
                mock_instance.get_setting.return_value = "test"
                mock_gsm.return_value = mock_instance

                with patch(
                    "local_deep_research.web.warning_checks.session",
                    {"username": "testuser"},
                ):
                    result = calculate_warnings()

                    assert isinstance(result, list)


class TestSettingsBlueprintImport:
    """Tests for settings blueprint import."""

    def test_blueprint_exists(self):
        """Test that settings blueprint exists."""
        from local_deep_research.web.routes.settings_routes import settings_bp

        assert settings_bp is not None
        assert settings_bp.name == "settings"


class TestSettingsPageRoutes:
    """Tests for settings page routes."""

    def test_settings_page_route_exists(self, client):
        """Test settings page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/")
        # Should exist but may require auth
        assert response.status_code == 302, response.status_code

    def test_main_config_page_route_exists(self, client):
        """Test main config page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/main")
        assert response.status_code == 302, response.status_code

    def test_collections_config_page_route_exists(self, client):
        """Test collections config page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/collections")
        assert response.status_code == 302, response.status_code

    def test_api_keys_config_page_route_exists(self, client):
        """Test API keys config page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api_keys")
        assert response.status_code == 302, response.status_code

    def test_search_engines_config_page_route_exists(self, client):
        """Test search engines config page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/search_engines")
        assert response.status_code == 302, response.status_code


class TestSettingsApiRoutes:
    """Tests for settings API routes."""

    def test_api_get_all_settings_route_exists(self, client):
        """Test /api GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api")
        assert response.status_code == 401, response.status_code

    def test_api_get_categories_route_exists(self, client):
        """Test /api/categories GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/categories")
        assert response.status_code == 401, response.status_code

    def test_api_get_types_route_exists(self, client):
        """Test /api/types GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/types")
        assert response.status_code == 401, response.status_code

    def test_api_get_ui_elements_route_exists(self, client):
        """Test /api/ui_elements GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/ui_elements")
        assert response.status_code == 401, response.status_code

    def test_api_get_warnings_route_exists(self, client):
        """Test /api/warnings GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/warnings")
        assert response.status_code == 401, response.status_code


class TestSaveAllSettings:
    """Tests for save_all_settings endpoint."""

    def test_save_all_settings_requires_post(self, client):
        """Test that save_all_settings requires POST method."""
        response = client.get(f"{SETTINGS_PREFIX}/save_all_settings")
        # GET should return 405 Method Not Allowed
        assert response.status_code == 405, response.status_code

    def test_save_all_settings_requires_json(self, client):
        """Test that save_all_settings requires JSON body."""
        response = client.post(f"{SETTINGS_PREFIX}/save_all_settings")
        assert response.status_code == 302, response.status_code


class TestResetToDefaults:
    """Tests for reset_to_defaults endpoint."""

    def test_reset_to_defaults_requires_post(self, client):
        """Test that reset_to_defaults requires POST method."""
        response = client.get(f"{SETTINGS_PREFIX}/reset_to_defaults")
        # GET should return 405 Method Not Allowed
        assert response.status_code == 405, response.status_code


class TestApiImportSettings:
    """Tests for api_import_settings endpoint."""

    def test_import_settings_requires_post(self, client):
        """Test that import_settings requires POST method."""
        response = client.get(f"{SETTINGS_PREFIX}/api/import")
        # GET should return 405 Method Not Allowed
        assert response.status_code == 401, response.status_code


class TestAvailableModelsApi:
    """Tests for available models API endpoint."""

    def test_api_available_models_route_exists(self, client):
        """Test /api/available-models GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/available-models")
        assert response.status_code == 401, response.status_code


class TestAvailableSearchEnginesApi:
    """Tests for available search engines API endpoint."""

    def test_api_available_search_engines_route_exists(self, client):
        """Test /api/available-search-engines GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/available-search-engines")
        assert response.status_code == 401, response.status_code


class TestSearchFavoritesApi:
    """Tests for search favorites API endpoints."""

    def test_api_get_search_favorites_route_exists(self, client):
        """Test /api/search-favorites GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/search-favorites")
        assert response.status_code == 401, response.status_code

    def test_api_toggle_search_favorite_requires_post(self, client):
        """Test /api/search-favorites/toggle requires POST."""
        response = client.get(f"{SETTINGS_PREFIX}/api/search-favorites/toggle")
        assert response.status_code == 401, response.status_code


class TestOllamaStatusApi:
    """Tests for Ollama status API endpoint."""

    def test_api_ollama_status_route_exists(self, client):
        """Test /api/ollama-status GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/ollama-status")
        assert response.status_code == 401, response.status_code


class TestRateLimitingApi:
    """Tests for rate limiting API endpoints."""

    def test_api_rate_limiting_status_route_exists(self, client):
        """Test /api/rate-limiting/status GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/rate-limiting/status")
        assert response.status_code == 401, response.status_code

    def test_api_rate_limiting_cleanup_requires_post(self, client):
        """Test /api/rate-limiting/cleanup requires POST."""
        response = client.get(f"{SETTINGS_PREFIX}/api/rate-limiting/cleanup")
        assert response.status_code == 401, response.status_code


class TestBulkSettingsApi:
    """Tests for bulk settings API endpoint."""

    def test_api_get_bulk_settings_route_exists(self, client):
        """Test /api/bulk GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/bulk")
        assert response.status_code == 401, response.status_code


class TestDataLocationApi:
    """Tests for data location API endpoint."""

    def test_api_data_location_route_exists(self, client):
        """Test /api/data-location GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/data-location")
        assert response.status_code == 401, response.status_code


class TestNotificationTestApi:
    """Tests for notification test API endpoint."""

    def test_api_test_notification_requires_post(self, client):
        """Test /api/notifications/test-url requires POST."""
        response = client.get(f"{SETTINGS_PREFIX}/api/notifications/test-url")
        assert response.status_code == 401, response.status_code


class TestOpenFileLocation:
    """Tests for open_file_location endpoint."""

    def test_open_file_location_requires_post(self, client):
        """Test open_file_location requires POST."""
        response = client.get(f"{SETTINGS_PREFIX}/open_file_location")
        assert response.status_code == 405, response.status_code


class TestFixCorruptedSettings:
    """Tests for fix_corrupted_settings endpoint."""

    def test_fix_corrupted_settings_requires_post(self, client):
        """Test fix_corrupted_settings requires POST."""
        response = client.get(f"{SETTINGS_PREFIX}/fix_corrupted_settings")
        assert response.status_code == 405, response.status_code


# ============= Extended Tests for Phase 3.5 Coverage =============


class TestSettingsApiExtended:
    """Extended tests for settings API endpoints."""

    def test_get_setting_by_key_route(self, client):
        """Test /api/<key> GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/llm.provider")
        assert response.status_code == 401, response.status_code

    def test_set_setting_by_key_route(self, client):
        """Test /api/<key> PUT route exists."""
        response = client.put(
            f"{SETTINGS_PREFIX}/api/llm.provider",
            json={"value": "ollama"},
            content_type="application/json",
        )
        assert response.status_code == 401, response.status_code


class TestSaveAllSettingsExtended:
    """Extended tests for save_all_settings endpoint."""

    def test_save_all_settings_with_valid_json(self, client):
        """Test save_all_settings with valid JSON."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={"llm.provider": "ollama"},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code

    def test_save_all_settings_with_checkbox_values(self, client):
        """Test save_all_settings with checkbox values."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={
                "web.enable_dark_mode": True,
                "web.auto_save": False,
            },
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code

    def test_save_all_settings_with_numeric_values(self, client):
        """Test save_all_settings with numeric values."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={
                "search.iterations": 5,
                "search.questions_per_iteration": 3,
            },
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code


class TestSaveSettingsTraditionalPost:
    """Tests for traditional POST form submission."""

    def test_save_settings_form_submission(self, client):
        """Test save_settings with form data."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_settings",
            data={"llm.provider": "ollama"},
            content_type="application/x-www-form-urlencoded",
        )
        assert response.status_code == 302, response.status_code

    def test_save_settings_with_redirect(self, client):
        """Test save_settings returns redirect."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_settings",
            data={"llm.provider": "ollama"},
            content_type="application/x-www-form-urlencoded",
            follow_redirects=False,
        )
        assert response.status_code == 302, response.status_code


class TestResetToDefaultsExtended:
    """Extended tests for reset_to_defaults endpoint."""

    def test_reset_to_defaults_with_json(self, client):
        """Test reset_to_defaults with JSON body."""
        response = client.post(
            f"{SETTINGS_PREFIX}/reset_to_defaults",
            json={"confirm": True},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code


class TestExportSettings:
    """Tests for settings export endpoint."""

    def test_api_export_settings_route_exists(self, client):
        """Test /api/export GET route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/api/export")
        assert response.status_code == 401, response.status_code


class TestImportSettingsExtended:
    """Extended tests for import_settings endpoint."""

    def test_import_settings_with_json(self, client):
        """Test import_settings with JSON body."""
        response = client.post(
            f"{SETTINGS_PREFIX}/api/import",
            json={"settings": {"llm.provider": "ollama"}},
            content_type="application/json",
        )
        assert response.status_code == 401, response.status_code

    def test_import_settings_with_empty_json(self, client):
        """Test import_settings with empty JSON."""
        response = client.post(
            f"{SETTINGS_PREFIX}/api/import",
            json={},
            content_type="application/json",
        )
        assert response.status_code == 401, response.status_code


class TestValidateSettingExtended:
    """Extended tests for validate_setting function."""

    def test_validate_select_setting(self):
        """Test validating select setting."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        # Create a proper Setting object for select input
        setting = BaseSetting(
            key="test_select",
            value="option1",
            type=SettingType.APP,
            name="Test Select",
            ui_element="select",
            options=[
                {"value": "option1", "label": "Option 1"},
                {"value": "option2", "label": "Option 2"},
                {"value": "option3", "label": "Option 3"},
            ],
        )

        # Test valid option
        valid, msg = validate_setting(setting, "option2")
        assert valid is True

    def test_validate_textarea_setting(self):
        """Test validating textarea setting."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        setting = BaseSetting(
            key="test_textarea",
            value="",
            type=SettingType.APP,
            name="Test Textarea",
            ui_element="textarea",
        )

        # Test multiline text
        valid, msg = validate_setting(setting, "Line 1\nLine 2\nLine 3")
        assert valid is True

    def test_validate_password_setting(self):
        """Test validating password setting."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        setting = BaseSetting(
            key="test_password",
            value="",
            type=SettingType.APP,  # Use APP type which exists
            name="Test Password",
            ui_element="password",
        )

        valid, msg = validate_setting(setting, "secret123")
        assert valid is True


class TestSettingValueConversion:
    """Tests for setting value type handling."""

    def test_setting_accepts_int_value(self):
        """Test that integer settings accept int values."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        setting = BaseSetting(
            key="test_int",
            value=0,
            type=SettingType.APP,
            name="Test Int",
            ui_element="number",
        )

        valid, msg = validate_setting(setting, 42)
        assert valid is True

    def test_setting_accepts_bool_true(self):
        """Test that checkbox settings accept True."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        setting = BaseSetting(
            key="test_bool",
            value=False,
            type=SettingType.APP,
            name="Test Bool",
            ui_element="checkbox",
        )

        valid, msg = validate_setting(setting, True)
        assert valid is True

    def test_setting_accepts_bool_false(self):
        """Test that checkbox settings accept False."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        setting = BaseSetting(
            key="test_bool",
            value=True,
            type=SettingType.APP,
            name="Test Bool",
            ui_element="checkbox",
        )

        valid, msg = validate_setting(setting, False)
        assert valid is True

    def test_setting_accepts_float_value(self):
        """Test that number settings accept float values."""
        from local_deep_research.web.routes.settings_routes import (
            validate_setting,
        )
        from local_deep_research.web.models.settings import (
            BaseSetting,
            SettingType,
        )

        setting = BaseSetting(
            key="test_float",
            value=0.0,
            type=SettingType.APP,
            name="Test Float",
            ui_element="number",
        )

        valid, msg = validate_setting(setting, 3.14)
        assert valid is True


class TestSettingsPageRoutesExtended:
    """Extended tests for settings page routes."""

    def test_llm_config_page_route_exists(self, client):
        """Test LLM config page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/llm")
        assert response.status_code == 302, response.status_code

    def test_search_config_page_route_exists(self, client):
        """Test search config page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/search")
        assert response.status_code == 404, response.status_code

    def test_report_config_page_route_exists(self, client):
        """Test report config page route exists."""
        response = client.get(f"{SETTINGS_PREFIX}/report")
        assert response.status_code == 404, response.status_code


class TestSettingsEdgeCases:
    """Edge case tests for settings routes."""

    def test_save_settings_with_special_characters(self, client):
        """Test saving settings with special characters."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={"custom.prompt": "Test <script>alert('xss')</script>"},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code

    def test_save_settings_with_unicode(self, client):
        """Test saving settings with unicode characters."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={"custom.name": "测试设置 日本語"},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code

    def test_save_settings_with_very_long_value(self, client):
        """Test saving settings with very long value."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={"custom.text": "a" * 100000},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code

    def test_get_invalid_setting_key(self, client):
        """Test getting invalid setting key."""
        response = client.get(f"{SETTINGS_PREFIX}/api/nonexistent.setting.key")
        assert response.status_code == 401, response.status_code

    def test_save_settings_with_empty_body(self, client):
        """Test saving settings with empty body."""
        response = client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code


class TestAvailableModelsApiExtended:
    """Extended tests for available models API endpoint."""

    def test_api_available_models_with_provider(self, client):
        """Test /api/available-models with provider parameter."""
        response = client.get(
            f"{SETTINGS_PREFIX}/api/available-models?provider=ollama"
        )
        assert response.status_code == 401, response.status_code


class TestNotificationTestApiExtended:
    """Extended tests for notification test API endpoint."""

    def test_api_test_notification_with_url(self, client):
        """Test /api/notifications/test-url with valid URL."""
        response = client.post(
            f"{SETTINGS_PREFIX}/api/notifications/test-url",
            json={"service_url": "mailto://test@example.com"},
            content_type="application/json",
        )
        assert response.status_code == 401, response.status_code

    def test_api_test_notification_missing_url(self, client):
        """Test /api/notifications/test-url without URL."""
        response = client.post(
            f"{SETTINGS_PREFIX}/api/notifications/test-url",
            json={},
            content_type="application/json",
        )
        assert response.status_code == 401, response.status_code


class TestSearchFavoritesApiExtended:
    """Extended tests for search favorites API endpoints."""

    def test_toggle_search_favorite_with_data(self, client):
        """Test toggling search favorite with data."""
        response = client.post(
            f"{SETTINGS_PREFIX}/api/search-favorites/toggle",
            json={"engine": "searxng"},
            content_type="application/json",
        )
        assert response.status_code == 401, response.status_code


class TestRateLimitingApiExtended:
    """Extended tests for rate limiting API endpoints."""

    def test_api_rate_limiting_cleanup_with_confirm(self, client):
        """Test /api/rate-limiting/cleanup with confirm."""
        response = client.post(
            f"{SETTINGS_PREFIX}/api/rate-limiting/cleanup",
            json={"confirm": True},
            content_type="application/json",
        )
        assert response.status_code == 401, response.status_code


class TestOpenFileLocationExtended:
    """Extended tests for open_file_location endpoint."""

    def test_open_file_location_with_path(self, client):
        """Test open_file_location with path."""
        response = client.post(
            f"{SETTINGS_PREFIX}/open_file_location",
            json={"path": "/tmp"},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code


class TestFixCorruptedSettingsExtended:
    """Extended tests for fix_corrupted_settings endpoint."""

    def test_fix_corrupted_settings_with_confirm(self, client):
        """Test fix_corrupted_settings with confirm."""
        response = client.post(
            f"{SETTINGS_PREFIX}/fix_corrupted_settings",
            json={"confirm": True},
            content_type="application/json",
        )
        assert response.status_code == 302, response.status_code


class TestCalculateWarningsExtended:
    """Extended tests for calculate_warnings function."""

    def test_calculate_warnings_with_various_settings(self):
        """Test calculate_warnings with various settings."""
        from local_deep_research.web.warning_checks import calculate_warnings

        with patch(
            "local_deep_research.web.warning_checks.get_user_db_session"
        ) as mock_session:
            mock_ctx = MagicMock()
            mock_session.return_value.__enter__ = Mock(return_value=mock_ctx)
            mock_session.return_value.__exit__ = Mock(return_value=False)

            with patch(
                "local_deep_research.web.warning_checks.get_settings_manager"
            ) as mock_gsm:
                mock_instance = MagicMock()
                # Simulate various settings that might trigger warnings
                mock_instance.get_setting.side_effect = (
                    lambda key, default=None: {
                        "llm.provider": "none",  # No LLM configured
                        "search.tool": "",  # No search engine
                    }.get(key, default)
                )
                mock_gsm.return_value = mock_instance

                with patch(
                    "local_deep_research.web.warning_checks.session",
                    {"username": "testuser"},
                ):
                    result = calculate_warnings()

                    assert isinstance(result, list)


class TestNonEditableSettingsProtection:
    """Tests that non-editable settings cannot be modified via bulk save endpoints."""

    def test_save_all_settings_skips_non_editable(self, authenticated_client):
        """Test that save_all_settings silently skips non-editable settings like allow_registrations."""
        # First, verify the setting exists and get its current value
        get_response = authenticated_client.get(
            f"{SETTINGS_PREFIX}/api/app.allow_registrations"
        )
        assert get_response.status_code == 200
        original_value = get_response.get_json().get("value")

        # Attempt to change app.allow_registrations (non-editable)
        response = authenticated_client.post(
            f"{SETTINGS_PREFIX}/save_all_settings",
            json={
                "app.allow_registrations": not original_value,
            },
            content_type="application/json",
        )
        # Should succeed (200) — the non-editable key is silently skipped
        assert response.status_code == 200

        # Verify the non-editable setting was NOT changed
        verify_response = authenticated_client.get(
            f"{SETTINGS_PREFIX}/api/app.allow_registrations"
        )
        assert verify_response.status_code == 200
        assert verify_response.get_json()["value"] == original_value

    def test_save_settings_form_skips_non_editable(self, authenticated_client):
        """Test that save_settings (form POST) silently skips non-editable settings."""
        # Get current value
        get_response = authenticated_client.get(
            f"{SETTINGS_PREFIX}/api/app.allow_registrations"
        )
        assert get_response.status_code == 200
        original_value = get_response.get_json().get("value")

        # Attempt to change via form POST
        response = authenticated_client.post(
            f"{SETTINGS_PREFIX}/save_settings",
            data={
                "app.allow_registrations": "false"
                if original_value
                else "true",
            },
            content_type="application/x-www-form-urlencoded",
            follow_redirects=True,
        )
        # Should redirect/succeed — non-editable key is silently skipped
        assert response.status_code == 200, response.status_code

        # Verify the non-editable setting was NOT changed
        verify_response = authenticated_client.get(
            f"{SETTINGS_PREFIX}/api/app.allow_registrations"
        )
        assert verify_response.status_code == 200
        assert verify_response.get_json()["value"] == original_value

    def test_put_api_rejects_non_editable(self, authenticated_client):
        """Test that PUT /api/<key> returns 403 for non-editable settings (existing behavior)."""
        response = authenticated_client.put(
            f"{SETTINGS_PREFIX}/api/app.allow_registrations",
            json={"value": False},
            content_type="application/json",
        )
        assert response.status_code == 403

    def test_delete_api_rejects_non_editable(self, authenticated_client):
        """Test that DELETE /api/<key> returns 403 for non-editable settings."""
        response = authenticated_client.delete(
            f"{SETTINGS_PREFIX}/api/app.allow_registrations"
        )
        assert response.status_code == 403

        # Verify the setting still exists
        get_response = authenticated_client.get(
            f"{SETTINGS_PREFIX}/api/app.allow_registrations"
        )
        assert get_response.status_code == 200


class TestCoerceSettingForWrite:
    """Unit tests for coerce_setting_for_write() helper."""

    def test_passes_check_env_false(self):
        """Verify check_env=False is always passed to get_typed_setting_value."""
        with patch(
            "local_deep_research.web.routes.settings_routes.get_typed_setting_value",
            return_value=42,
        ) as mock_gtsv:
            from local_deep_research.web.routes.settings_routes import (
                coerce_setting_for_write,
            )

            result = coerce_setting_for_write("some.key", "42", "number")

            mock_gtsv.assert_called_once_with(
                key="some.key",
                value="42",
                ui_element="number",
                default=None,
                check_env=False,
            )
            assert result == 42

    def test_passes_default_none(self):
        """Verify default=None is always passed to get_typed_setting_value."""
        with patch(
            "local_deep_research.web.routes.settings_routes.get_typed_setting_value",
            return_value=True,
        ) as mock_gtsv:
            from local_deep_research.web.routes.settings_routes import (
                coerce_setting_for_write,
            )

            result = coerce_setting_for_write("app.flag", "true", "checkbox")

            assert mock_gtsv.call_args.kwargs["default"] is None
            assert mock_gtsv.call_args.kwargs["check_env"] is False
            assert result is True

    def test_returns_get_typed_setting_value_result(self):
        """Verify helper returns exactly what get_typed_setting_value returns."""
        sentinel = object()
        with patch(
            "local_deep_research.web.routes.settings_routes.get_typed_setting_value",
            return_value=sentinel,
        ):
            from local_deep_research.web.routes.settings_routes import (
                coerce_setting_for_write,
            )

            result = coerce_setting_for_write("k", "v", "text")
            assert result is sentinel


class TestApiUpdateSettingTypeConversion:
    """Integration tests: PUT /api/<key> round-trip type conversion."""

    @staticmethod
    def _find_editable_setting(authenticated_client, ui_element):
        """Find an editable setting with the given ui_element by scanning /api.

        Returns (key, original_data) or (None, None) if not found.
        Uses GET /settings/api to list all keys, then probes candidates
        via GET /settings/api/<key> to check ui_element and editable.
        """
        resp = authenticated_client.get(f"{SETTINGS_PREFIX}/api")
        assert resp.status_code == 200
        all_keys = list(resp.get_json().get("settings", {}).keys())

        for key in all_keys:
            detail_resp = authenticated_client.get(
                f"{SETTINGS_PREFIX}/api/{key}"
            )
            if detail_resp.status_code != 200:
                continue
            data = detail_resp.get_json()
            if (
                data.get("ui_element") == ui_element
                and data.get("editable") is True
            ):
                return key, data
        return None, None

    def test_put_string_number_stored_as_int(self, authenticated_client):
        """PUT string "999" for number setting -> GET returns int 999."""
        key, original = self._find_editable_setting(
            authenticated_client, "number"
        )
        if key is None:
            pytest.skip("No editable number setting found in test database")

        # PUT a string number
        put_resp = authenticated_client.put(
            f"{SETTINGS_PREFIX}/api/{key}",
            json={"value": "999"},
            content_type="application/json",
        )
        assert put_resp.status_code == 200

        # GET should return int, not string
        verify_resp = authenticated_client.get(f"{SETTINGS_PREFIX}/api/{key}")
        assert verify_resp.status_code == 200
        stored_value = verify_resp.get_json()["value"]
        assert stored_value == 999
        assert isinstance(stored_value, int)

        # Restore original value
        authenticated_client.put(
            f"{SETTINGS_PREFIX}/api/{key}",
            json={"value": original["value"]},
            content_type="application/json",
        )

    def test_put_string_bool_stored_as_bool(self, authenticated_client):
        """PUT string "true" for checkbox setting -> GET returns bool True."""
        key, original = self._find_editable_setting(
            authenticated_client, "checkbox"
        )
        if key is None:
            pytest.skip("No editable checkbox setting found in test database")

        # PUT string "true"
        put_resp = authenticated_client.put(
            f"{SETTINGS_PREFIX}/api/{key}",
            json={"value": "true"},
            content_type="application/json",
        )
        assert put_resp.status_code == 200

        # GET should return bool, not string
        verify_resp = authenticated_client.get(f"{SETTINGS_PREFIX}/api/{key}")
        assert verify_resp.status_code == 200
        stored_value = verify_resp.get_json()["value"]
        assert stored_value is True
        assert isinstance(stored_value, bool)

        # Restore original value
        authenticated_client.put(
            f"{SETTINGS_PREFIX}/api/{key}",
            json={"value": original["value"]},
            content_type="application/json",
        )
