"""
Behavioral tests for settings/env_settings module and env_definitions.

Tests the EnvSetting class hierarchy, type conversion, validation,
and the SettingsRegistry.
"""

import os
from unittest.mock import patch

import pytest


class TestBooleanSetting:
    """Tests for BooleanSetting class."""

    def test_key_generates_env_var(self):
        """Key auto-generates LDR_ prefixed env var."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="testing.test_mode", description="desc")
        assert setting.env_var == "LDR_TESTING_TEST_MODE"

    def test_default_is_false(self):
        """Default value is False."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="test.flag", description="desc")
        assert setting.default is False

    def test_custom_default(self):
        """Can set custom default."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(
            key="test.flag", description="desc", default=True
        )
        assert setting.default is True

    def test_converts_true_strings(self):
        """Converts truthy strings to True."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="test.flag", description="desc")
        for val in ("true", "1", "yes", "on", "enabled"):
            assert setting._convert_value(val) is True

    def test_converts_false_strings(self):
        """Converts non-truthy strings to False."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="test.flag", description="desc")
        for val in ("false", "0", "no", "off", "disabled"):
            assert setting._convert_value(val) is False

    def test_case_insensitive_conversion(self):
        """Conversion is case-insensitive."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="test.flag", description="desc")
        assert setting._convert_value("TRUE") is True
        assert setting._convert_value("Enabled") is True

    def test_get_value_returns_default_when_not_set(self):
        """get_value returns default when env var not set."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="test.unset_xyz", description="desc")
        with patch.dict(os.environ, {}, clear=False):
            if setting.env_var in os.environ:
                del os.environ[setting.env_var]
            result = setting.get_value()
            assert result is False

    def test_get_value_from_env(self):
        """get_value reads from environment."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="test.env_bool_xyz", description="desc")
        with patch.dict(os.environ, {setting.env_var: "true"}):
            result = setting.get_value()
            assert result is True


class TestStringSetting:
    """Tests for StringSetting class."""

    def test_converts_value_as_is(self):
        """Returns string value unchanged."""
        from local_deep_research.settings.env_settings import StringSetting

        setting = StringSetting(key="test.str", description="desc")
        assert setting._convert_value("hello world") == "hello world"

    def test_default_is_none(self):
        """Default value is None."""
        from local_deep_research.settings.env_settings import StringSetting

        setting = StringSetting(key="test.str", description="desc")
        assert setting.default is None

    def test_required_raises_when_not_set(self):
        """Required setting raises ValueError when not set."""
        from local_deep_research.settings.env_settings import StringSetting

        setting = StringSetting(
            key="test.required_str_xyz", description="desc", required=True
        )
        with patch.dict(os.environ, {}, clear=False):
            if setting.env_var in os.environ:
                del os.environ[setting.env_var]
            with pytest.raises(
                ValueError, match="Required environment variable"
            ):
                setting.get_value()


class TestIntegerSetting:
    """Tests for IntegerSetting class."""

    def test_converts_valid_int(self):
        """Converts valid integer string."""
        from local_deep_research.settings.env_settings import IntegerSetting

        setting = IntegerSetting(key="test.int", description="desc")
        assert setting._convert_value("42") == 42

    def test_min_value_validation(self):
        """Raises ValueError when below minimum."""
        from local_deep_research.settings.env_settings import IntegerSetting

        setting = IntegerSetting(
            key="test.int", description="desc", min_value=10
        )
        with pytest.raises(ValueError, match="below minimum"):
            setting._convert_value("5")

    def test_max_value_validation(self):
        """Raises ValueError when above maximum."""
        from local_deep_research.settings.env_settings import IntegerSetting

        setting = IntegerSetting(
            key="test.int", description="desc", max_value=100
        )
        with pytest.raises(ValueError, match="above maximum"):
            setting._convert_value("200")

    def test_value_at_min_accepted(self):
        """Value exactly at minimum is accepted."""
        from local_deep_research.settings.env_settings import IntegerSetting

        setting = IntegerSetting(
            key="test.int", description="desc", min_value=10
        )
        assert setting._convert_value("10") == 10

    def test_value_at_max_accepted(self):
        """Value exactly at maximum is accepted."""
        from local_deep_research.settings.env_settings import IntegerSetting

        setting = IntegerSetting(
            key="test.int", description="desc", max_value=100
        )
        assert setting._convert_value("100") == 100

    def test_invalid_int_returns_default(self):
        """Invalid integer string returns default."""
        from local_deep_research.settings.env_settings import IntegerSetting

        setting = IntegerSetting(key="test.int", description="desc", default=42)
        result = setting._convert_value("abc")
        assert result == 42


class TestSecretSetting:
    """Tests for SecretSetting class."""

    def test_repr_hides_value(self):
        """Repr hides the actual value."""
        from local_deep_research.settings.env_settings import SecretSetting

        setting = SecretSetting(key="test.secret", description="desc")
        assert "***" in repr(setting)
        assert "test.secret" in repr(setting)

    def test_str_shows_set_status(self):
        """Str shows whether env var is set."""
        from local_deep_research.settings.env_settings import SecretSetting

        setting = SecretSetting(key="test.secret_xyz", description="desc")
        with patch.dict(os.environ, {}, clear=False):
            if setting.env_var in os.environ:
                del os.environ[setting.env_var]
            assert "NOT SET" in str(setting)

    def test_str_shows_set_when_env_present(self):
        """Str shows SET when env var is present."""
        from local_deep_research.settings.env_settings import SecretSetting

        setting = SecretSetting(key="test.secret_set_xyz", description="desc")
        with patch.dict(os.environ, {setting.env_var: "secret_value"}):
            result = str(setting)
            assert "<SET>" in result
            assert "secret_value" not in result


class TestEnumSetting:
    """Tests for EnumSetting class."""

    def test_accepts_valid_value(self):
        """Accepts value in allowed set."""
        from local_deep_research.settings.env_settings import EnumSetting

        setting = EnumSetting(
            key="test.enum",
            description="desc",
            allowed_values={"WAL", "DELETE", "OFF"},
        )
        assert setting._convert_value("wal") == "WAL"

    def test_rejects_invalid_value(self):
        """Raises ValueError for value not in allowed set."""
        from local_deep_research.settings.env_settings import EnumSetting

        setting = EnumSetting(
            key="test.enum",
            description="desc",
            allowed_values={"WAL", "DELETE"},
        )
        with pytest.raises(ValueError, match="not in allowed values"):
            setting._convert_value("INVALID")

    def test_case_insensitive_by_default(self):
        """Case-insensitive matching by default."""
        from local_deep_research.settings.env_settings import EnumSetting

        setting = EnumSetting(
            key="test.enum",
            description="desc",
            allowed_values={"WAL", "DELETE"},
        )
        assert setting._convert_value("wal") == "WAL"
        assert setting._convert_value("WAL") == "WAL"
        assert setting._convert_value("Wal") == "WAL"

    def test_case_sensitive_mode(self):
        """Case-sensitive mode rejects wrong case."""
        from local_deep_research.settings.env_settings import EnumSetting

        setting = EnumSetting(
            key="test.enum",
            description="desc",
            allowed_values={"WAL", "DELETE"},
            case_sensitive=True,
        )
        assert setting._convert_value("WAL") == "WAL"
        with pytest.raises(ValueError):
            setting._convert_value("wal")

    def test_returns_canonical_form(self):
        """Returns canonical (original) form regardless of input case."""
        from local_deep_research.settings.env_settings import EnumSetting

        setting = EnumSetting(
            key="test.enum",
            description="desc",
            allowed_values={"NORMAL", "FULL"},
        )
        assert setting._convert_value("normal") == "NORMAL"
        assert setting._convert_value("NORMAL") == "NORMAL"


class TestEnvSettingBase:
    """Tests for EnvSetting base class behavior."""

    def test_env_var_generation(self):
        """Env var is generated from key with LDR_ prefix."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(
            key="bootstrap.encryption_key", description="desc"
        )
        assert setting.env_var == "LDR_BOOTSTRAP_ENCRYPTION_KEY"

    def test_is_set_returns_false_when_not_set(self):
        """is_set returns False when env var not present."""
        from local_deep_research.settings.env_settings import StringSetting

        setting = StringSetting(key="test.is_set_xyz", description="desc")
        with patch.dict(os.environ, {}, clear=False):
            if setting.env_var in os.environ:
                del os.environ[setting.env_var]
            assert setting.is_set is False

    def test_is_set_returns_true_when_set(self):
        """is_set returns True when env var is present."""
        from local_deep_research.settings.env_settings import StringSetting

        setting = StringSetting(key="test.is_set_yes_xyz", description="desc")
        with patch.dict(os.environ, {setting.env_var: "value"}):
            assert setting.is_set is True

    def test_repr_includes_key_and_env_var(self):
        """Repr includes key and env_var."""
        from local_deep_research.settings.env_settings import BooleanSetting

        setting = BooleanSetting(key="test.repr", description="desc")
        r = repr(setting)
        assert "test.repr" in r
        assert "LDR_TEST_REPR" in r


class TestSettingsRegistry:
    """Tests for SettingsRegistry class."""

    def test_empty_registry(self):
        """New registry has no settings."""
        from local_deep_research.settings.env_settings import SettingsRegistry

        registry = SettingsRegistry()
        assert registry.list_all_settings() == []

    def test_register_category(self):
        """Can register a category of settings."""
        from local_deep_research.settings.env_settings import (
            SettingsRegistry,
            BooleanSetting,
        )

        registry = SettingsRegistry()
        settings = [
            BooleanSetting(key="cat.flag1", description="flag 1"),
            BooleanSetting(key="cat.flag2", description="flag 2"),
        ]
        registry.register_category("test_cat", settings)
        assert "cat.flag1" in registry.list_all_settings()
        assert "cat.flag2" in registry.list_all_settings()

    def test_get_returns_default_for_unknown_key(self):
        """get() returns default for unknown key."""
        from local_deep_research.settings.env_settings import SettingsRegistry

        registry = SettingsRegistry()
        assert registry.get("unknown.key", "fallback") == "fallback"

    def test_is_env_only(self):
        """is_env_only returns True for registered keys."""
        from local_deep_research.settings.env_settings import (
            SettingsRegistry,
            BooleanSetting,
        )

        registry = SettingsRegistry()
        registry.register_category(
            "test",
            [BooleanSetting(key="test.flag", description="desc")],
        )
        assert registry.is_env_only("test.flag") is True
        assert registry.is_env_only("other.key") is False

    def test_get_env_var(self):
        """get_env_var returns env var name for registered key."""
        from local_deep_research.settings.env_settings import (
            SettingsRegistry,
            StringSetting,
        )

        registry = SettingsRegistry()
        registry.register_category(
            "test",
            [StringSetting(key="test.name", description="desc")],
        )
        assert registry.get_env_var("test.name") == "LDR_TEST_NAME"
        assert registry.get_env_var("unknown") is None

    def test_get_all_env_vars(self):
        """get_all_env_vars returns dict of env var to description."""
        from local_deep_research.settings.env_settings import (
            SettingsRegistry,
            BooleanSetting,
        )

        registry = SettingsRegistry()
        registry.register_category(
            "test",
            [BooleanSetting(key="test.a", description="A desc")],
        )
        env_vars = registry.get_all_env_vars()
        assert "LDR_TEST_A" in env_vars
        assert env_vars["LDR_TEST_A"] == "A desc"

    def test_get_category_settings(self):
        """get_category_settings returns settings in a category."""
        from local_deep_research.settings.env_settings import (
            SettingsRegistry,
            BooleanSetting,
        )

        registry = SettingsRegistry()
        settings = [BooleanSetting(key="cat.x", description="x")]
        registry.register_category("mycat", settings)
        assert registry.get_category_settings("mycat") == settings
        assert registry.get_category_settings("unknown") == []

    def test_get_setting_object(self):
        """get_setting_object returns the setting object."""
        from local_deep_research.settings.env_settings import (
            SettingsRegistry,
            StringSetting,
        )

        registry = SettingsRegistry()
        setting = StringSetting(key="test.obj", description="desc")
        registry.register_category("test", [setting])
        assert registry.get_setting_object("test.obj") is setting
        assert registry.get_setting_object("unknown") is None


class TestBootstrapSettings:
    """Tests for bootstrap env_definitions."""

    def test_bootstrap_settings_is_list(self):
        """BOOTSTRAP_SETTINGS is a list."""
        from local_deep_research.settings.env_definitions.bootstrap import (
            BOOTSTRAP_SETTINGS,
        )

        assert isinstance(BOOTSTRAP_SETTINGS, list)

    def test_bootstrap_settings_non_empty(self):
        """BOOTSTRAP_SETTINGS is non-empty."""
        from local_deep_research.settings.env_definitions.bootstrap import (
            BOOTSTRAP_SETTINGS,
        )

        assert len(BOOTSTRAP_SETTINGS) > 0

    def test_all_have_key_attribute(self):
        """All settings have a key attribute."""
        from local_deep_research.settings.env_definitions.bootstrap import (
            BOOTSTRAP_SETTINGS,
        )

        for setting in BOOTSTRAP_SETTINGS:
            assert hasattr(setting, "key")
            assert setting.key.startswith("bootstrap.")

    def test_all_have_env_var_attribute(self):
        """All settings have auto-generated env_var."""
        from local_deep_research.settings.env_definitions.bootstrap import (
            BOOTSTRAP_SETTINGS,
        )

        for setting in BOOTSTRAP_SETTINGS:
            assert hasattr(setting, "env_var")
            assert setting.env_var.startswith("LDR_BOOTSTRAP_")


class TestDbConfigSettings:
    """Tests for db_config env_definitions."""

    def test_db_config_settings_is_list(self):
        """DB_CONFIG_SETTINGS is a list."""
        from local_deep_research.settings.env_definitions.db_config import (
            DB_CONFIG_SETTINGS,
        )

        assert isinstance(DB_CONFIG_SETTINGS, list)

    def test_db_config_settings_non_empty(self):
        """DB_CONFIG_SETTINGS is non-empty."""
        from local_deep_research.settings.env_definitions.db_config import (
            DB_CONFIG_SETTINGS,
        )

        assert len(DB_CONFIG_SETTINGS) > 0

    def test_all_keys_start_with_db_config(self):
        """All settings keys start with db_config."""
        from local_deep_research.settings.env_definitions.db_config import (
            DB_CONFIG_SETTINGS,
        )

        for setting in DB_CONFIG_SETTINGS:
            assert setting.key.startswith("db_config.")

    def test_journal_mode_enum_has_wal(self):
        """Journal mode setting includes WAL."""
        from local_deep_research.settings.env_definitions.db_config import (
            DB_CONFIG_SETTINGS,
        )

        journal_setting = None
        for s in DB_CONFIG_SETTINGS:
            if s.key == "db_config.journal_mode":
                journal_setting = s
                break
        assert journal_setting is not None
        assert "WAL" in journal_setting.allowed_values

    def test_cache_size_has_bounds(self):
        """Cache size setting has min/max bounds."""
        from local_deep_research.settings.env_definitions.db_config import (
            DB_CONFIG_SETTINGS,
        )

        cache_setting = None
        for s in DB_CONFIG_SETTINGS:
            if s.key == "db_config.cache_size_mb":
                cache_setting = s
                break
        assert cache_setting is not None
        assert cache_setting.min_value == 1
        assert cache_setting.max_value == 10000


class TestTestingSettings:
    """Tests for testing env_definitions."""

    def test_testing_settings_is_list(self):
        """TESTING_SETTINGS is a list."""
        from local_deep_research.settings.env_definitions.testing import (
            TESTING_SETTINGS,
        )

        assert isinstance(TESTING_SETTINGS, list)

    def test_has_test_mode_setting(self):
        """Has test_mode setting."""
        from local_deep_research.settings.env_definitions.testing import (
            TESTING_SETTINGS,
        )

        keys = [s.key for s in TESTING_SETTINGS]
        assert "testing.test_mode" in keys

    def test_ci_constant_is_bool(self):
        """CI constant is a boolean."""
        from local_deep_research.settings.env_definitions.testing import CI

        assert isinstance(CI, bool)

    def test_github_actions_constant_is_bool(self):
        """GITHUB_ACTIONS constant is a boolean."""
        from local_deep_research.settings.env_definitions.testing import (
            GITHUB_ACTIONS,
        )

        assert isinstance(GITHUB_ACTIONS, bool)
