"""
Comprehensive tests for SettingsManager.

Tests cover:
- Thread safety mechanisms
- Settings locking behavior
- get_setting functionality with various scenarios
- set_setting operations
- Import/export functionality
- Version management
- Static helper methods
"""

import os
import threading
import pytest
from unittest.mock import MagicMock, patch, PropertyMock

from local_deep_research.settings.manager import (
    SettingsManager,
    get_typed_setting_value,
    check_env_setting,
    _parse_number,
)


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

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        original_env = {
            k: v for k, v in os.environ.items() if k.startswith("LDR_")
        }
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        yield
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        for key, value in original_env.items():
            os.environ[key] = value

    def test_check_thread_safety_same_thread_passes(self):
        """Test that thread safety check passes when used in creation thread."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        # Should not raise when used in same thread
        manager._check_thread_safety()

    def test_check_thread_safety_different_thread_raises(self):
        """Test that thread safety check raises RuntimeError when used across threads."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        exception_raised = None

        def use_in_different_thread():
            nonlocal exception_raised
            try:
                manager._check_thread_safety()
            except RuntimeError as e:
                exception_raised = e

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

        assert exception_raised is not None
        assert "thread-safe" in str(exception_raised).lower()

    def test_check_thread_safety_no_session_skips_check(self):
        """Test that thread safety check is skipped without DB session."""
        manager = SettingsManager(db_session=None)

        # Should not raise even if called from different thread
        # because there's no db_session
        def use_in_different_thread():
            manager._check_thread_safety()  # Should not raise

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

    def test_settings_manager_thread_id_tracking(self):
        """Test that SettingsManager tracks creation thread ID."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        assert hasattr(manager, "_creation_thread_id")
        assert manager._creation_thread_id == threading.get_ident()

    def test_concurrent_access_from_multiple_threads(self):
        """Test that concurrent access from multiple threads raises errors."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        errors = []

        def access_from_thread():
            try:
                manager._check_thread_safety()
            except RuntimeError as e:
                errors.append(e)

        threads = [
            threading.Thread(target=access_from_thread) for _ in range(3)
        ]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        # All 3 threads should have raised errors
        assert len(errors) == 3


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

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        original_env = {
            k: v for k, v in os.environ.items() if k.startswith("LDR_")
        }
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        yield
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        for key, value in original_env.items():
            os.environ[key] = value

    def test_settings_locked_property_returns_false_when_unlocked(self):
        """Test settings_locked returns False by default."""
        manager = SettingsManager(db_session=None)

        # Manually set the private attribute to test
        manager._SettingsManager__settings_locked = False

        assert manager.settings_locked is False

    def test_settings_locked_property_returns_true_when_locked(self):
        """Test settings_locked returns True when app.lock_settings is True."""
        manager = SettingsManager(db_session=None)

        # Manually set the private attribute
        manager._SettingsManager__settings_locked = True

        assert manager.settings_locked is True

    def test_settings_locked_cached_after_first_check(self):
        """Test that settings_locked value is cached after first evaluation."""
        manager = SettingsManager(db_session=None)

        # Initially None
        assert manager._SettingsManager__settings_locked is None

        # After accessing, should be set
        with patch.object(manager, "get_setting", return_value=False):
            _ = manager.settings_locked

        # Now should be cached
        assert manager._SettingsManager__settings_locked is False

    def test_set_setting_blocked_when_locked(self):
        """Test that set_setting returns False when settings are locked."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

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

        result = manager.set_setting("test.key", "value")

        assert result is False

    def test_create_or_update_setting_blocked_when_locked(self):
        """Test that create_or_update_setting returns None when settings are locked."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

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

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

        assert result is None

    def test_settings_locked_exception_handling(self):
        """Test that settings_locked returns False on error."""
        manager = SettingsManager(db_session=None)

        # Force an exception during get_setting
        with patch.object(
            manager, "get_setting", side_effect=Exception("Test error")
        ):
            # Reset to force re-evaluation
            manager._SettingsManager__settings_locked = None

            result = manager.settings_locked

        assert result is False


class TestSettingsManagerGetSetting:
    """Tests for get_setting functionality."""

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        original_env = {
            k: v for k, v in os.environ.items() if k.startswith("LDR_")
        }
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        yield
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        for key, value in original_env.items():
            os.environ[key] = value

    def test_get_setting_returns_default_when_not_found(self):
        """Test that get_setting returns default when key not found."""
        manager = SettingsManager(db_session=None)

        result = manager.get_setting("nonexistent.key", default="fallback")

        assert result == "fallback"

    def test_get_setting_env_override_takes_priority(self):
        """Test that environment variable overrides DB value."""
        os.environ["LDR_APP_DEBUG"] = "true"

        mock_session = MagicMock()
        mock_setting = MagicMock()
        mock_setting.key = "app.debug"
        mock_setting.value = False
        mock_setting.ui_element = "checkbox"
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.all.return_value = [
            mock_setting
        ]

        manager = SettingsManager(db_session=mock_session)

        result = manager.get_setting("app.debug", check_env=True)

        # Environment variable should override
        assert result is True

    def test_get_setting_env_only_setting_from_env(self):
        """Test that env-only settings are read from environment."""
        os.environ["LDR_TESTING_TEST_MODE"] = "true"

        manager = SettingsManager(db_session=None)

        result = manager.get_setting("testing.test_mode")

        assert result is True

    def test_get_setting_nested_key_pattern(self):
        """Test that nested key pattern returns dict of settings."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        # Mock multiple settings matching pattern
        mock_settings = [
            MagicMock(key="llm.provider", value="openai", ui_element="select"),
            MagicMock(key="llm.temperature", value=0.7, ui_element="number"),
        ]
        mock_session.query.return_value.filter.return_value.all.return_value = (
            mock_settings
        )

        manager = SettingsManager(db_session=mock_session)

        result = manager.get_setting("llm")

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

    def test_get_setting_exact_key_match(self):
        """Test that exact key match returns single value."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

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

        manager = SettingsManager(db_session=mock_session)

        result = manager.get_setting("app.debug")

        assert result is True

    def test_get_setting_with_empty_string_default(self):
        """Test get_setting with empty string as default."""
        manager = SettingsManager(db_session=None)

        result = manager.get_setting("nonexistent.key", default="")

        assert result == ""

    def test_get_setting_with_none_default(self):
        """Test get_setting with None as default."""
        manager = SettingsManager(db_session=None)

        result = manager.get_setting("nonexistent.key", default=None)

        assert result is None

    def test_get_setting_sqlalchemy_error_handling(self):
        """Test that SQLAlchemy errors are handled and return default."""
        from sqlalchemy.exc import SQLAlchemyError

        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.all.side_effect = (
            SQLAlchemyError("DB error")
        )

        manager = SettingsManager(db_session=mock_session)

        result = manager.get_setting("app.debug", default="fallback")

        assert result == "fallback"

    def test_get_setting_auto_initializes_empty_db(self):
        """Test that _ensure_settings_initialized is called for empty DB."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 0

        with patch.object(
            SettingsManager, "load_from_defaults_file"
        ) as mock_load:
            SettingsManager(db_session=mock_session)

            mock_load.assert_called_once()


class TestSettingsManagerSetSetting:
    """Tests for set_setting functionality."""

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        original_env = {
            k: v for k, v in os.environ.items() if k.startswith("LDR_")
        }
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        yield
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        for key, value in original_env.items():
            os.environ[key] = value

    def test_set_setting_creates_new_setting(self):
        """Test that set_setting creates new setting when not exists."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.first.return_value = None

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

        with patch.object(manager, "_emit_settings_changed"):
            result = manager.set_setting("new.key", "new_value")

        assert result is True
        mock_session.add.assert_called_once()

    def test_set_setting_updates_existing_setting(self):
        """Test that set_setting updates existing setting."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        mock_setting = MagicMock()
        mock_setting.editable = True
        mock_session.query.return_value.filter.return_value.first.return_value = mock_setting

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

        with patch.object(manager, "_emit_settings_changed"):
            result = manager.set_setting("existing.key", "updated_value")

        assert result is True
        assert mock_setting.value == "updated_value"

    def test_set_setting_preserves_type(self):
        """Test that set_setting preserves the type of the value."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        mock_setting = MagicMock()
        mock_setting.editable = True
        mock_session.query.return_value.filter.return_value.first.return_value = mock_setting

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

        with patch.object(manager, "_emit_settings_changed"):
            manager.set_setting("test.int", 42)

        assert mock_setting.value == 42

    def test_set_setting_emits_websocket_event(self):
        """Test that set_setting emits WebSocket event on commit."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.first.return_value = None

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

        with patch.object(manager, "_emit_settings_changed") as mock_emit:
            manager.set_setting("test.key", "value", commit=True)

            mock_emit.assert_called_once_with(["test.key"])

    def test_set_setting_rollback_on_error(self):
        """Test that set_setting rolls back on error."""
        from sqlalchemy.exc import SQLAlchemyError

        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.first.side_effect = SQLAlchemyError()

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

        result = manager.set_setting("test.key", "value")

        assert result is False
        mock_session.rollback.assert_called_once()

    def test_set_setting_no_db_session_returns_false(self):
        """Test that set_setting returns False without DB session."""
        manager = SettingsManager(db_session=None)

        result = manager.set_setting("test.key", "value")

        assert result is False

    def test_set_setting_non_editable_returns_false(self):
        """Test that set_setting returns False for non-editable settings."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        mock_setting = MagicMock()
        mock_setting.editable = False
        mock_session.query.return_value.filter.return_value.first.return_value = mock_setting

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

        result = manager.set_setting("readonly.key", "value")

        assert result is False


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

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        original_env = {
            k: v for k, v in os.environ.items() if k.startswith("LDR_")
        }
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        yield
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        for key, value in original_env.items():
            os.environ[key] = value

    def test_import_settings_with_overwrite_true(self):
        """Test that import_settings overwrites existing values when overwrite=True."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "get_setting", return_value="old_value"):
            with patch.object(manager, "delete_setting"):
                with patch.object(manager, "_emit_settings_changed"):
                    manager.import_settings(
                        {"test.key": {"value": "new_value", "type": "APP"}},
                        overwrite=True,
                    )

        # Should have added the new value (delete + add)
        mock_session.add.assert_called()

    def test_import_settings_with_overwrite_false(self):
        """Test that import_settings preserves existing values when overwrite=False."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        with patch.object(
            manager, "get_setting", return_value="existing_value"
        ):
            with patch.object(manager, "delete_setting"):
                with patch.object(manager, "_emit_settings_changed"):
                    manager.import_settings(
                        {"test.key": {"value": "new_value", "type": "APP"}},
                        overwrite=False,
                    )

        # The value should be preserved (existing_value)
        mock_session.add.assert_called()

    def test_import_settings_with_delete_extra_true(self):
        """Test that import_settings deletes extra settings when delete_extra=True."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        # Mock get_all_settings to return extra key
        extra_settings = {
            "test.key": {"value": "v1"},
            "extra.key": {"value": "v2"},
        }

        with patch.object(manager, "get_setting", return_value=None):
            with patch.object(manager, "delete_setting") as mock_delete:
                with patch.object(
                    manager, "get_all_settings", return_value=extra_settings
                ):
                    with patch.object(manager, "_emit_settings_changed"):
                        manager.import_settings(
                            {"test.key": {"value": "v1", "type": "APP"}},
                            delete_extra=True,
                        )

        # Should delete the extra.key
        delete_calls = [
            call
            for call in mock_delete.call_args_list
            if call[0][0] == "extra.key"
        ]
        assert len(delete_calls) > 0

    def test_import_settings_type_detection_from_key(self):
        """Test that import_settings detects type from key prefix."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "get_setting", return_value=None):
            with patch.object(manager, "delete_setting"):
                with patch.object(manager, "_emit_settings_changed"):
                    manager.import_settings(
                        {
                            "llm.test": {"value": "v1", "type": "LLM"},
                            "search.test": {"value": "v2", "type": "SEARCH"},
                            "report.test": {"value": "v3", "type": "REPORT"},
                        }
                    )

        # All should be added
        assert mock_session.add.call_count == 3

    def test_get_all_settings_merges_defaults(self):
        """Test that get_all_settings merges defaults with DB values."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.all.return_value = []

        manager = SettingsManager(db_session=mock_session)

        # Mock default_settings
        with patch.object(
            SettingsManager,
            "default_settings",
            new_callable=PropertyMock,
            return_value={"default.key": {"value": "default"}},
        ):
            result = manager.get_all_settings()

        assert "default.key" in result

    def test_get_all_settings_marks_env_non_editable(self):
        """Test that settings overridden by env vars are marked non-editable."""
        os.environ["LDR_APP_DEBUG"] = "true"

        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        mock_setting = MagicMock()
        mock_setting.key = "app.debug"
        mock_setting.value = False
        mock_setting.type = MagicMock(name="APP")
        mock_setting.name = "Debug"
        mock_setting.description = "Debug mode"
        mock_setting.category = "app"
        mock_setting.ui_element = "checkbox"
        mock_setting.options = None
        mock_setting.min_value = None
        mock_setting.max_value = None
        mock_setting.step = None
        mock_setting.visible = True
        mock_setting.editable = True
        mock_session.query.return_value.all.return_value = [mock_setting]

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

        with patch.object(
            SettingsManager,
            "default_settings",
            new_callable=PropertyMock,
            return_value={},
        ):
            result = manager.get_all_settings()

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

    def test_get_settings_snapshot_flat_dict(self):
        """Test that get_settings_snapshot returns flat key-value dict."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        with patch.object(
            manager,
            "get_all_settings",
            return_value={
                "key1": {"value": "v1"},
                "key2": {"value": 42},
            },
        ):
            result = manager.get_settings_snapshot()

        assert result == {"key1": "v1", "key2": 42}

    def test_load_from_defaults_file(self):
        """Test that load_from_defaults_file calls import_settings."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "import_settings") as mock_import:
            with patch.object(
                SettingsManager,
                "default_settings",
                new_callable=PropertyMock,
                return_value={"test": {"value": "v"}},
            ):
                manager.load_from_defaults_file()

        mock_import.assert_called_once()


class TestSettingsManagerVersioning:
    """Tests for version management."""

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        original_env = {
            k: v for k, v in os.environ.items() if k.startswith("LDR_")
        }
        yield
        for key, value in original_env.items():
            os.environ[key] = value

    def test_db_version_matches_package_true(self):
        """Test db_version_matches_package returns True when versions match."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        from local_deep_research.__version__ import __version__ as pkg_version

        with patch.object(manager, "get_setting", return_value=pkg_version):
            result = manager.db_version_matches_package()

        assert result is True

    def test_db_version_matches_package_false(self):
        """Test db_version_matches_package returns False when versions differ."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "get_setting", return_value="0.0.0"):
            result = manager.db_version_matches_package()

        assert result is False

    def test_update_db_version(self):
        """Test that update_db_version saves package version."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        with patch.object(manager, "delete_setting"):
            manager.update_db_version()

        mock_session.add.assert_called_once()
        mock_session.commit.assert_called_once()


class TestSettingsManagerStaticMethods:
    """Tests for static helper methods."""

    def test_get_bootstrap_env_vars(self):
        """Test get_bootstrap_env_vars returns bootstrap variables."""
        result = SettingsManager.get_bootstrap_env_vars()

        assert isinstance(result, dict)
        assert "LDR_BOOTSTRAP_ENCRYPTION_KEY" in result
        assert "LDR_BOOTSTRAP_DATA_DIR" in result

    def test_is_bootstrap_env_var_true(self):
        """Test is_bootstrap_env_var returns True for bootstrap vars."""
        assert SettingsManager.is_bootstrap_env_var(
            "LDR_BOOTSTRAP_ENCRYPTION_KEY"
        )
        assert SettingsManager.is_bootstrap_env_var(
            "LDR_DB_CONFIG_CACHE_SIZE_MB"
        )

    def test_is_bootstrap_env_var_false(self):
        """Test is_bootstrap_env_var returns False for non-bootstrap vars."""
        assert not SettingsManager.is_bootstrap_env_var("LDR_TESTING_TEST_MODE")
        assert not SettingsManager.is_bootstrap_env_var("RANDOM_VAR")

    def test_is_env_only_setting_true(self):
        """Test is_env_only_setting returns True for env-only settings."""
        assert SettingsManager.is_env_only_setting("testing.test_mode")
        assert SettingsManager.is_env_only_setting("bootstrap.encryption_key")

    def test_is_env_only_setting_false(self):
        """Test is_env_only_setting returns False for DB settings."""
        assert not SettingsManager.is_env_only_setting("app.debug")
        assert not SettingsManager.is_env_only_setting("llm.provider")

    def test_get_env_var_for_setting(self):
        """Test get_env_var_for_setting returns correct env var name."""
        assert (
            SettingsManager.get_env_var_for_setting("app.host")
            == "LDR_APP_HOST"
        )
        assert (
            SettingsManager.get_env_var_for_setting("llm.provider")
            == "LDR_LLM_PROVIDER"
        )

    def test_get_setting_key_for_env_var(self):
        """Test get_setting_key_for_env_var returns correct setting key."""
        assert (
            SettingsManager.get_setting_key_for_env_var("LDR_APP_HOST")
            == "app.host"
        )
        assert (
            SettingsManager.get_setting_key_for_env_var("LDR_LLM_PROVIDER")
            == "llm.provider"
        )

    def test_get_setting_key_for_env_var_non_ldr(self):
        """Test get_setting_key_for_env_var returns None for non-LDR vars."""
        assert SettingsManager.get_setting_key_for_env_var("PATH") is None
        assert SettingsManager.get_setting_key_for_env_var("HOME") is None


class TestHelperFunctions:
    """Tests for module-level helper functions."""

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        original_env = {
            k: v for k, v in os.environ.items() if k.startswith("LDR_")
        }
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        yield
        for key in list(os.environ.keys()):
            if key.startswith("LDR_"):
                os.environ.pop(key, None)
        for key, value in original_env.items():
            os.environ[key] = value

    def test_parse_number_int(self):
        """Test _parse_number returns int for whole numbers."""
        assert _parse_number("42") == 42
        assert isinstance(_parse_number("42"), int)

    def test_parse_number_float(self):
        """Test _parse_number returns float for decimals."""
        assert _parse_number("3.14") == 3.14
        assert isinstance(_parse_number("3.14"), float)

    def test_parse_number_float_as_int(self):
        """Test _parse_number returns int for float with .0."""
        assert _parse_number("42.0") == 42
        assert isinstance(_parse_number("42.0"), int)

    def test_check_env_setting_returns_value(self):
        """Test check_env_setting returns env var value."""
        os.environ["LDR_APP_DEBUG"] = "true"

        result = check_env_setting("app.debug")

        assert result == "true"

    def test_check_env_setting_returns_none_when_not_set(self):
        """Test check_env_setting returns None when not set."""
        result = check_env_setting("nonexistent.key")

        assert result is None

    def test_get_typed_setting_value_unknown_ui_element(self):
        """Test get_typed_setting_value returns default for unknown UI element."""
        result = get_typed_setting_value(
            key="test",
            value="val",
            ui_element="unknown_element",
            default="fallback",
        )

        assert result == "fallback"

    def test_get_typed_setting_value_json_passthrough(self):
        """Test get_typed_setting_value passes JSON through unchanged."""
        json_value = {"key": "value", "list": [1, 2, 3]}

        result = get_typed_setting_value(
            key="test", value=json_value, ui_element="json", default=None
        )

        assert result == json_value

    def test_get_typed_setting_value_invalid_number(self):
        """Test get_typed_setting_value returns default for invalid number."""
        result = get_typed_setting_value(
            key="test", value="not_a_number", ui_element="number", default=99
        )

        assert result == 99

    def test_get_typed_setting_value_select_returns_string(self):
        """Test get_typed_setting_value returns string for select."""
        result = get_typed_setting_value(
            key="test", value="option1", ui_element="select", default=None
        )

        assert result == "option1"
        assert isinstance(result, str)


class TestDeleteSetting:
    """Tests for delete_setting functionality."""

    def test_delete_setting_success(self):
        """Test that delete_setting returns True on success."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.delete.return_value = 1

        manager = SettingsManager(db_session=mock_session)

        result = manager.delete_setting("test.key")

        assert result is True
        mock_session.commit.assert_called()

    def test_delete_setting_not_found(self):
        """Test that delete_setting returns False when key not found."""
        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.delete.return_value = 0

        manager = SettingsManager(db_session=mock_session)

        result = manager.delete_setting("nonexistent.key")

        assert result is False

    def test_delete_setting_no_session(self):
        """Test that delete_setting returns False without DB session."""
        manager = SettingsManager(db_session=None)

        result = manager.delete_setting("test.key")

        assert result is False

    def test_delete_setting_rollback_on_error(self):
        """Test that delete_setting rolls back on error."""
        from sqlalchemy.exc import SQLAlchemyError

        mock_session = MagicMock()
        mock_session.query.return_value.count.return_value = 1
        mock_session.query.return_value.filter.return_value.delete.side_effect = SQLAlchemyError()

        manager = SettingsManager(db_session=mock_session)

        result = manager.delete_setting("test.key")

        assert result is False
        mock_session.rollback.assert_called_once()
