"""Unit tests for prowler.lib.check.tool_wrapper.

Covers the leaf helper directly (Provider.is_tool_wrapper_provider delegates
to it). Tests the frozenset fast path, the entry-point fallback for external
plug-ins, the broken-plug-in path, the no-match path, and the module-level
cache.
"""

from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture(autouse=True)
def _clear_ep_class_cache():
    """Reset the leaf module's cache between tests so they stay independent."""
    from prowler.lib.check import tool_wrapper

    tool_wrapper._ep_class_cache.clear()
    yield
    tool_wrapper._ep_class_cache.clear()


def _make_entry_point(name, cls):
    """Create a mock entry point whose `load()` returns `cls`."""
    ep = MagicMock()
    ep.name = name
    ep.load.return_value = cls
    return ep


class TestIsToolWrapperProvider:
    """is_tool_wrapper_provider: frozenset + entry-point fallback."""

    @pytest.mark.parametrize("name", ["iac", "llm", "image"])
    def test_returns_true_for_builtin_tool_wrappers(self, name):
        from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider

        assert is_tool_wrapper_provider(name) is True

    @pytest.mark.parametrize("name", ["aws", "azure", "gcp", "github", "kubernetes"])
    def test_returns_false_for_regular_builtins(self, name):
        from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider

        assert is_tool_wrapper_provider(name) is False

    @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
    def test_returns_true_for_external_plugin_with_flag(self, mock_eps):
        from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider

        cls = MagicMock(is_external_tool_provider=True)
        mock_eps.return_value = [_make_entry_point("custom_wrapper", cls)]

        assert is_tool_wrapper_provider("custom_wrapper") is True

    @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
    def test_returns_false_for_external_plugin_without_flag(self, mock_eps):
        from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider

        cls = MagicMock(is_external_tool_provider=False)
        mock_eps.return_value = [_make_entry_point("vanilla_external", cls)]

        assert is_tool_wrapper_provider("vanilla_external") is False

    @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
    def test_returns_false_for_unknown_provider(self, mock_eps):
        from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider

        mock_eps.return_value = []

        assert is_tool_wrapper_provider("does-not-exist") is False

    @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
    def test_builtin_name_shortcircuits_before_loading_same_name_plugin(self, mock_eps):
        """A plug-in registered under a built-in's name cannot flip the
        built-in onto the tool-wrapper path, and its module is never loaded."""
        from prowler.lib.check.tool_wrapper import is_tool_wrapper_provider

        malicious = _make_entry_point("aws", MagicMock(is_external_tool_provider=True))
        mock_eps.return_value = [malicious]

        # `aws` is a built-in, so classification short-circuits to False...
        assert is_tool_wrapper_provider("aws") is False
        # ...and the shadowing plug-in's code is never executed via ep.load().
        malicious.load.assert_not_called()


class TestLoadEpClass:
    """_load_ep_class: cache, broken plug-ins, no-match."""

    @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
    def test_caches_result_across_calls(self, mock_eps):
        from prowler.lib.check.tool_wrapper import _load_ep_class

        cls = MagicMock(is_external_tool_provider=True)
        mock_eps.return_value = [_make_entry_point("cached_one", cls)]

        first = _load_ep_class("cached_one")
        second = _load_ep_class("cached_one")

        assert first is cls
        assert second is cls
        # entry_points consulted only on the first call
        assert mock_eps.call_count == 1

    @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
    def test_returns_none_for_broken_plugin(self, mock_eps):
        from prowler.lib.check.tool_wrapper import _load_ep_class

        broken_ep = MagicMock()
        broken_ep.name = "broken"
        broken_ep.load.side_effect = ImportError("plug-in is broken")
        mock_eps.return_value = [broken_ep]

        assert _load_ep_class("broken") is None

    @patch("prowler.lib.check.tool_wrapper.importlib.metadata.entry_points")
    def test_returns_none_when_no_entry_point_matches(self, mock_eps):
        from prowler.lib.check.tool_wrapper import _load_ep_class

        cls = MagicMock()
        mock_eps.return_value = [_make_entry_point("other_provider", cls)]

        assert _load_ep_class("missing_provider") is None
