"""Step definitions for plugins_loader_coverage.feature.

These steps target specific uncovered lines in
cleveragents/infrastructure/plugins/loader.py:

- Lines 203-209: except block in load_from_entry_points when ep.load() fails
- Lines 242, 244-246: validate_protocol fallback to issubclass on
  instantiation failure
- Lines 247-248: validate_protocol when issubclass raises TypeError
"""

from typing import Protocol, runtime_checkable
from unittest.mock import MagicMock, patch

from behave import given, then, when

from cleveragents.infrastructure.plugins.exceptions import ProtocolMismatchError
from cleveragents.infrastructure.plugins.loader import PluginLoader

# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------


@given("the plugin loader module is imported")
def step_plugin_loader_imported(context):
    """Verify the PluginLoader class is importable."""
    assert PluginLoader is not None
    context.plugin_loader = PluginLoader()


# ---------------------------------------------------------------------------
# Entry-point discovery: failure path (lines 203-209)
# ---------------------------------------------------------------------------


@given("the entry points are mocked with one that raises on load")
def step_mock_failing_entry_point(context):
    """Create a mock entry point whose load() raises an exception."""
    failing_ep = MagicMock()
    failing_ep.name = "broken_plugin"
    failing_ep.value = "some.module:BrokenClass"
    failing_ep.load.side_effect = ImportError("Simulated import failure")

    # Mock the entry_points() call to return a selectable object
    mock_eps = MagicMock()
    mock_eps.select.return_value = [failing_ep]
    context.mock_entry_points = mock_eps
    context.failing_ep = failing_ep


@when("I call load_from_entry_points")
def step_call_load_from_entry_points(context):
    """Call load_from_entry_points with the mocked entry points."""
    with patch(
        "cleveragents.infrastructure.plugins.loader.importlib.metadata.entry_points",
        return_value=context.mock_entry_points,
    ):
        context.discovered = context.plugin_loader.load_from_entry_points()


@then("the plugin result should be an empty descriptor list")
def step_verify_empty_descriptors(context):
    """The failing entry point should have been skipped."""
    assert context.discovered == [], f"Expected empty list, got {context.discovered}"


@then("the failed entry point should have been logged as a warning")
def step_verify_warning_logged(context):
    """Verify the entry point was attempted (load was called)."""
    context.failing_ep.load.assert_called_once()


# ---------------------------------------------------------------------------
# validate_protocol: instantiation fails, issubclass succeeds (lines 242, 244-246)
# ---------------------------------------------------------------------------


@runtime_checkable
class _SampleProtocol(Protocol):
    """A simple runtime-checkable Protocol for testing issubclass fallback."""

    def do_work(self) -> str: ...


@given("I have a class that requires constructor arguments")
def step_class_requires_args(context):
    """Create a class whose __init__ requires mandatory arguments."""

    class RequiresArgs(_SampleProtocol):
        def __init__(self, mandatory_arg):
            self._arg = mandatory_arg

        def do_work(self) -> str:
            return "done"

    context.non_instantiable_class = RequiresArgs


@given("I have a runtime checkable protocol the class satisfies via issubclass")
def step_protocol_satisfied_by_subclass(context):
    """Store the protocol that the class satisfies structurally."""
    context.target_protocol = _SampleProtocol


@when("I call validate_protocol with the non-instantiable class and protocol")
def step_call_validate_protocol_subclass_fallback(context):
    """Call validate_protocol; expect it to fall back to issubclass and succeed."""
    context.validate_result = PluginLoader.validate_protocol(
        context.non_instantiable_class,
        context.target_protocol,
    )


@then("validate_protocol should return True")
def step_verify_validate_true(context):
    """The fallback issubclass check should have returned True."""
    assert context.validate_result is True


# ---------------------------------------------------------------------------
# validate_protocol: instantiation fails, issubclass raises TypeError (lines 247-248)
# ---------------------------------------------------------------------------


@given("I have a class that cannot be instantiated without arguments")
def step_class_cannot_instantiate(context):
    """Create a class whose __init__ raises when called with no args."""

    class NeedsArgs:
        def __init__(self, required):
            self._r = required

    context.non_instantiable_class = NeedsArgs


@given("I have a protocol-like object that causes issubclass to raise TypeError")
def step_protocol_causes_typeerror(context):
    """Create a protocol-like object that causes issubclass to raise TypeError.

    We need an object that:
    - Has a __name__ attribute (so the error message in validate_protocol works)
    - Causes issubclass() to raise TypeError when used as second arg
    - Also causes isinstance() to raise TypeError

    A class with a metaclass that raises TypeError on __instancecheck__
    and __subclasscheck__ achieves this.
    """

    class TypeErrorMeta(type):
        def __instancecheck__(cls, instance):
            raise TypeError("Simulated TypeError from isinstance")

        def __subclasscheck__(cls, instance):
            raise TypeError("Simulated TypeError from issubclass")

    class BadProtocol(metaclass=TypeErrorMeta):
        pass

    context.target_protocol = BadProtocol


@when("I call validate_protocol expecting a mismatch error")
def step_call_validate_protocol_expect_error(context):
    """Call validate_protocol and capture the expected error."""
    try:
        PluginLoader.validate_protocol(
            context.non_instantiable_class,
            context.target_protocol,
        )
        context.validate_error = None
    except ProtocolMismatchError as exc:
        context.validate_error = exc
    except Exception as exc:
        context.validate_error = exc


@then("a plugin-loader ProtocolMismatchError should be raised")
def step_verify_protocol_mismatch(context):
    """Verify that ProtocolMismatchError was raised."""
    assert context.validate_error is not None, "Expected an error but none was raised"
    assert isinstance(context.validate_error, ProtocolMismatchError), (
        f"Expected ProtocolMismatchError, got {type(context.validate_error).__name__}: "
        f"{context.validate_error}"
    )


# ---------------------------------------------------------------------------
# validate_protocol: instantiation fails, issubclass returns False
# ---------------------------------------------------------------------------


@given("I have a runtime checkable protocol the class does not satisfy")
def step_protocol_not_satisfied(context):
    """Store a protocol the class does NOT satisfy."""

    @runtime_checkable
    class UnrelatedProtocol(Protocol):
        def completely_different_method(self) -> int: ...

    context.target_protocol = UnrelatedProtocol
