"""
Module whitelist for safe dynamic imports.

This module provides secure dynamic import functionality with a strict whitelist
of allowed modules and class names. This prevents arbitrary code execution through
user-controlled configuration values.

Security Features:
- Validates module paths against a whitelist of trusted modules
- Validates class names against a whitelist of legitimate search engine classes
- Prevents loading of arbitrary code through malicious configuration
- Logs security-relevant events for auditing
"""

import importlib
from typing import Optional, Type

from loguru import logger


# Whitelist of allowed module paths for dynamic import.
# Only modules in this list can be loaded dynamically.
# SECURITY: Only relative paths (starting with ".") are allowed.
# This ensures all imports are relative to local_deep_research.web_search_engines
# and prevents arbitrary module imports from other packages.
ALLOWED_MODULE_PATHS: frozenset[str] = frozenset(
    [
        # Relative imports only (relative to local_deep_research.web_search_engines)
        # Absolute paths are NOT allowed for security reasons
        ".engines.full_search",
        ".engines.meta_search_engine",
        ".engines.parallel_search_engine",
        ".engines.search_engine_arxiv",
        ".engines.search_engine_brave",
        ".engines.search_engine_collection",
        ".engines.search_engine_ddg",
        ".engines.search_engine_elasticsearch",
        ".engines.search_engine_exa",
        ".engines.search_engine_github",
        ".engines.search_engine_google_pse",
        ".engines.search_engine_guardian",
        ".engines.search_engine_library",
        ".engines.local_embedding_manager",
        ".engines.search_engine_mojeek",
        ".engines.search_engine_nasa_ads",
        ".engines.search_engine_openalex",
        ".engines.search_engine_paperless",
        ".engines.search_engine_pubmed",
        ".engines.search_engine_retriever",
        ".engines.search_engine_scaleserp",
        ".engines.search_engine_searxng",
        ".engines.search_engine_semantic_scholar",
        ".engines.search_engine_serper",
        ".engines.search_engine_serpapi",
        ".engines.search_engine_tavily",
        ".engines.search_engine_gutenberg",
        ".engines.search_engine_openlibrary",
        ".engines.search_engine_pubchem",
        ".engines.search_engine_stackexchange",
        ".engines.search_engine_wayback",
        ".engines.search_engine_wikipedia",
        ".engines.search_engine_wikinews",
        ".engines.search_engine_zenodo",
        ".search_engine_base",
    ]
)

# Legacy alias for backward compatibility
ALLOWED_MODULES = ALLOWED_MODULE_PATHS


# Whitelist of allowed class names for search engines.
# Only classes in this list can be instantiated through dynamic import.
ALLOWED_CLASS_NAMES: frozenset[str] = frozenset(
    [
        # Search engine implementation classes
        "ArXivSearchEngine",
        "BaseSearchEngine",
        "BraveSearchEngine",
        "CollectionSearchEngine",
        "DuckDuckGoSearchEngine",
        "ElasticsearchSearchEngine",
        "GutenbergSearchEngine",
        "ExaSearchEngine",
        "FullSearchResults",
        "GitHubSearchEngine",
        "GooglePSESearchEngine",
        "GuardianSearchEngine",
        "LibraryRAGSearchEngine",
        "MetaSearchEngine",
        "MojeekSearchEngine",
        "NasaAdsSearchEngine",
        "OpenAlexSearchEngine",
        "OpenLibrarySearchEngine",
        "PaperlessSearchEngine",
        "ParallelSearchEngine",
        "PubChemSearchEngine",
        "PubMedSearchEngine",
        "RetrieverSearchEngine",
        "ScaleSerpSearchEngine",
        "SearXNGSearchEngine",
        "SemanticScholarSearchEngine",
        "SerpAPISearchEngine",
        "SerperSearchEngine",
        "StackExchangeSearchEngine",
        "TavilySearchEngine",
        "WaybackSearchEngine",
        "WikinewsSearchEngine",
        "WikipediaSearchEngine",
        "ZenodoSearchEngine",
    ]
)


class SecurityError(Exception):
    """Raised when a security validation fails during module import."""

    pass


# Legacy alias for backward compatibility
ModuleNotAllowedError = SecurityError


def validate_module_import(module_path: str, class_name: str) -> bool:
    """
    Validate that both module_path and class_name are in their respective whitelists.

    This function provides a security check to ensure that dynamically loaded
    search engine modules are from trusted sources only. It validates:
    1. Module path starts with "." (relative import only)
    2. Module path is in the whitelist
    3. Class name is in the whitelist

    Args:
        module_path: The Python module path (MUST be relative, starting with ".")
        class_name: The class name to import from the module

    Returns:
        True if all validations pass, False otherwise

    Example:
        >>> validate_module_import(".engines.search_engine_brave", "BraveSearchEngine")
        True
        >>> validate_module_import("os", "system")
        False
    """
    if not module_path or not class_name:
        logger.warning(
            "Module validation failed: empty module_path or class_name"
        )
        return False

    # SECURITY: Only allow relative imports (starting with ".")
    # This ensures imports are relative to local_deep_research.web_search_engines
    # and prevents loading arbitrary modules like "os" or "subprocess"
    if not module_path.startswith("."):
        logger.warning(
            f"Security: Rejected non-relative module path: {module_path}. "
            f"Only relative imports (starting with '.') are allowed."
        )
        return False

    module_valid = module_path in ALLOWED_MODULE_PATHS
    class_valid = class_name in ALLOWED_CLASS_NAMES

    if not module_valid:
        logger.warning(f"Module path not in whitelist: {module_path}")

    if not class_valid:
        logger.warning(f"Class name not in whitelist: {class_name}")

    return module_valid and class_valid


def get_safe_module_class(
    module_path: str,
    class_name: str,
    package: Optional[str] = None,
) -> Type:
    """
    Safely import a class from a module, validating against both whitelists.

    This function provides secure dynamic import functionality by:
    1. Validating the module path against a strict whitelist
    2. Validating the class name against a strict whitelist
    3. Only allowing imports from trusted local_deep_research modules
    4. Preventing arbitrary code execution through configuration

    Args:
        module_path: The module path to import from
            (e.g., ".engines.search_engine_brave" for relative imports)
        class_name: The class name to retrieve from the module
        package: Optional package for relative imports. If not provided and
            module_path starts with ".", defaults to
            "local_deep_research.web_search_engines"

    Returns:
        The requested class from the module

    Raises:
        SecurityError: If the module path or class name is not in the whitelist
        ModuleNotFoundError: If the module cannot be imported
        AttributeError: If the class does not exist in the module

    Example:
        >>> cls = get_safe_module_class(".engines.search_engine_brave", "BraveSearchEngine")
        >>> engine = cls(api_key="...")
    """
    # Validate both module path and class name against whitelists
    if not validate_module_import(module_path, class_name):
        logger.error(
            f"Security: Blocked attempt to import non-whitelisted module/class: "
            f"module_path={module_path!r}, class_name={class_name!r}"
        )
        raise SecurityError(
            f"Import blocked: module_path={module_path!r} or class_name={class_name!r} "
            f"is not in the security whitelist. Only trusted local_deep_research "
            f"modules and classes can be dynamically imported."
        )

    # Determine package for relative imports
    if package is None and module_path.startswith("."):
        package = "local_deep_research.web_search_engines"

    # Import the module
    try:
        # bearer:disable python_lang_code_injection
        module = importlib.import_module(module_path, package=package)
    except ModuleNotFoundError:
        logger.exception(f"Failed to import whitelisted module {module_path}")
        raise

    # Get the class from the module
    try:
        engine_class = getattr(module, class_name)
    except AttributeError:
        logger.exception(
            f"Class '{class_name}' not found in module '{module_path}'"
        )
        raise

    logger.debug(f"Successfully loaded {class_name} from {module_path}")
    return engine_class
