"""
Extended tests for settings/manager.py

Tests cover additional functionality not in the main test file:
- Type conversion edge cases (get_bool_setting, various ui_elements)
- Environment variable edge cases
- Import/export settings
- Version checking
- Default settings loading
- create_setting helper
- WebSocket event emission
- Nested settings paths
- Password/sensitive field handling
"""

import os
import json
from unittest.mock import Mock, MagicMock, patch


class MockSetting:
    """Mock Setting model for testing."""

    def __init__(
        self,
        key,
        value,
        ui_element="text",
        editable=True,
        type=None,
        name=None,
        description=None,
        category=None,
        options=None,
        min_value=None,
        max_value=None,
        step=None,
        visible=True,
    ):
        self.key = key
        self.value = value
        self.ui_element = ui_element
        self.editable = editable
        self.type = type or Mock(name="APP")
        self.name = name or key
        self.description = description or ""
        self.category = category
        self.options = options
        self.min_value = min_value
        self.max_value = max_value
        self.step = step
        self.visible = visible
        self.updated_at = None


class TestGetBoolSetting:
    """Tests for get_bool_setting method."""

    def test_get_bool_setting_true_string(self):
        """get_bool_setting converts 'true' string to True."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.debug", value="true", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_bool_setting("app.debug", check_env=False)

        assert result is True

    def test_get_bool_setting_false_string(self):
        """get_bool_setting converts 'false' string to False."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.debug", value="false", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_bool_setting("app.debug", check_env=False)

        assert result is False

    def test_get_bool_setting_1_string(self):
        """get_bool_setting converts '1' string to True."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.debug", value="1", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_bool_setting("app.debug", check_env=False)

        assert result is True

    def test_get_bool_setting_0_string(self):
        """get_bool_setting converts '0' string to False."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.debug", value="0", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_bool_setting("app.debug", check_env=False)

        assert result is False

    def test_get_bool_setting_boolean_true(self):
        """get_bool_setting handles actual boolean True."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.debug", value=True, ui_element="checkbox"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_bool_setting("app.debug", check_env=False)

        assert result is True

    def test_get_bool_setting_returns_default(self):
        """get_bool_setting returns default when setting not found."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_bool_setting("app.nonexistent", default=True)

        assert result is True

    def test_get_bool_setting_yes_string(self):
        """get_bool_setting converts 'yes' string to True."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.enabled", value="yes", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_bool_setting("app.enabled", check_env=False)

        assert result is True


class TestUIElementTypeConversion:
    """Tests for UI element type conversion."""

    def test_checkbox_returns_bool(self):
        """Checkbox ui_element returns boolean."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.debug", value=True, ui_element="checkbox"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("app.debug", check_env=False)

        assert isinstance(result, bool)

    def test_number_returns_float(self):
        """Number ui_element returns float."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.temperature", value=0.7, ui_element="number"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("llm.temperature", check_env=False)

        assert isinstance(result, float)

    def test_range_returns_float(self):
        """Range ui_element returns float."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.top_p", value=0.9, ui_element="range"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("llm.top_p", check_env=False)

        assert isinstance(result, float)

    def test_text_returns_string(self):
        """Text ui_element returns string."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.model", value="gpt-4", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("llm.model", check_env=False)

        assert isinstance(result, str)

    def test_password_returns_string(self):
        """Password ui_element returns string."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.api_key", value="secret123", ui_element="password"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("llm.api_key", check_env=False)

        assert isinstance(result, str)

    def test_select_returns_string(self):
        """Select ui_element returns string."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.theme", value="dark", ui_element="select"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("app.theme", check_env=False)

        assert isinstance(result, str)

    def test_json_returns_as_is(self):
        """JSON ui_element returns value as-is."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        json_value = {"key": "value", "nested": {"data": 123}}
        mock_setting = MockSetting(
            key="app.config", value=json_value, ui_element="json"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("app.config", check_env=False)

        assert result == json_value

    def test_unknown_ui_element_returns_default(self):
        """Unknown ui_element returns default value."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.unknown", value="test", ui_element="unknown_type"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting(
            "app.unknown", default="default_val", check_env=False
        )

        assert result == "default_val"


class TestEnvironmentVariableEdgeCases:
    """Tests for environment variable handling edge cases."""

    def test_env_override_invalid_number(self):
        """Invalid number in env var falls back to DB value."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.temperature", value=0.7, ui_element="number"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        os.environ["LDR_LLM_TEMPERATURE"] = "not_a_number"
        try:
            manager = SettingsManager(db_session=mock_session)
            result = manager.get_setting("llm.temperature", check_env=True)

            # Should fall back to DB value
            assert result == 0.7
        finally:
            del os.environ["LDR_LLM_TEMPERATURE"]

    def test_env_override_empty_string(self):
        """Empty string env var is treated as unset (Docker/Unraid templates create blank entries)."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.model", value="gpt-4", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        os.environ["LDR_LLM_MODEL"] = ""
        try:
            manager = SettingsManager(db_session=mock_session)
            result = manager.get_setting("llm.model", check_env=True)

            # Empty env var should NOT override the DB value
            assert result == "gpt-4"
        finally:
            del os.environ["LDR_LLM_MODEL"]

    def test_env_override_empty_string_no_db_value(self):
        """Empty env var with no DB value falls through to default.

        This is the first-launch Unraid scenario: env vars are empty strings,
        no DB row exists yet, so the default should be returned.
        """
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        # No DB rows match this key
        mock_query.filter.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        os.environ["LDR_LLM_PROVIDER"] = ""
        try:
            manager = SettingsManager(db_session=mock_session)
            result = manager.get_setting(
                "llm.provider", default="openai", check_env=True
            )
            assert result == "openai"
        finally:
            del os.environ["LDR_LLM_PROVIDER"]

    def test_env_override_empty_string_checkbox(self):
        """Empty env var for checkbox setting falls through to DB value."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.debug", value=True, ui_element="checkbox"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        os.environ["LDR_APP_DEBUG"] = ""
        try:
            manager = SettingsManager(db_session=mock_session)
            result = manager.get_setting("app.debug", check_env=True)
            assert result is True
        finally:
            del os.environ["LDR_APP_DEBUG"]

    def test_env_override_empty_string_number(self):
        """Empty env var for number setting falls through to DB value."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.temperature", value=0.7, ui_element="number"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        os.environ["LDR_LLM_TEMPERATURE"] = ""
        try:
            manager = SettingsManager(db_session=mock_session)
            result = manager.get_setting("llm.temperature", check_env=True)
            assert result == 0.7
        finally:
            del os.environ["LDR_LLM_TEMPERATURE"]

    def test_env_override_with_nested_key(self):
        """Nested key with multiple dots converts correctly."""
        from local_deep_research.settings.manager import (
            check_env_setting,
        )

        os.environ["LDR_SEARCH_TOOL_MAX_RESULTS"] = "50"
        try:
            result = check_env_setting("search.tool.max_results")
            assert result == "50"
        finally:
            del os.environ["LDR_SEARCH_TOOL_MAX_RESULTS"]


class TestNestedSettingsPaths:
    """Tests for nested settings path handling."""

    def test_get_setting_returns_nested_dict(self):
        """Getting higher-level key returns nested dict."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_settings = [
            MockSetting(key="llm.model", value="gpt-4", ui_element="text"),
            MockSetting(key="llm.temperature", value=0.7, ui_element="number"),
        ]
        mock_query.filter.return_value.all.return_value = mock_settings
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("llm", check_env=False)

        assert isinstance(result, dict)
        assert "model" in result
        assert "temperature" in result

    def test_get_setting_exact_match_single(self):
        """Getting exact key returns single value."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="llm.model", value="gpt-4", ui_element="text"
        )
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_setting("llm.model", check_env=False)

        assert result == "gpt-4"


class TestImportExportSettings:
    """Tests for import/export functionality."""

    def test_import_settings_basic(self):
        """import_settings loads settings from dict."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.delete.return_value = 1
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        settings_data = {
            "app.debug": {
                "value": True,
                "type": "APP",
                "name": "Debug",
                "ui_element": "checkbox",
            },
        }

        with patch.object(manager, "_emit_settings_changed"):
            manager.import_settings(settings_data)

        mock_session.add.assert_called()
        mock_session.commit.assert_called()

    def test_import_settings_no_overwrite(self):
        """import_settings preserves existing values when overwrite=False."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.delete.return_value = 0
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        manager.get_setting = Mock(return_value="existing_value")

        settings_data = {
            "app.key": {
                "value": "new_value",
                "type": "APP",
                "name": "Key",
                "ui_element": "text",
            },
        }

        with patch.object(manager, "_emit_settings_changed"):
            manager.import_settings(settings_data, overwrite=False)

        # Check that existing value was preserved
        added_setting = mock_session.add.call_args[0][0]
        assert added_setting.value == "existing_value"

    def test_import_settings_delete_extra(self):
        """import_settings deletes extra settings when delete_extra=True."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.delete.return_value = 1
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        # Mock get_all_settings to return extra setting
        manager.get_all_settings = Mock(
            return_value={
                "app.keep": {"value": True},
                "app.extra": {"value": False},
            }
        )
        manager.delete_setting = Mock(return_value=True)

        settings_data = {
            "app.keep": {
                "value": True,
                "type": "APP",
                "name": "Keep",
                "ui_element": "checkbox",
            },
        }

        with patch.object(manager, "_emit_settings_changed"):
            manager.import_settings(settings_data, delete_extra=True)

        # Should have deleted the extra setting
        manager.delete_setting.assert_any_call("app.extra", commit=False)


class TestLoadFromDefaultsFile:
    """Tests for load_from_defaults_file method."""

    def test_load_from_defaults_file_calls_import(self):
        """load_from_defaults_file calls import_settings."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        manager = SettingsManager(db_session=mock_session)

        # Mock import_settings and default_settings
        manager.import_settings = Mock()
        manager.__dict__.pop("default_settings", None)

        with patch.object(
            type(manager),
            "default_settings",
            new_callable=lambda: property(
                lambda self: {"app.test": {"value": True}}
            ),
        ):
            manager.load_from_defaults_file()

        manager.import_settings.assert_called_once()


class TestVersionChecking:
    """Tests for version checking functionality."""

    def test_update_db_version(self):
        """update_db_version updates the version in DB."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        from local_deep_research.__version__ import __version__

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.delete.return_value = 1
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        manager.update_db_version()

        mock_session.add.assert_called_once()
        added_setting = mock_session.add.call_args[0][0]
        assert added_setting.value == __version__
        mock_session.commit.assert_called_once()


class TestWebSocketEmission:
    """Tests for WebSocket event emission."""

    @staticmethod
    def _request_ctx(username="alice"):
        """A Flask request context with ``username`` in the session, so
        _emit_settings_changed can resolve the owning user to scope the emit."""
        from flask import Flask, session

        app = Flask(__name__)
        app.secret_key = "test-secret"
        ctx = app.test_request_context()
        ctx.push()
        if username is not None:
            session["username"] = username
        return ctx

    def _patched_socket(self, mock_socket_instance):
        # Give user_room a realistic return so room scoping can be asserted.
        mock_socket_instance.user_room.side_effect = lambda u: f"user:{u}"
        return patch.dict(
            "sys.modules",
            {
                "local_deep_research.web.services.socket_service": MagicMock(
                    SocketIOService=Mock(return_value=mock_socket_instance)
                )
            },
        )

    @staticmethod
    def _working_manager():
        """A SettingsManager whose get_setting("app.debug") resolves to a real
        value, so the only thing that can suppress the emit is the scoping
        guard under test — not an incidental failure reading the setting."""
        from local_deep_research.settings.manager import SettingsManager

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.all.return_value = [
            MockSetting(key="app.debug", value=True)
        ]
        mock_session.query.return_value = mock_query
        return SettingsManager(db_session=mock_session)

    def test_emit_settings_changed_scoped_to_owning_user_room(self):
        """The event is emitted only to the changing user's per-user room —
        never broadcast — so one user's setting values (incl. plaintext API
        keys) never reach other connected clients."""
        from local_deep_research.settings.manager import SettingsManager

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(key="app.debug", value=True)
        mock_query.filter.return_value.all.return_value = [mock_setting]
        mock_session.query.return_value = mock_query

        mock_socket_instance = Mock()
        manager = SettingsManager(db_session=mock_session)

        ctx = self._request_ctx(username="alice")
        try:
            with self._patched_socket(mock_socket_instance):
                manager._emit_settings_changed(["app.debug"])
        finally:
            ctx.pop()

        mock_socket_instance.emit_socket_event.assert_called_once()
        args, kwargs = mock_socket_instance.emit_socket_event.call_args
        assert args[0] == "settings_changed"
        assert "app.debug" in args[1]["changed_keys"]
        # The targeting room must be the owning user's room.
        assert kwargs.get("room") == "user:alice"

    def test_emit_settings_changed_skipped_without_request_context(self):
        """A settings change outside any request (start-up defaults, background
        workers, migrations) has no user tab to notify, so nothing is emitted
        — crucially, it must not fall back to a cross-user broadcast."""
        mock_socket_instance = Mock()
        manager = self._working_manager()

        # No request context pushed.
        with self._patched_socket(mock_socket_instance):
            manager._emit_settings_changed(["app.debug"])

        mock_socket_instance.emit_socket_event.assert_not_called()

    def test_emit_settings_changed_skipped_when_no_username(self):
        """A request without an authenticated username emits nothing."""
        mock_socket_instance = Mock()
        manager = self._working_manager()

        ctx = self._request_ctx(username=None)
        try:
            with self._patched_socket(mock_socket_instance):
                manager._emit_settings_changed(["app.debug"])
        finally:
            ctx.pop()

        mock_socket_instance.emit_socket_event.assert_not_called()

    def test_emit_settings_changed_handles_exception(self):
        """_emit_settings_changed handles exceptions gracefully."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        manager = SettingsManager(db_session=mock_session)

        # Mock SocketIOService to raise ValueError
        mock_socket_module = MagicMock()
        mock_socket_module.SocketIOService.side_effect = ValueError(
            "Not initialized"
        )

        with patch.dict(
            "sys.modules",
            {
                "local_deep_research.web.services.socket_service": mock_socket_module
            },
        ):
            # Should not raise
            manager._emit_settings_changed(["app.debug"])


class TestDefaultSettingsProperty:
    """Tests for default_settings property."""

    @patch("local_deep_research.settings.manager.defaults")
    def test_default_settings_loads_json_files(self, mock_defaults):
        """default_settings loads JSON files from defaults directory."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        import tempfile

        with tempfile.TemporaryDirectory() as tmpdir:
            # Create mock defaults path
            from pathlib import Path

            mock_defaults.__file__ = str(Path(tmpdir) / "__init__.py")

            # Create a test JSON file
            json_file = Path(tmpdir) / "test_settings.json"
            json_file.write_text(
                json.dumps(
                    {
                        "test.setting": {
                            "value": "test_value",
                            "type": "APP",
                            "name": "Test",
                            "ui_element": "text",
                        }
                    }
                )
            )

            manager = SettingsManager()
            settings = manager.default_settings

            assert "test.setting" in settings

    def test_default_settings_handles_invalid_json(self):
        """default_settings handles invalid JSON gracefully."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        manager = SettingsManager()

        # Should not raise even if there are issues
        settings = manager.default_settings
        assert isinstance(settings, dict)


class TestSettingTypeDetermination:
    """Tests for automatic setting type determination."""

    def test_set_setting_determines_llm_type(self):
        """set_setting creates LLM type for llm. keys."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        from local_deep_research.database.models import SettingType

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "_emit_settings_changed"):
            manager.set_setting("llm.new_setting", "value")

        added_setting = mock_session.add.call_args[0][0]
        assert added_setting.type == SettingType.LLM

    def test_set_setting_determines_search_type(self):
        """set_setting creates SEARCH type for search. keys."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        from local_deep_research.database.models import SettingType

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "_emit_settings_changed"):
            manager.set_setting("search.new_setting", "value")

        added_setting = mock_session.add.call_args[0][0]
        assert added_setting.type == SettingType.SEARCH

    def test_set_setting_determines_report_type(self):
        """set_setting creates REPORT type for report. keys."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        from local_deep_research.database.models import SettingType

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "_emit_settings_changed"):
            manager.set_setting("report.new_setting", "value")

        added_setting = mock_session.add.call_args[0][0]
        assert added_setting.type == SettingType.REPORT

    def test_set_setting_defaults_to_app_type(self):
        """set_setting creates APP type for other keys."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        from local_deep_research.database.models import SettingType

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "_emit_settings_changed"):
            manager.set_setting("other.new_setting", "value")

        added_setting = mock_session.add.call_args[0][0]
        assert added_setting.type == SettingType.APP


class TestCreateOrUpdateSettingTypes:
    """Tests for create_or_update_setting type handling."""

    def test_create_or_update_with_llm_setting_class(self):
        """create_or_update_setting handles LLMSetting class."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "_emit_settings_changed"):
            result = manager.create_or_update_setting(
                {
                    "key": "llm.model",
                    "value": "gpt-4",
                    "name": "Model",
                    "description": "LLM model",
                }
            )

        assert result is not None

    def test_create_or_update_with_search_setting_class(self):
        """create_or_update_setting handles SearchSetting class."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_query.filter.return_value.first.return_value = None
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "_emit_settings_changed"):
            result = manager.create_or_update_setting(
                {
                    "key": "search.max_results",
                    "value": 10,
                    "name": "Max Results",
                    "description": "Maximum results",
                }
            )

        assert result is not None

    def test_create_or_update_with_non_editable_returns_none(self):
        """create_or_update_setting returns None for non-editable setting."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        existing_setting = MockSetting(
            key="app.version", value="1.0", editable=False
        )
        mock_query.filter.return_value.first.return_value = existing_setting
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        result = manager.create_or_update_setting(
            {"key": "app.version", "value": "2.0", "name": "Version"}
        )

        assert result is None


class TestEdgeCases:
    """Tests for edge cases and boundary conditions."""

    def test_get_setting_without_db_uses_defaults(self):
        """get_setting uses defaults when no DB session."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        manager = SettingsManager()  # No db_session

        # Should use default_settings
        result = manager.get_setting("app.nonexistent", default="fallback")

        # Returns default since setting likely doesn't exist
        assert result == "fallback"

    def test_get_all_settings_marks_locked_non_editable(self):
        """get_all_settings marks settings non-editable when locked."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_settings = [
            MockSetting(key="app.debug", value=True, editable=True),
        ]
        mock_query.all.return_value = mock_settings
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        manager._SettingsManager__settings_locked = True

        result = manager.get_all_settings()

        assert result["app.debug"]["editable"] is False

    def test_db_error_returns_default(self):
        """Database error returns default value."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        from sqlalchemy.exc import SQLAlchemyError

        mock_session = Mock()
        # Let __init__'s _ensure_settings_initialized() succeed
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        # Now make query raise for get_setting
        mock_session.query.side_effect = SQLAlchemyError("DB Error")
        result = manager.get_setting("app.test", default="default")

        assert result == "default"

    def test_thread_safety_no_db_passes(self):
        """Thread safety check passes without DB session."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )

        manager = SettingsManager()  # No db_session

        # Should not raise even from different thread concept
        manager._check_thread_safety()

    def test_set_setting_rollback_on_error(self):
        """set_setting calls rollback on SQLAlchemy error."""
        from local_deep_research.settings.manager import (
            SettingsManager,
        )
        from sqlalchemy.exc import SQLAlchemyError

        mock_session = Mock()
        mock_session.commit.side_effect = SQLAlchemyError("Commit failed")
        mock_query = Mock()
        mock_query.filter.return_value.first.return_value = MockSetting(
            key="app.test", value="old"
        )
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.set_setting("app.test", "new")

        assert result is False
        mock_session.rollback.assert_called_once()
