"""
Consistency tests between the engine registry and the security whitelist.

These tests ensure that:
- Every module_path/class_name in the engine registry is whitelisted
- Every whitelisted module actually exists and can be imported
- Every (module_path, class_name) pair from the registry resolves to a real class

This prevents silent runtime failures when registry and whitelist drift out of sync.
"""

import importlib

import pytest

from local_deep_research.security.module_whitelist import (
    ALLOWED_CLASS_NAMES,
    ALLOWED_MODULE_PATHS,
)
from local_deep_research.web_search_engines.engine_registry import (
    ENGINE_REGISTRY,
)


def _collect_configured_engines():
    """Collect all (source, module_path, class_name) triples from the engine registry.

    The engine registry is the single source of truth for engine module paths
    and class names (previously stored in JSON defaults).

    Also picks up full_search_module / full_search_class pairs used by
    engines that support full-document fetching.
    """
    results = []

    for name, entry in ENGINE_REGISTRY.items():
        results.append(("engine_registry", entry.module_path, entry.class_name))
        if entry.full_search_module and entry.full_search_class:
            results.append(
                (
                    "engine_registry",
                    entry.full_search_module,
                    entry.full_search_class,
                )
            )

    return results


class TestRegistryModulePathsInWhitelist:
    """Every module_path in the engine registry must be in ALLOWED_MODULE_PATHS."""

    def test_registry_module_paths_in_whitelist(self):
        """All registry module_path values are present in the whitelist."""
        engines = _collect_configured_engines()
        assert engines, "No engines found — collector may be broken"

        missing = []
        for source, module_path, _class_name in engines:
            if module_path not in ALLOWED_MODULE_PATHS:
                missing.append(f"{source}: {module_path}")

        assert not missing, (
            "Module paths in engine registry but NOT in ALLOWED_MODULE_PATHS:\n"
            + "\n".join(f"  - {m}" for m in missing)
        )


class TestRegistryClassNamesInWhitelist:
    """Every class_name in the engine registry must be in ALLOWED_CLASS_NAMES."""

    def test_registry_class_names_in_whitelist(self):
        """All registry class_name values are present in the whitelist."""
        engines = _collect_configured_engines()
        assert engines, "No engines found — collector may be broken"

        missing = []
        for source, _module_path, class_name in engines:
            if class_name not in ALLOWED_CLASS_NAMES:
                missing.append(f"{source}: {class_name}")

        assert not missing, (
            "Class names in engine registry but NOT in ALLOWED_CLASS_NAMES:\n"
            + "\n".join(f"  - {m}" for m in missing)
        )


class TestWhitelistedModulesResolve:
    """Every entry in ALLOWED_MODULE_PATHS must be importable."""

    @pytest.mark.parametrize("module_path", sorted(ALLOWED_MODULE_PATHS))
    def test_whitelisted_module_resolves(self, module_path):
        """Whitelisted module '{module_path}' can be imported."""
        try:
            importlib.import_module(
                module_path,
                package="local_deep_research.web_search_engines",
            )
        except ModuleNotFoundError:
            pytest.fail(
                f"Whitelisted module {module_path!r} cannot be imported. "
                f"Was the module deleted or renamed?"
            )


class TestWhitelistedClassesExistInModules:
    """Every (module_path, class_name) pair from the registry must resolve to a real class."""

    def test_whitelisted_classes_exist_in_modules(self):
        """Each registry class exists in its declared module."""
        engines = _collect_configured_engines()
        assert engines, "No engines found — collector may be broken"

        # Deduplicate by (module_path, class_name)
        seen = set()
        failures = []
        for source, module_path, class_name in engines:
            pair = (module_path, class_name)
            if pair in seen:
                continue
            seen.add(pair)

            try:
                mod = importlib.import_module(
                    module_path,
                    package="local_deep_research.web_search_engines",
                )
            except ModuleNotFoundError:
                failures.append(f"{source}: module {module_path!r} not found")
                continue

            if not hasattr(mod, class_name):
                failures.append(
                    f"{source}: class {class_name!r} not found in {module_path!r}"
                )

        assert not failures, (
            "Config references that don't resolve:\n"
            + "\n".join(f"  - {f}" for f in failures)
        )
