"""
Behavioral tests for security/module_whitelist module.

Tests the whitelist validation logic, security error handling,
and module/class name filtering.
"""

import pytest


class TestAllowedModulePaths:
    """Tests for ALLOWED_MODULE_PATHS constant."""

    def test_is_frozenset(self):
        """ALLOWED_MODULE_PATHS is a frozenset."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        assert isinstance(ALLOWED_MODULE_PATHS, frozenset)

    def test_is_non_empty(self):
        """ALLOWED_MODULE_PATHS is non-empty."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        assert len(ALLOWED_MODULE_PATHS) > 0

    def test_all_paths_start_with_dot(self):
        """All paths start with '.' (relative imports only)."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        for path in ALLOWED_MODULE_PATHS:
            assert path.startswith("."), (
                f"Path {path!r} does not start with '.'"
            )

    def test_all_paths_are_strings(self):
        """All paths are strings."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        for path in ALLOWED_MODULE_PATHS:
            assert isinstance(path, str)

    def test_contains_brave_engine(self):
        """Contains brave search engine module."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        assert ".engines.search_engine_brave" in ALLOWED_MODULE_PATHS

    def test_contains_searxng_engine(self):
        """Contains SearXNG search engine module."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        assert ".engines.search_engine_searxng" in ALLOWED_MODULE_PATHS

    def test_contains_base_engine(self):
        """Contains search_engine_base module."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        assert ".search_engine_base" in ALLOWED_MODULE_PATHS

    def test_does_not_contain_os(self):
        """Does not contain 'os' module."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        assert "os" not in ALLOWED_MODULE_PATHS

    def test_does_not_contain_subprocess(self):
        """Does not contain 'subprocess' module."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
        )

        assert "subprocess" not in ALLOWED_MODULE_PATHS

    def test_legacy_alias_matches(self):
        """ALLOWED_MODULES is the same as ALLOWED_MODULE_PATHS."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_MODULE_PATHS,
            ALLOWED_MODULES,
        )

        assert ALLOWED_MODULES is ALLOWED_MODULE_PATHS


class TestAllowedClassNames:
    """Tests for ALLOWED_CLASS_NAMES constant."""

    def test_is_frozenset(self):
        """ALLOWED_CLASS_NAMES is a frozenset."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_CLASS_NAMES,
        )

        assert isinstance(ALLOWED_CLASS_NAMES, frozenset)

    def test_is_non_empty(self):
        """ALLOWED_CLASS_NAMES is non-empty."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_CLASS_NAMES,
        )

        assert len(ALLOWED_CLASS_NAMES) > 0

    def test_all_names_are_strings(self):
        """All class names are strings."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_CLASS_NAMES,
        )

        for name in ALLOWED_CLASS_NAMES:
            assert isinstance(name, str)

    def test_contains_brave_search_engine(self):
        """Contains BraveSearchEngine class."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_CLASS_NAMES,
        )

        assert "BraveSearchEngine" in ALLOWED_CLASS_NAMES

    def test_contains_base_search_engine(self):
        """Contains BaseSearchEngine class."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_CLASS_NAMES,
        )

        assert "BaseSearchEngine" in ALLOWED_CLASS_NAMES

    def test_does_not_contain_generic_names(self):
        """Does not contain generic dangerous class names."""
        from local_deep_research.security.module_whitelist import (
            ALLOWED_CLASS_NAMES,
        )

        assert "Popen" not in ALLOWED_CLASS_NAMES
        assert "system" not in ALLOWED_CLASS_NAMES


class TestSecurityError:
    """Tests for SecurityError exception."""

    def test_is_exception_subclass(self):
        """SecurityError is an Exception subclass."""
        from local_deep_research.security.module_whitelist import SecurityError

        assert issubclass(SecurityError, Exception)

    def test_can_be_raised_and_caught(self):
        """SecurityError can be raised and caught."""
        from local_deep_research.security.module_whitelist import SecurityError

        with pytest.raises(SecurityError):
            raise SecurityError("test")

    def test_message_preserved(self):
        """Error message is preserved."""
        from local_deep_research.security.module_whitelist import SecurityError

        err = SecurityError("blocked import")
        assert str(err) == "blocked import"

    def test_legacy_alias(self):
        """ModuleNotAllowedError is same as SecurityError."""
        from local_deep_research.security.module_whitelist import (
            SecurityError,
            ModuleNotAllowedError,
        )

        assert ModuleNotAllowedError is SecurityError


class TestValidateModuleImport:
    """Tests for validate_module_import function."""

    def test_valid_module_and_class(self):
        """Returns True for whitelisted module and class."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(
            ".engines.search_engine_brave", "BraveSearchEngine"
        )
        assert result is True

    def test_invalid_module_path(self):
        """Returns False for non-whitelisted module."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(
            ".engines.evil_module", "BraveSearchEngine"
        )
        assert result is False

    def test_invalid_class_name(self):
        """Returns False for non-whitelisted class."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(
            ".engines.search_engine_brave", "EvilClass"
        )
        assert result is False

    def test_both_invalid(self):
        """Returns False when both module and class are invalid."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(".evil.module", "EvilClass")
        assert result is False

    def test_empty_module_path(self):
        """Returns False for empty module path."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import("", "BraveSearchEngine")
        assert result is False

    def test_empty_class_name(self):
        """Returns False for empty class name."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(".engines.search_engine_brave", "")
        assert result is False

    def test_none_module_path(self):
        """Returns False for None module path."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(None, "BraveSearchEngine")
        assert result is False

    def test_none_class_name(self):
        """Returns False for None class name."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(".engines.search_engine_brave", None)
        assert result is False

    def test_rejects_absolute_import_os(self):
        """Rejects absolute import of 'os' module."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import("os", "system")
        assert result is False

    def test_rejects_absolute_import_subprocess(self):
        """Rejects absolute import of 'subprocess' module."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import("subprocess", "Popen")
        assert result is False

    def test_rejects_non_relative_whitelisted_path(self):
        """Rejects non-relative path even if it looks similar to whitelisted one."""
        from local_deep_research.security.module_whitelist import (
            validate_module_import,
        )

        result = validate_module_import(
            "engines.search_engine_brave", "BraveSearchEngine"
        )
        assert result is False


class TestGetSafeModuleClass:
    """Tests for get_safe_module_class function security validation."""

    def test_raises_for_non_whitelisted_module(self):
        """Raises SecurityError for non-whitelisted module."""
        from local_deep_research.security.module_whitelist import (
            get_safe_module_class,
            SecurityError,
        )

        with pytest.raises(SecurityError) as exc_info:
            get_safe_module_class(".evil.module", "EvilClass")
        assert "not in the security whitelist" in str(exc_info.value)

    def test_raises_for_non_whitelisted_class(self):
        """Raises SecurityError for non-whitelisted class name."""
        from local_deep_research.security.module_whitelist import (
            get_safe_module_class,
            SecurityError,
        )

        with pytest.raises(SecurityError) as exc_info:
            get_safe_module_class(".engines.search_engine_brave", "EvilClass")
        assert "not in the security whitelist" in str(exc_info.value)

    def test_raises_for_absolute_import(self):
        """Raises SecurityError for absolute import path."""
        from local_deep_research.security.module_whitelist import (
            get_safe_module_class,
            SecurityError,
        )

        with pytest.raises(SecurityError):
            get_safe_module_class("os", "system")

    def test_error_message_includes_module_path(self):
        """Error message includes the rejected module path."""
        from local_deep_research.security.module_whitelist import (
            get_safe_module_class,
            SecurityError,
        )

        with pytest.raises(SecurityError) as exc_info:
            get_safe_module_class(".evil.module", "EvilClass")
        assert ".evil.module" in str(exc_info.value)

    def test_error_message_includes_class_name(self):
        """Error message includes the rejected class name."""
        from local_deep_research.security.module_whitelist import (
            get_safe_module_class,
            SecurityError,
        )

        with pytest.raises(SecurityError) as exc_info:
            get_safe_module_class(".evil.module", "EvilClass")
        assert "EvilClass" in str(exc_info.value)
