"""High-value tests for settings/env_definitions/db_config.py.

Tests the DB_CONFIG_SETTINGS list: IntegerSetting and EnumSetting instances,
their defaults, boundaries, env-var resolution, deprecated aliases, and
validation of invalid values.
"""

import pytest

from local_deep_research.settings.env_definitions.db_config import (
    DB_CONFIG_SETTINGS,
)
from local_deep_research.settings.env_settings import (
    IntegerSetting,
    EnumSetting,
    SettingsRegistry,
)


def _find(key: str):
    """Return the setting object with the given key."""
    for s in DB_CONFIG_SETTINGS:
        if s.key == key:
            return s
    raise KeyError(f"Setting {key!r} not found in DB_CONFIG_SETTINGS")


class TestDbConfigRegistry:
    """Tests for registering DB_CONFIG_SETTINGS in a SettingsRegistry."""

    def test_register_all_settings(self):
        """All settings can be registered under the db_config category."""
        registry = SettingsRegistry()
        registry.register_category("db_config", DB_CONFIG_SETTINGS)
        for s in DB_CONFIG_SETTINGS:
            assert registry.is_env_only(s.key)

    def test_setting_count(self):
        """DB_CONFIG_SETTINGS contains the expected number of settings."""
        assert len(DB_CONFIG_SETTINGS) == 8

    def test_all_keys_start_with_db_config(self):
        """Every key should use the 'db_config.' prefix."""
        for s in DB_CONFIG_SETTINGS:
            assert s.key.startswith("db_config."), f"{s.key} missing prefix"


class TestEnvVarNaming:
    """Verify that the auto-generated env_var matches the canonical form."""

    @pytest.mark.parametrize(
        "key, expected_env_var",
        [
            ("db_config.cache_size_mb", "LDR_DB_CONFIG_CACHE_SIZE_MB"),
            ("db_config.journal_mode", "LDR_DB_CONFIG_JOURNAL_MODE"),
            ("db_config.synchronous", "LDR_DB_CONFIG_SYNCHRONOUS"),
            ("db_config.page_size", "LDR_DB_CONFIG_PAGE_SIZE"),
            ("db_config.kdf_iterations", "LDR_DB_CONFIG_KDF_ITERATIONS"),
            ("db_config.kdf_algorithm", "LDR_DB_CONFIG_KDF_ALGORITHM"),
            ("db_config.hmac_algorithm", "LDR_DB_CONFIG_HMAC_ALGORITHM"),
            (
                "db_config.cipher_memory_security",
                "LDR_DB_CONFIG_CIPHER_MEMORY_SECURITY",
            ),
        ],
    )
    def test_canonical_env_var(self, key, expected_env_var):
        s = _find(key)
        assert s.env_var == expected_env_var


class TestDeprecatedEnvVars:
    """Settings that have a deprecated alias should resolve via it."""

    @pytest.mark.parametrize(
        "key, deprecated_var",
        [
            ("db_config.cache_size_mb", "LDR_DB_CACHE_SIZE_MB"),
            ("db_config.journal_mode", "LDR_DB_JOURNAL_MODE"),
            ("db_config.synchronous", "LDR_DB_SYNCHRONOUS"),
            ("db_config.page_size", "LDR_DB_PAGE_SIZE"),
            ("db_config.kdf_iterations", "LDR_DB_KDF_ITERATIONS"),
            ("db_config.kdf_algorithm", "LDR_DB_KDF_ALGORITHM"),
            ("db_config.hmac_algorithm", "LDR_DB_HMAC_ALGORITHM"),
        ],
    )
    def test_deprecated_alias_stored(self, key, deprecated_var):
        """The deprecated_env_var attribute matches expectations."""
        s = _find(key)
        assert s.deprecated_env_var == deprecated_var

    def test_cipher_memory_security_has_no_deprecated_alias(self):
        s = _find("db_config.cipher_memory_security")
        assert s.deprecated_env_var is None

    def test_deprecated_alias_fallback_returns_value(self, monkeypatch):
        """When only the deprecated alias is set, the value should be resolved."""
        s = _find("db_config.cache_size_mb")
        monkeypatch.delenv(s.env_var, raising=False)
        monkeypatch.setenv(s.deprecated_env_var, "128")
        assert s.get_value() == 128

    def test_canonical_takes_precedence_over_deprecated(self, monkeypatch):
        """When both canonical and deprecated vars are set, canonical wins."""
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "32")
        monkeypatch.setenv(s.deprecated_env_var, "999")
        assert s.get_value() == 32


class TestCacheSizeMb:
    """Tests for db_config.cache_size_mb."""

    def test_type(self):
        assert isinstance(_find("db_config.cache_size_mb"), IntegerSetting)

    def test_default_value(self, monkeypatch):
        s = _find("db_config.cache_size_mb")
        monkeypatch.delenv(s.env_var, raising=False)
        monkeypatch.delenv(s.deprecated_env_var, raising=False)
        assert s.get_value() == 64

    def test_valid_value(self, monkeypatch):
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "256")
        assert s.get_value() == 256

    def test_min_boundary(self, monkeypatch):
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "1")
        assert s.get_value() == 1

    def test_below_min_raises(self, monkeypatch):
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "0")
        with pytest.raises(ValueError, match="below minimum"):
            s.get_value()

    def test_max_boundary(self, monkeypatch):
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "10000")
        assert s.get_value() == 10000

    def test_above_max_raises(self, monkeypatch):
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "10001")
        with pytest.raises(ValueError, match="above maximum"):
            s.get_value()

    def test_non_integer_returns_default(self, monkeypatch):
        """Non-integer strings fall back to the default value."""
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "not_a_number")
        assert s.get_value() == s.default

    def test_negative_value_raises(self, monkeypatch):
        """Negative values are below the minimum."""
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "-10")
        with pytest.raises(ValueError, match="below minimum"):
            s.get_value()


class TestPageSize:
    """Tests for db_config.page_size."""

    def test_default(self, monkeypatch):
        s = _find("db_config.page_size")
        monkeypatch.delenv(s.env_var, raising=False)
        monkeypatch.delenv(s.deprecated_env_var, raising=False)
        assert s.get_value() == 16384

    def test_min_boundary(self, monkeypatch):
        s = _find("db_config.page_size")
        monkeypatch.setenv(s.env_var, "512")
        assert s.get_value() == 512

    def test_below_min_raises(self, monkeypatch):
        s = _find("db_config.page_size")
        monkeypatch.setenv(s.env_var, "256")
        with pytest.raises(ValueError, match="below minimum"):
            s.get_value()

    def test_max_boundary(self, monkeypatch):
        s = _find("db_config.page_size")
        monkeypatch.setenv(s.env_var, "65536")
        assert s.get_value() == 65536

    def test_above_max_raises(self, monkeypatch):
        s = _find("db_config.page_size")
        monkeypatch.setenv(s.env_var, "65537")
        with pytest.raises(ValueError, match="above maximum"):
            s.get_value()


class TestJournalMode:
    """Tests for db_config.journal_mode."""

    def test_type(self):
        assert isinstance(_find("db_config.journal_mode"), EnumSetting)

    def test_default(self, monkeypatch):
        s = _find("db_config.journal_mode")
        monkeypatch.delenv(s.env_var, raising=False)
        monkeypatch.delenv(s.deprecated_env_var, raising=False)
        assert s.get_value() == "WAL"

    @pytest.mark.parametrize(
        "raw_input, expected",
        [
            ("WAL", "WAL"),
            ("wal", "WAL"),
            ("DELETE", "DELETE"),
            ("TRUNCATE", "TRUNCATE"),
            ("PERSIST", "PERSIST"),
            ("MEMORY", "MEMORY"),
            ("OFF", "OFF"),
        ],
    )
    def test_valid_values_case_insensitive(
        self, monkeypatch, raw_input, expected
    ):
        s = _find("db_config.journal_mode")
        monkeypatch.setenv(s.env_var, raw_input)
        assert s.get_value() == expected

    def test_invalid_value_raises(self, monkeypatch):
        s = _find("db_config.journal_mode")
        monkeypatch.setenv(s.env_var, "INVALID_MODE")
        with pytest.raises(ValueError, match="not in allowed values"):
            s.get_value()


class TestSynchronous:
    """Tests for db_config.synchronous."""

    def test_default(self, monkeypatch):
        s = _find("db_config.synchronous")
        monkeypatch.delenv(s.env_var, raising=False)
        monkeypatch.delenv(s.deprecated_env_var, raising=False)
        assert s.get_value() == "NORMAL"

    @pytest.mark.parametrize("value", ["OFF", "NORMAL", "FULL", "EXTRA"])
    def test_all_valid_values(self, monkeypatch, value):
        s = _find("db_config.synchronous")
        monkeypatch.setenv(s.env_var, value)
        assert s.get_value() == value

    def test_invalid_raises(self, monkeypatch):
        s = _find("db_config.synchronous")
        monkeypatch.setenv(s.env_var, "LAZY")
        with pytest.raises(ValueError, match="not in allowed values"):
            s.get_value()


class TestKdfAlgorithm:
    """Tests for db_config.kdf_algorithm."""

    def test_default(self, monkeypatch):
        s = _find("db_config.kdf_algorithm")
        monkeypatch.delenv(s.env_var, raising=False)
        monkeypatch.delenv(s.deprecated_env_var, raising=False)
        assert s.get_value() == "PBKDF2_HMAC_SHA512"

    @pytest.mark.parametrize(
        "value",
        ["PBKDF2_HMAC_SHA512", "PBKDF2_HMAC_SHA256", "PBKDF2_HMAC_SHA1"],
    )
    def test_all_valid_values(self, monkeypatch, value):
        s = _find("db_config.kdf_algorithm")
        monkeypatch.setenv(s.env_var, value)
        assert s.get_value() == value

    def test_invalid_raises(self, monkeypatch):
        s = _find("db_config.kdf_algorithm")
        monkeypatch.setenv(s.env_var, "SCRYPT")
        with pytest.raises(ValueError, match="not in allowed values"):
            s.get_value()


class TestCipherMemorySecurity:
    """Tests for db_config.cipher_memory_security."""

    def test_default(self, monkeypatch):
        s = _find("db_config.cipher_memory_security")
        monkeypatch.delenv(s.env_var, raising=False)
        assert s.get_value() == "OFF"

    @pytest.mark.parametrize(
        "raw_input, expected",
        [("ON", "ON"), ("on", "ON"), ("OFF", "OFF"), ("off", "OFF")],
    )
    def test_valid_values(self, monkeypatch, raw_input, expected):
        s = _find("db_config.cipher_memory_security")
        monkeypatch.setenv(s.env_var, raw_input)
        assert s.get_value() == expected

    def test_invalid_raises(self, monkeypatch):
        s = _find("db_config.cipher_memory_security")
        monkeypatch.setenv(s.env_var, "YES")
        with pytest.raises(ValueError, match="not in allowed values"):
            s.get_value()


class TestRegistryGetIntegration:
    """Test SettingsRegistry.get() for db_config settings."""

    def _make_registry(self):
        r = SettingsRegistry()
        r.register_category("db_config", DB_CONFIG_SETTINGS)
        return r

    def test_get_returns_default_when_env_not_set(self, monkeypatch):
        """When no env var is set, registry.get() returns the setting default."""
        r = self._make_registry()
        s = _find("db_config.cache_size_mb")
        monkeypatch.delenv(s.env_var, raising=False)
        monkeypatch.delenv(s.deprecated_env_var, raising=False)
        assert r.get("db_config.cache_size_mb") == 64

    def test_get_returns_override_default_for_unknown_key(self):
        """Unknown key returns the caller-supplied default."""
        r = self._make_registry()
        assert r.get("db_config.nonexistent", default=42) == 42

    def test_get_returns_caller_default_on_validation_error(self, monkeypatch):
        """When the env var fails validation, get() returns the caller default."""
        r = self._make_registry()
        s = _find("db_config.cache_size_mb")
        monkeypatch.setenv(s.env_var, "0")  # below minimum
        assert r.get("db_config.cache_size_mb", default=99) == 99

    def test_get_env_var_for_known_key(self):
        r = self._make_registry()
        assert (
            r.get_env_var("db_config.cache_size_mb")
            == "LDR_DB_CONFIG_CACHE_SIZE_MB"
        )

    def test_get_env_var_for_unknown_key(self):
        r = self._make_registry()
        assert r.get_env_var("no.such.key") is None
