"""
Tests for the SettingsManager class.

Tests cover:
- Initialization
- Getting settings
- Setting values
- Thread safety checks
- Default settings loading
- Environment variable overrides
"""

import os
import threading
from unittest.mock import Mock, 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 TestSettingsManagerInit:
    """Tests for SettingsManager initialization."""

    def test_init_stores_db_session(self):
        """SettingsManager stores the database session."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

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

        assert manager.db_session is mock_session

    def test_init_without_db_session(self):
        """SettingsManager can be initialized without db session."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        manager = SettingsManager()

        assert manager.db_session is None

    def test_init_stores_thread_id(self):
        """SettingsManager stores creation thread ID."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        manager = SettingsManager()

        assert manager._creation_thread_id == threading.get_ident()


class TestCheckEnvSetting:
    """Tests for check_env_setting function."""

    def test_check_env_setting_found(self):
        """check_env_setting returns value when env var is set."""
        from local_deep_research.web.services.settings_manager import (
            check_env_setting,
        )

        # Set environment variable
        os.environ["LDR_APP_DEBUG"] = "true"
        try:
            result = check_env_setting("app.debug")
            assert result == "true"
        finally:
            del os.environ["LDR_APP_DEBUG"]

    def test_check_env_setting_not_found(self):
        """check_env_setting returns None when env var not set."""
        from local_deep_research.web.services.settings_manager import (
            check_env_setting,
        )

        # Make sure env var is not set
        if "LDR_APP_NONEXISTENT" in os.environ:
            del os.environ["LDR_APP_NONEXISTENT"]

        result = check_env_setting("app.nonexistent")
        assert result is None

    def test_check_env_setting_converts_dots_to_underscores(self):
        """check_env_setting converts dots to underscores in key."""
        from local_deep_research.web.services.settings_manager import (
            check_env_setting,
        )

        os.environ["LDR_LLM_MODEL_NAME"] = "test-model"
        try:
            result = check_env_setting("llm.model.name")
            assert result == "test-model"
        finally:
            del os.environ["LDR_LLM_MODEL_NAME"]


class TestSettingsManagerGetSetting:
    """Tests for SettingsManager.get_setting method."""

    def test_get_setting_from_db(self):
        """get_setting retrieves value from database."""
        from local_deep_research.web.services.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 result is True

    def test_get_setting_returns_default_when_not_found(self):
        """get_setting returns default when setting not in database."""
        from local_deep_research.web.services.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_setting("app.nonexistent", default="default_value")

        assert result == "default_value"

    def test_get_setting_with_env_override(self):
        """get_setting uses environment variable when available."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

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

        os.environ["LDR_APP_TEST_VALUE"] = "env_value"
        try:
            manager = SettingsManager(db_session=mock_session)
            result = manager.get_setting("app.test_value", check_env=True)

            assert result == "env_value"
        finally:
            del os.environ["LDR_APP_TEST_VALUE"]

    def test_get_setting_number_type(self):
        """get_setting correctly converts number types."""
        from local_deep_research.web.services.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 result == 0.7
        assert isinstance(result, float)


class TestSettingsManagerSetSetting:
    """Tests for SettingsManager.set_setting method."""

    def test_set_setting_updates_existing(self):
        """set_setting updates existing setting in database."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(key="app.debug", value=False, editable=True)
        mock_query.filter.return_value.first.return_value = mock_setting
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "_emit_settings_changed"):
            result = manager.set_setting("app.debug", True)

        assert result is True
        assert mock_setting.value is True
        mock_session.commit.assert_called_once()

    def test_set_setting_fails_without_db_session(self):
        """set_setting returns False when no db session."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        manager = SettingsManager()
        result = manager.set_setting("app.debug", True)

        assert result is False

    def test_set_setting_fails_for_non_editable(self):
        """set_setting returns False for non-editable settings."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

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

        manager = SettingsManager(db_session=mock_session)
        result = manager.set_setting("app.version", "2.0")

        assert result is False

    def test_set_setting_creates_new(self):
        """set_setting creates new setting when not exists."""
        from local_deep_research.web.services.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.set_setting("app.new_setting", "value")

        assert result is True
        mock_session.add.assert_called_once()


class TestSettingsManagerThreadSafety:
    """Tests for SettingsManager thread safety."""

    def test_thread_safety_check_same_thread(self):
        """_check_thread_safety passes in same thread."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

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

        # Should not raise
        manager._check_thread_safety()

    def test_thread_safety_check_different_thread(self):
        """_check_thread_safety raises in different thread."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

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

        error_raised = [False]

        def run_in_thread():
            try:
                manager._check_thread_safety()
            except RuntimeError as e:
                if "not thread-safe" in str(e):
                    error_raised[0] = True

        thread = threading.Thread(target=run_in_thread)
        thread.start()
        thread.join()

        assert error_raised[0] is True


class TestSettingsManagerGetAllSettings:
    """Tests for SettingsManager.get_all_settings method."""

    def test_get_all_settings_returns_dict(self):
        """get_all_settings returns dictionary of all settings."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_settings = [
            MockSetting(key="app.debug", value=True, ui_element="checkbox"),
            MockSetting(key="llm.model", value="gpt-4", ui_element="text"),
        ]
        mock_query.all.return_value = mock_settings
        mock_session.query.return_value = mock_query

        manager = SettingsManager(db_session=mock_session)
        result = manager.get_all_settings()

        assert "app.debug" in result
        assert "llm.model" in result
        assert result["app.debug"]["value"] is True
        assert result["llm.model"]["value"] == "gpt-4"


class TestSettingsManagerDeleteSetting:
    """Tests for SettingsManager.delete_setting method."""

    def test_delete_setting_success(self):
        """delete_setting removes setting from database."""
        from local_deep_research.web.services.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)
        result = manager.delete_setting("app.old_setting")

        assert result is True
        mock_session.commit.assert_called_once()

    def test_delete_setting_not_found(self):
        """delete_setting returns False when setting not found."""
        from local_deep_research.web.services.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)
        result = manager.delete_setting("app.nonexistent")

        assert result is False

    def test_delete_setting_fails_without_db(self):
        """delete_setting returns False without db session."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        manager = SettingsManager()
        result = manager.delete_setting("app.setting")

        assert result is False


class TestSettingsManagerLocking:
    """Tests for settings locking functionality."""

    def test_settings_locked_property(self):
        """settings_locked returns lock status."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.lock_settings", 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)

        # Access property - should query and cache
        assert manager.settings_locked is True

    def test_set_setting_blocked_when_locked(self):
        """set_setting returns False when settings are locked."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

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

        # Force settings to be locked
        manager._SettingsManager__settings_locked = True

        result = manager.set_setting("app.debug", True)

        assert result is False


class TestSettingsManagerCreateOrUpdate:
    """Tests for SettingsManager.create_or_update_setting method."""

    def test_create_setting_with_dict(self):
        """create_or_update_setting accepts dictionary input."""
        from local_deep_research.web.services.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": "app.new_setting",
                    "value": "test_value",
                    "name": "New Setting",
                    "description": "A new setting",
                }
            )

        assert result is not None
        mock_session.add.assert_called_once()

    def test_update_existing_setting(self):
        """create_or_update_setting updates existing setting."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.existing", value="old_value", editable=True
        )
        mock_query.filter.return_value.first.return_value = mock_setting
        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": "app.existing",
                    "value": "new_value",
                    "name": "Existing Setting",
                    "description": "Updated",
                }
            )

        assert result is not None
        assert mock_setting.value == "new_value"

    def test_create_or_update_fails_without_db(self):
        """create_or_update_setting returns None without db session."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        manager = SettingsManager()
        result = manager.create_or_update_setting(
            {"key": "app.test", "value": "value"}
        )

        assert result is None


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

    def test_db_version_matches_package_true(self):
        """db_version_matches_package returns True when versions match."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )
        from local_deep_research.__version__ import __version__

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.version", value=__version__, 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.db_version_matches_package()

        assert result is True

    def test_db_version_matches_package_false(self):
        """db_version_matches_package returns False when versions differ."""
        from local_deep_research.web.services.settings_manager import (
            SettingsManager,
        )

        mock_session = Mock()
        mock_query = Mock()
        mock_setting = MockSetting(
            key="app.version", value="0.0.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.db_version_matches_package()

        assert result is False
