"""Step definitions for plugin_extension_points.feature.

Tests all 30 spec-defined extension points: registration, lookup
by name, lookup by category, protocol type correctness, entry point
discovery, context subsystem wiring, and idempotent registration.

Based on issue #939.
"""

from __future__ import annotations

from typing import Any

from behave import given, then, when
from behave.runner import Context

from cleveragents.infrastructure.plugins.extension_catalog import (
    TOTAL_EXTENSION_POINTS,
    get_extension_point_definitions,
    get_extension_points_by_category,
    register_all_extension_points,
)
from cleveragents.infrastructure.plugins.extension_protocols import (
    ALL_EXTENSION_PROTOCOLS,
    A2AExtensionMethodExtension,
    A2ATransportExtension,
    ConfigSourceExtension,
    ConfigValidatorExtension,
    ContextPipelineComponentExtension,
    ContextStorageBackendExtension,
    ContextStrategyExtension,
    EventFilterExtension,
    EventHandlerExtension,
    OutputFormatExtension,
    OutputMaterializerExtension,
    OutputRendererExtension,
    ResourceResolverExtension,
    ResourceTypeHandlerExtension,
    SafetyGuardrailExtension,
    SkillProviderExtension,
    SkillTemplateExtension,
    ToolMiddlewareExtension,
    ToolProviderExtension,
    ValidationRuleProviderExtension,
    ValidationRunnerExtension,
)
from cleveragents.infrastructure.plugins.manager import PluginManager
from cleveragents.infrastructure.plugins.types import ExtensionPoint

# ---------------------------------------------------------------------------
# Fresh PluginManager
# ---------------------------------------------------------------------------


@given("a fresh PluginManager for extension point registration")
def step_fresh_pm(context: Context) -> None:
    context.ep_manager = PluginManager()


@given("a PluginManager with all extension points registered")
def step_pm_with_all_eps(context: Context) -> None:
    context.ep_manager = PluginManager()
    register_all_extension_points(context.ep_manager)


# ---------------------------------------------------------------------------
# Register all extension points
# ---------------------------------------------------------------------------


@when("I register all spec-defined extension points")
def step_register_all(context: Context) -> None:
    context.ep_registered_count = register_all_extension_points(
        context.ep_manager,
    )


@when("I register all spec-defined extension points again")
def step_register_all_again(context: Context) -> None:
    register_all_extension_points(context.ep_manager)


@then("exactly {count:d} extension points should be registered")
def step_exactly_n_registered(context: Context, count: int) -> None:
    eps = context.ep_manager.list_extension_points()
    assert len(eps) == count, f"Expected {count}, got {len(eps)}"


# ---------------------------------------------------------------------------
# Extension point definitions
# ---------------------------------------------------------------------------


@when("I retrieve the extension point definitions")
def step_retrieve_defs(context: Context) -> None:
    context.ep_definitions = get_extension_point_definitions()


@then("I should get exactly {count:d} extension point definitions")
def step_defs_count(context: Context, count: int) -> None:
    assert len(context.ep_definitions) == count, (
        f"Expected {count}, got {len(context.ep_definitions)}"
    )


@then("each definition should have a non-empty name")
def step_each_def_has_name(context: Context) -> None:
    for ep in context.ep_definitions:
        assert ep.name, f"Extension point has empty name: {ep}"


@then("each definition should have a protocol_type")
def step_each_def_has_protocol(context: Context) -> None:
    for ep in context.ep_definitions:
        assert ep.protocol_type is not None, (
            f"Extension point {ep.name} has no protocol_type"
        )


@then("each definition should have a non-empty description")
def step_each_def_has_description(context: Context) -> None:
    for ep in context.ep_definitions:
        assert ep.description, f"Extension point {ep.name} has empty description"


# ---------------------------------------------------------------------------
# Lookup by name
# ---------------------------------------------------------------------------


@when('I look up extension point "{name}"')
def step_lookup_by_name(context: Context, name: str) -> None:
    eps = context.ep_manager.list_extension_points()
    matches = [ep for ep in eps if ep.name == name]
    context.ep_lookup_result: ExtensionPoint | None = matches[0] if matches else None


@then('the looked-up extension point name should be "{name}"')
def step_lookup_name_is(context: Context, name: str) -> None:
    assert context.ep_lookup_result is not None, f"Extension point '{name}' not found"
    assert context.ep_lookup_result.name == name


@then("the looked-up extension point should be None")
def step_lookup_is_none(context: Context) -> None:
    assert context.ep_lookup_result is None


# ---------------------------------------------------------------------------
# Lookup by category
# ---------------------------------------------------------------------------


@when("I retrieve extension points grouped by category")
def step_grouped_by_category(context: Context) -> None:
    context.ep_categories = get_extension_points_by_category()


@then('the "{category}" category should have {count:d} extension points')
def step_category_count(context: Context, category: str, count: int) -> None:
    actual = len(context.ep_categories.get(category, []))
    assert actual == count, f"Category '{category}': expected {count}, got {actual}"


# ---------------------------------------------------------------------------
# Protocol type correctness
# ---------------------------------------------------------------------------


@then('the protocol type name should be "{proto_name}"')
def step_protocol_type_name(context: Context, proto_name: str) -> None:
    assert context.ep_lookup_result is not None
    actual = context.ep_lookup_result.protocol_type.__name__
    assert actual == proto_name, f"Expected '{proto_name}', got '{actual}'"


# ---------------------------------------------------------------------------
# Entry point discovery
# ---------------------------------------------------------------------------


@when('I discover extension point plugins from group "{group}"')
def step_discover_ep_group(context: Context, group: str) -> None:
    context.ep_discovered = context.ep_manager.discover(group=group)


@then("the entry point discovered list should be empty")
def step_ep_discovered_empty(context: Context) -> None:
    assert len(context.ep_discovered) == 0


# ---------------------------------------------------------------------------
# TOTAL_EXTENSION_POINTS constant
# ---------------------------------------------------------------------------


@then("the TOTAL_EXTENSION_POINTS constant should equal {value:d}")
def step_total_constant(context: Context, value: int) -> None:
    assert value == TOTAL_EXTENSION_POINTS, (
        f"Expected {value}, got {TOTAL_EXTENSION_POINTS}"
    )


# ---------------------------------------------------------------------------
# Context extension point wiring in ACMSPipeline
# ---------------------------------------------------------------------------


@given("an ACMSPipeline wired with the plugin manager")
def step_pipeline_with_pm(context: Context) -> None:
    from cleveragents.application.services.acms_service import ACMSPipeline

    context.ep_pipeline = ACMSPipeline(
        plugin_manager=context.ep_manager,
    )


@given("an ACMSPipeline without a plugin manager")
def step_pipeline_without_pm(context: Context) -> None:
    from cleveragents.application.services.acms_service import ACMSPipeline

    context.ep_pipeline = ACMSPipeline()


@then("the pipeline should report {count:d} context extension points")
def step_pipeline_context_eps(context: Context, count: int) -> None:
    actual = len(context.ep_pipeline.context_extension_points)
    assert actual == count, f"Expected {count} context extension points, got {actual}"


# ---------------------------------------------------------------------------
# Runtime-checkable protocols
# ---------------------------------------------------------------------------


@when("I retrieve all extension protocol types")
def step_retrieve_all_protocols(context: Context) -> None:
    context.ep_protocol_map = ALL_EXTENSION_PROTOCOLS


@then("every protocol should be runtime-checkable")
def step_all_protocols_runtime_checkable(context: Context) -> None:
    for name, proto in context.ep_protocol_map.items():
        # runtime_checkable protocols have _is_runtime_protocol = True
        is_rc: Any = getattr(proto, "_is_runtime_protocol", False)
        assert is_rc, f"Protocol {name} ({proto.__name__}) is not runtime-checkable"


# ---------------------------------------------------------------------------
# registry_key matches category
# ---------------------------------------------------------------------------


@then("each definition registry_key should match its name category prefix")
def step_registry_key_matches(context: Context) -> None:
    for ep in context.ep_definitions:
        expected_category = ep.name.split(".")[0]
        assert ep.registry_key == expected_category, (
            f"Extension point '{ep.name}': registry_key '{ep.registry_key}' "
            f"does not match expected category '{expected_category}'"
        )


# ---------------------------------------------------------------------------
# Concrete protocol implementations for coverage
# ---------------------------------------------------------------------------


class _StubContextStrategy:
    @property
    def name(self) -> str:
        return "stub"

    def can_handle(self, request: Any) -> float:
        return 1.0

    def assemble(self, fragments: Any, budget: Any) -> list[Any]:
        return []


class _StubPipelineComponent:
    @property
    def component_name(self) -> str:
        return "stub_component"

    def process(self, fragments: Any, context: Any) -> list[Any]:
        return list(fragments)


class _StubStorageBackend:
    def __init__(self) -> None:
        self._store: dict[str, Any] = {}

    @property
    def backend_type(self) -> str:
        return "memory"

    def store(self, key: str, data: Any) -> None:
        self._store[key] = data

    def retrieve(self, key: str) -> Any:
        return self._store.get(key)


class _StubOutputRenderer:
    @property
    def format_name(self) -> str:
        return "text"

    def render(self, data: Any) -> str:
        return str(data)


class _StubOutputMaterializer:
    @property
    def target_type(self) -> str:
        return "stdout"

    def materialize(self, content: str, destination: str) -> None:
        pass


class _StubOutputFormat:
    @property
    def format_id(self) -> str:
        return "custom"

    def serialize(self, data: Any) -> str:
        return str(data)

    def deserialize(self, raw: str) -> Any:
        return raw


class _StubValidationRunner:
    @property
    def runner_name(self) -> str:
        return "stub_runner"

    def validate(self, target: Any) -> list[Any]:
        return []


class _StubValidationRuleProvider:
    @property
    def provider_name(self) -> str:
        return "stub_provider"

    def get_rules(self) -> list[Any]:
        return []


class _StubToolProvider:
    @property
    def provider_name(self) -> str:
        return "stub_tools"

    def list_tools(self) -> list[str]:
        return ["tool1"]

    def invoke(self, tool_name: str, arguments: Any) -> Any:
        return None


class _StubToolMiddleware:
    @property
    def middleware_name(self) -> str:
        return "stub_mw"

    def before_invoke(self, tool_name: str, arguments: Any) -> dict[str, Any]:
        return dict(arguments)

    def after_invoke(self, tool_name: str, result: Any) -> Any:
        return result


class _StubSkillProvider:
    @property
    def provider_name(self) -> str:
        return "stub_skills"

    def list_skills(self) -> list[str]:
        return ["skill1"]

    def get_skill(self, skill_name: str) -> Any:
        return None


class _StubSkillTemplate:
    @property
    def template_name(self) -> str:
        return "stub_template"

    def render(self, context: Any) -> str:
        return "rendered"


class _StubResourceResolver:
    @property
    def resolver_name(self) -> str:
        return "stub_resolver"

    def resolve(self, uri: str) -> Any:
        return None


class _StubResourceTypeHandler:
    @property
    def handled_type(self) -> str:
        return "file"

    def can_handle(self, resource_type: str) -> bool:
        return resource_type == "file"

    def load(self, uri: str) -> Any:
        return None


class _StubA2ATransport:
    @property
    def transport_name(self) -> str:
        return "stub_transport"

    def send(self, destination: str, payload: Any) -> None:
        pass

    def receive(self) -> Any:
        return None


class _StubA2AExtensionMethod:
    @property
    def method_name(self) -> str:
        return "stub_method"

    def execute(self, params: Any) -> Any:
        return None


class _StubEventHandler:
    @property
    def handler_name(self) -> str:
        return "stub_handler"

    def handle(self, event: Any) -> None:
        pass


class _StubEventFilter:
    @property
    def filter_name(self) -> str:
        return "stub_filter"

    def should_propagate(self, event: Any) -> bool:
        return True


class _StubConfigSource:
    @property
    def source_name(self) -> str:
        return "stub_source"

    def load(self) -> dict[str, Any]:
        return {}


class _StubConfigValidator:
    @property
    def validator_name(self) -> str:
        return "stub_validator"

    def validate(self, config: Any) -> list[str]:
        return []


class _StubSafetyGuardrail:
    @property
    def guardrail_name(self) -> str:
        return "stub_guardrail"

    def check(self, action: Any) -> bool:
        return True

    def explain(self) -> str:
        return "Always allows"


# --- Context Strategy ---


@given("a concrete ContextStrategyExtension implementation")
def step_concrete_ctx_strategy(context: Context) -> None:
    context.ep_impl = _StubContextStrategy()


@then("it should be recognized as a ContextStrategyExtension instance")
def step_isinstance_ctx_strategy(context: Context) -> None:
    assert isinstance(context.ep_impl, ContextStrategyExtension)


@then("calling its protocol methods should succeed")
def step_call_ctx_strategy_methods(context: Context) -> None:
    impl = context.ep_impl
    assert impl.name == "stub"
    assert impl.can_handle({}) == 1.0
    assert impl.assemble([], None) == []


# --- Pipeline Component ---


@given("a concrete ContextPipelineComponentExtension implementation")
def step_concrete_pipeline(context: Context) -> None:
    context.ep_impl = _StubPipelineComponent()


@then("it should be recognized as a ContextPipelineComponentExtension instance")
def step_isinstance_pipeline(context: Context) -> None:
    assert isinstance(context.ep_impl, ContextPipelineComponentExtension)


@then("calling its pipeline component methods should succeed")
def step_call_pipeline_methods(context: Context) -> None:
    impl = context.ep_impl
    assert impl.component_name == "stub_component"
    assert impl.process(["a", "b"], {}) == ["a", "b"]


# --- Storage Backend ---


@given("a concrete ContextStorageBackendExtension implementation")
def step_concrete_storage(context: Context) -> None:
    context.ep_impl = _StubStorageBackend()


@then("it should be recognized as a ContextStorageBackendExtension instance")
def step_isinstance_storage(context: Context) -> None:
    assert isinstance(context.ep_impl, ContextStorageBackendExtension)


@then("calling its storage methods should succeed")
def step_call_storage_methods(context: Context) -> None:
    impl = context.ep_impl
    assert impl.backend_type == "memory"
    impl.store("k", "v")
    assert impl.retrieve("k") == "v"


# --- Output Renderer ---


@given("a concrete OutputRendererExtension implementation")
def step_concrete_renderer(context: Context) -> None:
    context.ep_impl = _StubOutputRenderer()


@then("it should be recognized as an OutputRendererExtension instance")
def step_isinstance_renderer(context: Context) -> None:
    assert isinstance(context.ep_impl, OutputRendererExtension)


@then("calling its renderer methods should succeed")
def step_call_renderer_methods(context: Context) -> None:
    impl = context.ep_impl
    assert impl.format_name == "text"
    assert impl.render(42) == "42"


# --- Output Materializer ---


@given("a concrete OutputMaterializerExtension implementation")
def step_concrete_materializer(context: Context) -> None:
    context.ep_impl = _StubOutputMaterializer()


@then("it should be recognized as an OutputMaterializerExtension instance")
def step_isinstance_materializer(context: Context) -> None:
    assert isinstance(context.ep_impl, OutputMaterializerExtension)


# --- Output Format ---


@given("a concrete OutputFormatExtension implementation")
def step_concrete_format(context: Context) -> None:
    context.ep_impl = _StubOutputFormat()


@then("it should be recognized as an OutputFormatExtension instance")
def step_isinstance_format(context: Context) -> None:
    assert isinstance(context.ep_impl, OutputFormatExtension)


# --- Validation Runner ---


@given("a concrete ValidationRunnerExtension implementation")
def step_concrete_val_runner(context: Context) -> None:
    context.ep_impl = _StubValidationRunner()


@then("it should be recognized as a ValidationRunnerExtension instance")
def step_isinstance_val_runner(context: Context) -> None:
    assert isinstance(context.ep_impl, ValidationRunnerExtension)


# --- Validation Rule Provider ---


@given("a concrete ValidationRuleProviderExtension implementation")
def step_concrete_val_rule(context: Context) -> None:
    context.ep_impl = _StubValidationRuleProvider()


@then("it should be recognized as a ValidationRuleProviderExtension instance")
def step_isinstance_val_rule(context: Context) -> None:
    assert isinstance(context.ep_impl, ValidationRuleProviderExtension)


# --- Tool Provider ---


@given("a concrete ToolProviderExtension implementation")
def step_concrete_tool_provider(context: Context) -> None:
    context.ep_impl = _StubToolProvider()


@then("it should be recognized as a ToolProviderExtension instance")
def step_isinstance_tool_provider(context: Context) -> None:
    assert isinstance(context.ep_impl, ToolProviderExtension)


# --- Tool Middleware ---


@given("a concrete ToolMiddlewareExtension implementation")
def step_concrete_tool_mw(context: Context) -> None:
    context.ep_impl = _StubToolMiddleware()


@then("it should be recognized as a ToolMiddlewareExtension instance")
def step_isinstance_tool_mw(context: Context) -> None:
    assert isinstance(context.ep_impl, ToolMiddlewareExtension)


# --- Skill Provider ---


@given("a concrete SkillProviderExtension implementation")
def step_concrete_skill_provider(context: Context) -> None:
    context.ep_impl = _StubSkillProvider()


@then("it should be recognized as a SkillProviderExtension instance")
def step_isinstance_skill_provider(context: Context) -> None:
    assert isinstance(context.ep_impl, SkillProviderExtension)


# --- Skill Template ---


@given("a concrete SkillTemplateExtension implementation")
def step_concrete_skill_template(context: Context) -> None:
    context.ep_impl = _StubSkillTemplate()


@then("it should be recognized as a SkillTemplateExtension instance")
def step_isinstance_skill_template(context: Context) -> None:
    assert isinstance(context.ep_impl, SkillTemplateExtension)


# --- Resource Resolver ---


@given("a concrete ResourceResolverExtension implementation")
def step_concrete_res_resolver(context: Context) -> None:
    context.ep_impl = _StubResourceResolver()


@then("it should be recognized as a ResourceResolverExtension instance")
def step_isinstance_res_resolver(context: Context) -> None:
    assert isinstance(context.ep_impl, ResourceResolverExtension)


# --- Resource Type Handler ---


@given("a concrete ResourceTypeHandlerExtension implementation")
def step_concrete_res_handler(context: Context) -> None:
    context.ep_impl = _StubResourceTypeHandler()


@then("it should be recognized as a ResourceTypeHandlerExtension instance")
def step_isinstance_res_handler(context: Context) -> None:
    assert isinstance(context.ep_impl, ResourceTypeHandlerExtension)


# --- A2A Transport ---


@given("a concrete A2ATransportExtension implementation")
def step_concrete_a2a_transport(context: Context) -> None:
    context.ep_impl = _StubA2ATransport()


@then("it should be recognized as an A2ATransportExtension instance")
def step_isinstance_a2a_transport(context: Context) -> None:
    assert isinstance(context.ep_impl, A2ATransportExtension)


# --- A2A Extension Method ---


@given("a concrete A2AExtensionMethodExtension implementation")
def step_concrete_a2a_method(context: Context) -> None:
    context.ep_impl = _StubA2AExtensionMethod()


@then("it should be recognized as an A2AExtensionMethodExtension instance")
def step_isinstance_a2a_method(context: Context) -> None:
    assert isinstance(context.ep_impl, A2AExtensionMethodExtension)


# --- Event Handler ---


@given("a concrete EventHandlerExtension implementation")
def step_concrete_event_handler(context: Context) -> None:
    context.ep_impl = _StubEventHandler()


@then("it should be recognized as an EventHandlerExtension instance")
def step_isinstance_event_handler(context: Context) -> None:
    assert isinstance(context.ep_impl, EventHandlerExtension)


# --- Event Filter ---


@given("a concrete EventFilterExtension implementation")
def step_concrete_event_filter(context: Context) -> None:
    context.ep_impl = _StubEventFilter()


@then("it should be recognized as an EventFilterExtension instance")
def step_isinstance_event_filter(context: Context) -> None:
    assert isinstance(context.ep_impl, EventFilterExtension)


# --- Config Source ---


@given("a concrete ConfigSourceExtension implementation")
def step_concrete_config_source(context: Context) -> None:
    context.ep_impl = _StubConfigSource()


@then("it should be recognized as a ConfigSourceExtension instance")
def step_isinstance_config_source(context: Context) -> None:
    assert isinstance(context.ep_impl, ConfigSourceExtension)


# --- Config Validator ---


@given("a concrete ConfigValidatorExtension implementation")
def step_concrete_config_validator(context: Context) -> None:
    context.ep_impl = _StubConfigValidator()


@then("it should be recognized as a ConfigValidatorExtension instance")
def step_isinstance_config_validator(context: Context) -> None:
    assert isinstance(context.ep_impl, ConfigValidatorExtension)


# --- Safety Guardrail ---


@given("a concrete SafetyGuardrailExtension implementation")
def step_concrete_safety(context: Context) -> None:
    context.ep_impl = _StubSafetyGuardrail()


@then("it should be recognized as a SafetyGuardrailExtension instance")
def step_isinstance_safety(context: Context) -> None:
    assert isinstance(context.ep_impl, SafetyGuardrailExtension)


@then("calling its guardrail methods should succeed")
def step_call_safety_methods(context: Context) -> None:
    impl = context.ep_impl
    assert impl.guardrail_name == "stub_guardrail"
    assert impl.check("some_action") is True
    assert impl.explain() == "Always allows"
