"""
Comprehensive tests for boolean centralization feature.

This module tests the centralized boolean handling functionality:
1. to_bool() utility function from settings_utils.py
2. extract_bool_setting() from settings_utils.py
3. get_bool_setting_from_snapshot() from thread_settings.py
4. get_bool_setting() method on SettingsManager
"""

import pytest
from unittest.mock import patch

from local_deep_research.api.settings_utils import (
    to_bool,
    extract_bool_setting,
)
from local_deep_research.config.thread_settings import (
    get_bool_setting_from_snapshot,
)
from local_deep_research.settings.manager import (
    SettingsManager,
)


class TestToBoolUtility:
    """Test the to_bool() utility function."""

    def test_boolean_true_passthrough(self):
        """Test that boolean True passes through unchanged."""
        assert to_bool(True) is True

    def test_boolean_false_passthrough(self):
        """Test that boolean False passes through unchanged."""
        assert to_bool(False) is False

    def test_none_with_default_false(self):
        """Test that None returns default value (False)."""
        assert to_bool(None) is False

    def test_none_with_default_true(self):
        """Test that None returns custom default value (True)."""
        assert to_bool(None, default=True) is True

    # Truthy string values
    @pytest.mark.parametrize(
        "value",
        [
            "true",
            "True",
            "TRUE",
            "TrUe",  # Mixed case
            "1",
            "yes",
            "Yes",
            "YES",
            "YeS",  # Mixed case
            "on",
            "On",
            "ON",
            "enabled",
            "Enabled",
            "ENABLED",
            "EnAbLeD",  # Mixed case
        ],
    )
    def test_truthy_strings(self, value):
        """Test various truthy string representations return True."""
        assert to_bool(value) is True

    # Falsy string values
    @pytest.mark.parametrize(
        "value",
        [
            "false",
            "False",
            "FALSE",
            "0",
            "no",
            "No",
            "NO",
            "off",
            "Off",
            "OFF",
            "disabled",
            "",  # Empty string
            "random",  # Random string not in truthy list
            "2",  # Not "1"
            "truth",  # Not exactly "true"
            "enabled_partially",  # Contains "enabled" but not exact
        ],
    )
    def test_falsy_strings(self, value):
        """Test various falsy string representations return False."""
        assert to_bool(value) is False

    # Whitespace handling
    def test_empty_string_is_false(self):
        """Test that empty string returns False."""
        assert to_bool("") is False

    def test_whitespace_only_string_is_false(self):
        """Test that whitespace-only strings return False."""
        # Note: The implementation doesn't strip whitespace,
        # so these should be False (not in truthy list)
        assert to_bool("   ") is False
        assert to_bool("\t") is False
        assert to_bool("\n") is False

    def test_string_with_leading_whitespace_is_false(self):
        """Test that strings with leading/trailing whitespace don't match."""
        # The current implementation doesn't strip, so these won't match
        assert to_bool("  true") is False
        assert to_bool("true  ") is False
        assert to_bool("  yes  ") is False

    # Numeric values
    def test_integer_zero_is_false(self):
        """Test that integer 0 returns False."""
        assert to_bool(0) is False

    def test_integer_one_is_true(self):
        """Test that integer 1 returns True."""
        assert to_bool(1) is True

    def test_positive_integer_is_true(self):
        """Test that positive integers return True."""
        assert to_bool(42) is True
        assert to_bool(1000) is True

    def test_negative_integer_is_true(self):
        """Test that negative integers return True."""
        assert to_bool(-1) is True
        assert to_bool(-42) is True

    def test_float_zero_is_false(self):
        """Test that float 0.0 returns False."""
        assert to_bool(0.0) is False

    def test_nonzero_float_is_true(self):
        """Test that non-zero floats return True."""
        assert to_bool(3.14) is True
        assert to_bool(-2.71) is True

    # Collection types
    def test_empty_list_is_false(self):
        """Test that empty list returns False."""
        assert to_bool([]) is False

    def test_nonempty_list_is_true(self):
        """Test that non-empty list returns True."""
        assert to_bool([1, 2, 3]) is True
        assert to_bool(["false"]) is True

    def test_empty_dict_is_false(self):
        """Test that empty dict returns False."""
        assert to_bool({}) is False

    def test_nonempty_dict_is_true(self):
        """Test that non-empty dict returns True."""
        assert to_bool({"key": "value"}) is True

    def test_empty_tuple_is_false(self):
        """Test that empty tuple returns False."""
        assert to_bool(()) is False

    def test_nonempty_tuple_is_true(self):
        """Test that non-empty tuple returns True."""
        assert to_bool((1, 2)) is True

    # Edge cases
    def test_returns_actual_bool_not_truthy(self):
        """Test that return value is actual bool, not just truthy."""
        result = to_bool("true")
        assert result is True
        assert isinstance(result, bool)

        result = to_bool("false")
        assert result is False
        assert isinstance(result, bool)

    def test_case_sensitivity(self):
        """Test that string comparison is case-insensitive via .lower()."""
        # These should all return True
        assert to_bool("TRUE") is True
        assert to_bool("TrUe") is True
        assert to_bool("YES") is True
        assert to_bool("YeS") is True

    def test_default_parameter_default_value(self):
        """Test that default parameter defaults to False."""
        # When value is None and no default specified
        assert to_bool(None) is False


class TestExtractBoolSetting:
    """Test the extract_bool_setting() function."""

    def test_extract_from_dict_with_value_key(self):
        """Test extracting boolean from dict with 'value' key."""
        snapshot = {"setting.key": {"value": "true"}}
        assert extract_bool_setting(snapshot, "setting.key") is True

    def test_extract_from_dict_with_direct_value(self):
        """Test extracting boolean from dict with direct value."""
        snapshot = {"setting.key": "yes"}
        assert extract_bool_setting(snapshot, "setting.key") is True

    def test_extract_from_dict_with_boolean_value(self):
        """Test extracting boolean that's already a bool."""
        snapshot = {"setting.key": {"value": True}}
        assert extract_bool_setting(snapshot, "setting.key") is True

    def test_extract_missing_key_uses_default_false(self):
        """Test that missing key returns default (False)."""
        snapshot = {"other.key": "true"}
        assert extract_bool_setting(snapshot, "missing.key") is False

    def test_extract_missing_key_uses_custom_default(self):
        """Test that missing key returns custom default value."""
        snapshot = {"other.key": "true"}
        assert (
            extract_bool_setting(snapshot, "missing.key", default=True) is True
        )

    def test_extract_none_snapshot(self):
        """Test extracting from None snapshot returns default."""
        assert extract_bool_setting(None, "any.key") is False
        assert extract_bool_setting(None, "any.key", default=True) is True

    def test_extract_empty_snapshot(self):
        """Test extracting from empty snapshot returns default."""
        assert extract_bool_setting({}, "any.key") is False

    def test_extract_truthy_string_values(self):
        """Test extracting various truthy string values."""
        snapshot = {
            "setting1": {"value": "true"},
            "setting2": {"value": "1"},
            "setting3": {"value": "yes"},
            "setting4": {"value": "on"},
            "setting5": {"value": "enabled"},
        }
        assert extract_bool_setting(snapshot, "setting1") is True
        assert extract_bool_setting(snapshot, "setting2") is True
        assert extract_bool_setting(snapshot, "setting3") is True
        assert extract_bool_setting(snapshot, "setting4") is True
        assert extract_bool_setting(snapshot, "setting5") is True

    def test_extract_falsy_string_values(self):
        """Test extracting various falsy string values."""
        snapshot = {
            "setting1": {"value": "false"},
            "setting2": {"value": "0"},
            "setting3": {"value": "no"},
            "setting4": {"value": ""},
            "setting5": {"value": "random"},
        }
        assert extract_bool_setting(snapshot, "setting1") is False
        assert extract_bool_setting(snapshot, "setting2") is False
        assert extract_bool_setting(snapshot, "setting3") is False
        assert extract_bool_setting(snapshot, "setting4") is False
        assert extract_bool_setting(snapshot, "setting5") is False

    def test_extract_with_numeric_values(self):
        """Test extracting numeric values."""
        snapshot = {
            "zero": {"value": 0},
            "one": {"value": 1},
            "positive": {"value": 42},
        }
        assert extract_bool_setting(snapshot, "zero") is False
        assert extract_bool_setting(snapshot, "one") is True
        assert extract_bool_setting(snapshot, "positive") is True


class TestGetBoolSettingFromSnapshot:
    """Test the get_bool_setting_from_snapshot() function."""

    def test_get_from_snapshot_with_value_key(self):
        """Test getting boolean from snapshot with 'value' key."""
        snapshot = {"test.setting": {"value": "true"}}
        result = get_bool_setting_from_snapshot(
            "test.setting", settings_snapshot=snapshot
        )
        assert result is True

    def test_get_from_snapshot_with_direct_value(self):
        """Test getting boolean from snapshot with direct value."""
        snapshot = {"test.setting": "yes"}
        result = get_bool_setting_from_snapshot(
            "test.setting", settings_snapshot=snapshot
        )
        assert result is True

    def test_get_from_snapshot_with_boolean_value(self):
        """Test getting boolean that's already a bool."""
        snapshot = {"test.setting": True}
        result = get_bool_setting_from_snapshot(
            "test.setting", settings_snapshot=snapshot
        )
        assert result is True

    def test_get_missing_key_uses_default_false(self):
        """Test that missing key returns default (False)."""
        snapshot = {"other.setting": "true"}
        result = get_bool_setting_from_snapshot(
            "missing.key", settings_snapshot=snapshot
        )
        assert result is False

    def test_get_missing_key_uses_custom_default(self):
        """Test that missing key returns custom default value."""
        snapshot = {"other.setting": "true"}
        result = get_bool_setting_from_snapshot(
            "missing.key", default=True, settings_snapshot=snapshot
        )
        assert result is True

    def test_get_with_none_snapshot_uses_default(self):
        """Test that None snapshot with default doesn't raise error."""
        # When settings_snapshot is None and default is provided
        result = get_bool_setting_from_snapshot(
            "any.key", default=True, settings_snapshot=None
        )
        assert result is True

    def test_get_truthy_string_values(self):
        """Test getting various truthy string values."""
        snapshot = {
            "s1": "true",
            "s2": "1",
            "s3": "yes",
            "s4": "on",
            "s5": "enabled",
        }
        assert (
            get_bool_setting_from_snapshot("s1", settings_snapshot=snapshot)
            is True
        )
        assert (
            get_bool_setting_from_snapshot("s2", settings_snapshot=snapshot)
            is True
        )
        assert (
            get_bool_setting_from_snapshot("s3", settings_snapshot=snapshot)
            is True
        )
        assert (
            get_bool_setting_from_snapshot("s4", settings_snapshot=snapshot)
            is True
        )
        assert (
            get_bool_setting_from_snapshot("s5", settings_snapshot=snapshot)
            is True
        )

    def test_get_falsy_string_values(self):
        """Test getting various falsy string values."""
        snapshot = {
            "s1": "false",
            "s2": "0",
            "s3": "no",
            "s4": "off",
            "s5": "",
        }
        assert (
            get_bool_setting_from_snapshot("s1", settings_snapshot=snapshot)
            is False
        )
        assert (
            get_bool_setting_from_snapshot("s2", settings_snapshot=snapshot)
            is False
        )
        assert (
            get_bool_setting_from_snapshot("s3", settings_snapshot=snapshot)
            is False
        )
        assert (
            get_bool_setting_from_snapshot("s4", settings_snapshot=snapshot)
            is False
        )
        assert (
            get_bool_setting_from_snapshot("s5", settings_snapshot=snapshot)
            is False
        )

    def test_get_with_none_value_uses_default(self):
        """Test that None value returns default."""
        snapshot = {"test.setting": None}
        result = get_bool_setting_from_snapshot(
            "test.setting", default=True, settings_snapshot=snapshot
        )
        assert result is True

    def test_get_with_numeric_values(self):
        """Test getting numeric values."""
        snapshot = {"zero": 0, "one": 1, "positive": 42}
        assert (
            get_bool_setting_from_snapshot("zero", settings_snapshot=snapshot)
            is False
        )
        assert (
            get_bool_setting_from_snapshot("one", settings_snapshot=snapshot)
            is True
        )
        assert (
            get_bool_setting_from_snapshot(
                "positive", settings_snapshot=snapshot
            )
            is True
        )

    def test_backward_compatibility_username_parameter(self):
        """Test that username parameter is accepted (for backward compatibility)."""
        snapshot = {"test.setting": "true"}
        # Should not raise an error even though username is unused
        result = get_bool_setting_from_snapshot(
            "test.setting", username="testuser", settings_snapshot=snapshot
        )
        assert result is True

    def test_check_fallback_llm_parameter(self):
        """Test that check_fallback_llm parameter is accepted."""
        snapshot = {"test.setting": "yes"}
        # Should not raise an error
        result = get_bool_setting_from_snapshot(
            "test.setting",
            settings_snapshot=snapshot,
            check_fallback_llm=True,
        )
        assert result is True


class TestSettingsManagerGetBoolSetting:
    """Test the get_bool_setting() method on SettingsManager."""

    def test_get_bool_setting_with_boolean_true(self):
        """Test getting a setting that's already boolean True."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=True):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_with_boolean_false(self):
        """Test getting a setting that's already boolean False."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=False):
            result = manager.get_bool_setting("test.key")
            assert result is False

    def test_get_bool_setting_with_string_true(self):
        """Test getting a setting with string 'true'."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="true"):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_with_string_false(self):
        """Test getting a setting with string 'false'."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="false"):
            result = manager.get_bool_setting("test.key")
            assert result is False

    def test_get_bool_setting_with_string_yes(self):
        """Test getting a setting with string 'yes'."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="yes"):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_with_string_one(self):
        """Test getting a setting with string '1'."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="1"):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_with_string_on(self):
        """Test getting a setting with string 'on'."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="on"):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_with_string_enabled(self):
        """Test getting a setting with string 'enabled'."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="enabled"):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_with_none_uses_default_false(self):
        """Test that None value returns default (False)."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=None):
            result = manager.get_bool_setting("test.key")
            assert result is False

    def test_get_bool_setting_with_none_uses_custom_default(self):
        """Test that None value returns custom default."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=None):
            result = manager.get_bool_setting("test.key", default=True)
            assert result is True

    def test_get_bool_setting_with_integer_zero(self):
        """Test getting a setting with integer 0."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=0):
            result = manager.get_bool_setting("test.key")
            assert result is False

    def test_get_bool_setting_with_integer_one(self):
        """Test getting a setting with integer 1."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=1):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_with_positive_integer(self):
        """Test getting a setting with positive integer."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=42):
            result = manager.get_bool_setting("test.key")
            assert result is True

    def test_get_bool_setting_passes_check_env_true(self):
        """Test that check_env=True is passed to get_setting."""
        manager = SettingsManager(db_session=None)
        with patch.object(
            manager, "get_setting", return_value=True
        ) as mock_get:
            manager.get_bool_setting("test.key", check_env=True)
            mock_get.assert_called_once_with("test.key", False, True)

    def test_get_bool_setting_passes_check_env_false(self):
        """Test that check_env=False is passed to get_setting."""
        manager = SettingsManager(db_session=None)
        with patch.object(
            manager, "get_setting", return_value=True
        ) as mock_get:
            manager.get_bool_setting("test.key", check_env=False)
            mock_get.assert_called_once_with("test.key", False, False)

    def test_get_bool_setting_passes_custom_default(self):
        """Test that custom default is passed to get_setting."""
        manager = SettingsManager(db_session=None)
        with patch.object(
            manager, "get_setting", return_value=None
        ) as mock_get:
            manager.get_bool_setting("test.key", default=True)
            mock_get.assert_called_once_with("test.key", True, True)

    def test_get_bool_setting_with_empty_string(self):
        """Test getting a setting with empty string."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=""):
            result = manager.get_bool_setting("test.key")
            assert result is False

    def test_get_bool_setting_with_random_string(self):
        """Test getting a setting with random string (not in truthy list)."""
        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="random"):
            result = manager.get_bool_setting("test.key")
            assert result is False

    def test_get_bool_setting_case_insensitive(self):
        """Test that string comparison is case-insensitive."""
        manager = SettingsManager(db_session=None)

        with patch.object(manager, "get_setting", return_value="TRUE"):
            assert manager.get_bool_setting("test.key") is True

        with patch.object(manager, "get_setting", return_value="YES"):
            assert manager.get_bool_setting("test.key") is True

        with patch.object(manager, "get_setting", return_value="ON"):
            assert manager.get_bool_setting("test.key") is True

    def test_get_bool_setting_with_list(self):
        """Test getting a setting with list value."""
        manager = SettingsManager(db_session=None)

        with patch.object(manager, "get_setting", return_value=[]):
            assert manager.get_bool_setting("test.key") is False

        with patch.object(manager, "get_setting", return_value=[1, 2, 3]):
            assert manager.get_bool_setting("test.key") is True

    def test_get_bool_setting_with_dict(self):
        """Test getting a setting with dict value."""
        manager = SettingsManager(db_session=None)

        with patch.object(manager, "get_setting", return_value={}):
            assert manager.get_bool_setting("test.key") is False

        with patch.object(
            manager, "get_setting", return_value={"key": "value"}
        ):
            assert manager.get_bool_setting("test.key") is True


class TestBooleanCentralizationIntegration:
    """Integration tests for the boolean centralization feature."""

    def test_consistency_across_all_methods(self):
        """Test that all boolean methods return consistent results."""
        # Test with string "true"
        assert to_bool("true") is True
        assert extract_bool_setting({"key": "true"}, "key") is True
        assert (
            get_bool_setting_from_snapshot(
                "key", settings_snapshot={"key": "true"}
            )
            is True
        )

        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value="true"):
            assert manager.get_bool_setting("key") is True

    def test_consistency_with_boolean_values(self):
        """Test consistency when passing actual boolean values."""
        assert to_bool(True) is True
        assert extract_bool_setting({"key": True}, "key") is True
        assert (
            get_bool_setting_from_snapshot(
                "key", settings_snapshot={"key": True}
            )
            is True
        )

        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=True):
            assert manager.get_bool_setting("key") is True

    def test_consistency_with_none_and_defaults(self):
        """Test consistency when handling None with defaults."""
        assert to_bool(None, default=True) is True
        assert extract_bool_setting({"key": None}, "key", default=True) is True
        assert (
            get_bool_setting_from_snapshot(
                "key", default=True, settings_snapshot={"key": None}
            )
            is True
        )

        manager = SettingsManager(db_session=None)
        with patch.object(manager, "get_setting", return_value=None):
            assert manager.get_bool_setting("key", default=True) is True

    def test_all_truthy_strings_consistent(self):
        """Test that all truthy strings work consistently across methods."""
        truthy_values = ["true", "1", "yes", "on", "enabled"]

        for value in truthy_values:
            assert to_bool(value) is True, f"to_bool failed for {value}"
            assert extract_bool_setting({"k": value}, "k") is True, (
                f"extract_bool_setting failed for {value}"
            )
            assert (
                get_bool_setting_from_snapshot(
                    "k", settings_snapshot={"k": value}
                )
                is True
            ), f"get_bool_setting_from_snapshot failed for {value}"

            manager = SettingsManager(db_session=None)
            with patch.object(manager, "get_setting", return_value=value):
                assert manager.get_bool_setting("k") is True, (
                    f"get_bool_setting failed for {value}"
                )

    def test_all_falsy_strings_consistent(self):
        """Test that all falsy strings work consistently across methods."""
        falsy_values = ["false", "0", "no", "off", ""]

        for value in falsy_values:
            assert to_bool(value) is False, f"to_bool failed for {value}"
            assert extract_bool_setting({"k": value}, "k") is False, (
                f"extract_bool_setting failed for {value}"
            )
            assert (
                get_bool_setting_from_snapshot(
                    "k", settings_snapshot={"k": value}
                )
                is False
            ), f"get_bool_setting_from_snapshot failed for {value}"

            manager = SettingsManager(db_session=None)
            with patch.object(manager, "get_setting", return_value=value):
                assert manager.get_bool_setting("k") is False, (
                    f"get_bool_setting failed for {value}"
                )
