"""Test suite for the environment settings system."""

import os
from pathlib import Path
import pytest

from local_deep_research.settings.env_registry import (
    registry,
    get_env_setting,
    is_test_mode,
    use_fallback_llm,
    is_ci_environment,
)
from local_deep_research.settings.manager import SettingsManager


class TestEnvRegistry:
    """Test the environment registry functionality."""

    @pytest.fixture(autouse=True)
    def clean_env(self):
        """Clean environment before each test."""
        # Store original env vars
        original_env = {
            k: v
            for k, v in os.environ.items()
            if k.startswith("LDR_") or k in ["CI", "TESTING"]
        }

        # Clean env vars
        for key in list(os.environ.keys()):
            if key.startswith("LDR_") or key in ["CI", "TESTING"]:
                os.environ.pop(key, None)

        yield

        # Restore original env vars
        for key in list(os.environ.keys()):
            if key.startswith("LDR_") or key in ["CI", "TESTING"]:
                os.environ.pop(key, None)
        for key, value in original_env.items():
            os.environ[key] = value

    def test_registry_has_settings(self):
        """Test that registry has settings registered."""
        all_settings = registry.list_all_settings()
        assert len(all_settings) > 0, "Registry should have settings"
        assert "testing.test_mode" in all_settings
        assert "testing.use_fallback_llm" in all_settings
        assert "bootstrap.encryption_key" in all_settings
        assert "db_config.cache_size_mb" in all_settings

    def test_categories(self):
        """Test that all categories have settings."""
        categories = ["testing", "bootstrap", "db_config"]
        for cat in categories:
            settings = registry.get_category_settings(cat)
            assert len(settings) > 0, f"Category '{cat}' should have settings"

    def test_boolean_setting(self):
        """Test boolean setting conversion."""
        # Test various true values
        for value in [
            "true",
            "True",
            "TRUE",
            "1",
            "yes",
            "YES",
            "on",
            "enabled",
        ]:
            os.environ["LDR_TESTING_TEST_MODE"] = value
            assert is_test_mode(), f"'{value}' should be True"

        # Test various false values
        for value in ["false", "False", "FALSE", "0", "no", "NO", "off", ""]:
            os.environ["LDR_TESTING_TEST_MODE"] = value
            assert not is_test_mode(), f"'{value}' should be False"

    def test_fallback_llm_setting(self):
        """Test fallback LLM setting with defaults."""
        # Test default value when not set
        assert not use_fallback_llm()

        os.environ["LDR_TESTING_USE_FALLBACK_LLM"] = "1"
        assert use_fallback_llm()

        os.environ["LDR_TESTING_USE_FALLBACK_LLM"] = "yes"
        assert use_fallback_llm()

        os.environ.pop("LDR_TESTING_USE_FALLBACK_LLM")
        assert not use_fallback_llm()

    def test_ci_environment(self):
        """Test CI environment detection."""
        assert not is_ci_environment()

        os.environ["CI"] = "true"
        assert is_ci_environment()

        os.environ["CI"] = "false"
        assert not is_ci_environment()

    def test_integer_setting(self):
        """Test integer setting conversion and validation."""
        os.environ["LDR_DB_CONFIG_CACHE_SIZE_MB"] = "512"
        cache_size = get_env_setting("db_config.cache_size_mb")
        assert cache_size == 512

        # Test invalid integer returns default
        os.environ["LDR_DB_CONFIG_CACHE_SIZE_MB"] = "invalid"
        cache_size = get_env_setting("db_config.cache_size_mb", 100)
        assert cache_size == 100

        # Test when not set uses default from setting definition
        os.environ.pop("LDR_DB_CONFIG_CACHE_SIZE_MB", None)
        cache_size = get_env_setting("db_config.cache_size_mb")
        assert cache_size == 100  # Default from db_config.py

    def test_enum_setting(self):
        """Test enum setting with case-insensitive matching."""
        # Test lowercase input returns canonical form
        os.environ["LDR_DB_CONFIG_JOURNAL_MODE"] = "wal"
        journal_mode = get_env_setting("db_config.journal_mode")
        assert journal_mode == "WAL"

        os.environ["LDR_DB_CONFIG_JOURNAL_MODE"] = "TRUNCATE"
        journal_mode = get_env_setting("db_config.journal_mode")
        assert journal_mode == "TRUNCATE"

        # Test default when not set
        os.environ.pop("LDR_DB_CONFIG_JOURNAL_MODE", None)
        journal_mode = get_env_setting("db_config.journal_mode")
        assert journal_mode == "WAL"  # Default value

    def test_path_setting(self):
        """Test path setting expansion."""
        test_path = "/tmp/test_ldr_path"
        os.environ["LDR_BOOTSTRAP_DATA_DIR"] = test_path
        data_dir = get_env_setting("bootstrap.data_dir")
        assert data_dir == test_path

        # Test home expansion
        os.environ["LDR_BOOTSTRAP_DATA_DIR"] = "~/test_path"
        data_dir = get_env_setting("bootstrap.data_dir")
        assert data_dir.startswith(str(Path.home()))
        assert "test_path" in data_dir

    def test_secret_setting(self):
        """Test secret setting hides values."""
        os.environ["LDR_BOOTSTRAP_ENCRYPTION_KEY"] = "secret_key_123"
        setting_obj = registry.get_setting_object("bootstrap.encryption_key")

        # Value should be hidden in string representation
        assert "secret_key_123" not in str(setting_obj)
        assert "SET" in str(setting_obj)

        # But value should still be retrievable
        value = get_env_setting("bootstrap.encryption_key")
        assert value == "secret_key_123"


class TestSettingsManagerIntegration:
    """Test SettingsManager integration with env settings."""

    @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_env_only_setting(self):
        """Test getting env-only settings through SettingsManager."""
        sm = SettingsManager(db_session=None)

        os.environ["LDR_TESTING_TEST_MODE"] = "true"
        test_mode = sm.get_setting("testing.test_mode")
        assert test_mode

        # Test with default when env var not set
        result = sm.get_setting("testing.use_fallback_llm", default=True)
        assert (
            not result
        )  # Env setting default (False) overrides our param default

        os.environ["LDR_TESTING_USE_FALLBACK_LLM"] = "false"
        result = sm.get_setting("testing.use_fallback_llm", default=True)
        assert not result  # Env var overrides default

    def test_is_env_only_setting(self):
        """Test checking if a setting is env-only."""
        sm = SettingsManager(db_session=None)

        # These should be env-only
        assert sm.is_env_only_setting("testing.test_mode")
        assert sm.is_env_only_setting("testing.use_fallback_llm")
        assert sm.is_env_only_setting("bootstrap.encryption_key")
        assert sm.is_env_only_setting("db_config.cache_size_mb")

        # These should NOT be env-only (regular settings)
        assert not sm.is_env_only_setting("app.debug")
        assert not sm.is_env_only_setting("llm.provider")
        assert not sm.is_env_only_setting("search.max_concurrent")

    def test_get_bootstrap_vars(self):
        """Test getting bootstrap environment variables."""
        sm = SettingsManager(db_session=None)

        bootstrap_vars = sm.get_bootstrap_env_vars()

        # Should include bootstrap settings
        assert "LDR_BOOTSTRAP_ENCRYPTION_KEY" in bootstrap_vars
        assert "LDR_BOOTSTRAP_DATA_DIR" in bootstrap_vars
        assert "LDR_BOOTSTRAP_CONFIG_DIR" in bootstrap_vars

        # Should include db_config settings
        assert "LDR_DB_CONFIG_CACHE_SIZE_MB" in bootstrap_vars
        assert "LDR_DB_CONFIG_JOURNAL_MODE" in bootstrap_vars

        # Should have a reasonable number of vars
        assert len(bootstrap_vars) >= 10

    def test_is_bootstrap_env_var(self):
        """Test checking if an env var is a bootstrap variable."""
        sm = SettingsManager(db_session=None)

        # Bootstrap vars
        assert sm.is_bootstrap_env_var("LDR_BOOTSTRAP_ENCRYPTION_KEY")
        assert sm.is_bootstrap_env_var("LDR_DB_CONFIG_PAGE_SIZE")
        assert sm.is_bootstrap_env_var("LDR_BOOTSTRAP_DATA_DIR")

        # Testing vars are in ALL_ENV_ONLY_VARS but not specifically bootstrap
        # They are allowed by the pre-commit hook but not considered bootstrap vars
        assert not sm.is_bootstrap_env_var("LDR_TESTING_TEST_MODE")
        assert not sm.is_bootstrap_env_var("LDR_TESTING_USE_FALLBACK_LLM")

        # Non-existent var
        assert not sm.is_bootstrap_env_var("RANDOM_VAR")
        assert not sm.is_bootstrap_env_var("SOME_OTHER_VAR")


class TestEnvVarMapping:
    """Test environment variable name mapping."""

    def test_get_env_var_for_setting(self):
        """Test getting env var name for a setting."""
        assert (
            registry.get_env_var("testing.test_mode") == "LDR_TESTING_TEST_MODE"
        )
        assert (
            registry.get_env_var("testing.use_fallback_llm")
            == "LDR_TESTING_USE_FALLBACK_LLM"
        )
        assert (
            registry.get_env_var("bootstrap.encryption_key")
            == "LDR_BOOTSTRAP_ENCRYPTION_KEY"
        )
        assert (
            registry.get_env_var("db_config.cache_size_mb")
            == "LDR_DB_CONFIG_CACHE_SIZE_MB"
        )
        assert registry.get_env_var("nonexistent.setting") is None

    def test_get_all_env_vars(self):
        """Test getting all environment variables."""
        all_env_vars = registry.get_all_env_vars()

        # Should have a reasonable number
        assert len(all_env_vars) >= 15

        # Should include key variables
        assert "LDR_TESTING_TEST_MODE" in all_env_vars
        assert "LDR_TESTING_USE_FALLBACK_LLM" in all_env_vars
        assert "LDR_BOOTSTRAP_ENCRYPTION_KEY" in all_env_vars
        # CI and TESTING are external vars, not in registry anymore

    def test_category_specific_vars(self):
        """Test getting category-specific environment variables."""
        # Testing vars
        testing_vars = registry.get_testing_vars()
        assert (
            len(testing_vars) >= 2
        )  # We only have test_mode and use_fallback_llm now
        assert "LDR_TESTING_TEST_MODE" in testing_vars
        assert "LDR_TESTING_USE_FALLBACK_LLM" in testing_vars
        # CI and TESTING are now external vars, not in the registry

        # Bootstrap vars (includes db_config)
        bootstrap_vars = registry.get_bootstrap_vars()
        assert len(bootstrap_vars) >= 10
        assert "LDR_BOOTSTRAP_ENCRYPTION_KEY" in bootstrap_vars
        assert "LDR_BOOTSTRAP_DATA_DIR" in bootstrap_vars
        assert "LDR_DB_CONFIG_CACHE_SIZE_MB" in bootstrap_vars
        assert "LDR_DB_CONFIG_JOURNAL_MODE" in bootstrap_vars

    def test_is_env_only(self):
        """Test checking if a setting is env-only."""
        # These should be env-only
        assert registry.is_env_only("testing.test_mode")
        assert registry.is_env_only("bootstrap.encryption_key")
        assert registry.is_env_only("db_config.cache_size_mb")

        # These should not exist in env registry
        assert not registry.is_env_only("app.debug")
        assert not registry.is_env_only("nonexistent.setting")
