"""Step definitions for resource_type_extension_interface.feature."""

from __future__ import annotations

from typing import Any

from behave import given, then, when


def _make_resext_class(
    *,
    skip_provision: bool = False,
    skip_deprovision: bool = False,
    skip_status: bool = False,
    skip_validate_config: bool = False,
    skip_to_dict: bool = False,
) -> type:
    """Return a concrete ResourceType subclass, optionally omitting methods."""
    from cleveragents.resources import ResourceConfig, ResourceStatus, ResourceType

    methods: dict[str, Any] = {}

    if not skip_provision:

        def provision(self: Any, config: ResourceConfig) -> ResourceStatus:
            return ResourceStatus.ACTIVE

        methods["provision"] = provision

    if not skip_deprovision:

        def deprovision(self: Any, config: ResourceConfig) -> ResourceStatus:
            return ResourceStatus.DEPROVISIONED

        methods["deprovision"] = deprovision

    if not skip_status:

        def status(self: Any, config: ResourceConfig) -> ResourceStatus:
            return ResourceStatus.ACTIVE

        methods["status"] = status

    if not skip_validate_config:

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

        methods["validate_config"] = validate_config

    if not skip_to_dict:

        def to_dict(self: Any) -> dict[str, object]:
            return {"type": "test/concrete"}

        methods["to_dict"] = to_dict

    return type("ConcreteResextType", (ResourceType,), methods)


def _unique_name(context: Any, base_name: str) -> str:
    """Return a scenario-unique variant of *base_name*.

    The real process-wide registry is shared across parallel scenarios; using a
    per-scenario suffix prevents name collisions while still exercising the real
    `register_resource_type` / `get_resource_type` / `list_resource_types`
    code paths (which is what diff_coverage requires).
    """
    return f"{base_name}-{context._resext_scenario_id}"


@given("the resource extension interface is imported")
def step_resext_import(context: Any) -> None:
    import cleveragents.resources as resources_pkg
    from cleveragents.resources import (
        ResourceConfig,
        ResourceStatus,
        ResourceType,
        get_resource_type,
        list_resource_types,
        register_resource_type,
    )

    context.resources_pkg = resources_pkg
    context.ResourceConfig = ResourceConfig
    context.ResourceStatus = ResourceStatus
    context.ResourceType = ResourceType
    context.register_resource_type = register_resource_type
    context.get_resource_type = get_resource_type
    context.list_resource_types = list_resource_types

    context.raised_error = None
    context.result = None
    import uuid

    context._resext_scenario_id = uuid.uuid4().hex[:8]
    context.last_get_result = None


@given("the resext registry is cleared")
def step_resext_clear_registry(context: Any) -> None:
    """No-op: unique scenario IDs prevent conflicts in parallel tests."""
    # Registry is not cleared to avoid race conditions in parallel tests
    pass


@given("I have a concrete ResourceType instance")
def step_resext_have_instance(context: Any) -> None:
    """Create a concrete ResourceType instance and store on context."""
    ConcreteClass = _make_resext_class()
    context.concrete_instance = ConcreteClass()


@when('I create a ResourceConfig with name "{name}" and type "{rtype}"')
def step_resext_create_config(context: Any, name: str, rtype: str) -> None:
    context.resource_config = context.ResourceConfig(name=name, resource_type=rtype)


@when(
    'I create a ResourceConfig with name "{name}" type "{rtype}"'
    ' and properties {{"host": "localhost"}}'
)
def step_resext_create_config_with_props(context: Any, name: str, rtype: str) -> None:
    context.resource_config = context.ResourceConfig(
        name=name, resource_type=rtype, properties={"host": "localhost"}
    )


@when("I try to instantiate ResourceType directly")
def step_resext_try_instantiate_abstract(context: Any) -> None:
    # Pyright otherwise flags this call because ResourceType is abstract; the
    # runtime check that instantiation raises TypeError is the actual test.
    # Indirecting through an ``Any``-typed alias satisfies Pyright without
    # using a ``# type: ignore`` (prohibited by CONTRIBUTING.md).
    resource_type_cls: Any = context.ResourceType
    try:
        resource_type_cls()
        context.raised_error = None
    except TypeError as exc:
        context.raised_error = exc


@when("I define a resext class missing the provision method")
def step_resext_missing_provision(context: Any) -> None:
    cls = _make_resext_class(skip_provision=True)
    try:
        cls()
        context.raised_error = None
    except TypeError as exc:
        context.raised_error = exc


@when("I define a resext class missing the deprovision method")
def step_resext_missing_deprovision(context: Any) -> None:
    cls = _make_resext_class(skip_deprovision=True)
    try:
        cls()
        context.raised_error = None
    except TypeError as exc:
        context.raised_error = exc


@when("I define a resext class missing the status method")
def step_resext_missing_status(context: Any) -> None:
    cls = _make_resext_class(skip_status=True)
    try:
        cls()
        context.raised_error = None
    except TypeError as exc:
        context.raised_error = exc


@when("I define a resext class missing the validate_config method")
def step_resext_missing_validate_config(context: Any) -> None:
    cls = _make_resext_class(skip_validate_config=True)
    try:
        cls()
        context.raised_error = None
    except TypeError as exc:
        context.raised_error = exc


@when("I define a resext class missing the to_dict method")
def step_resext_missing_to_dict(context: Any) -> None:
    cls = _make_resext_class(skip_to_dict=True)
    try:
        cls()
        context.raised_error = None
    except TypeError as exc:
        context.raised_error = exc


@when("I define a complete concrete ResourceType subclass")
def step_resext_define_complete_subclass(context: Any) -> None:
    context.ConcreteClass = _make_resext_class()
    context.raised_error = None


@then("it should be instantiable without errors")
def step_resext_instantiable_without_errors(context: Any) -> None:
    try:
        instance = context.ConcreteClass()
        assert instance is not None
    except Exception as exc:
        raise AssertionError(f"Unexpected error: {exc}") from exc


@when("I call provision with a valid ResourceConfig")
def step_resext_call_provision(context: Any) -> None:
    cfg = context.ResourceConfig(name="test", resource_type="custom/test")
    context.result = context.concrete_instance.provision(cfg)


@when("I call deprovision with a valid ResourceConfig")
def step_resext_call_deprovision(context: Any) -> None:
    cfg = context.ResourceConfig(name="test", resource_type="custom/test")
    context.result = context.concrete_instance.deprovision(cfg)


@when("I call status with a valid ResourceConfig")
def step_resext_call_status(context: Any) -> None:
    cfg = context.ResourceConfig(name="test", resource_type="custom/test")
    context.result = context.concrete_instance.status(cfg)


@when("I call validate_config with a valid ResourceConfig")
def step_resext_call_validate_config(context: Any) -> None:
    cfg = context.ResourceConfig(name="test", resource_type="custom/test")
    context.result = context.concrete_instance.validate_config(cfg)


@when("I call to_dict")
def step_resext_call_to_dict(context: Any) -> None:
    context.result = context.concrete_instance.to_dict()


@when('I register a resext class as "{name}"')
def step_resext_register_class(context: Any, name: str) -> None:
    cls = _make_resext_class()
    unique = _unique_name(context, name)
    context.last_registered_class = cls
    context.last_registered_name = unique
    context.raised_error = None
    try:
        context.register_resource_type(unique, cls)
        # Immediately verify registration succeeded via the real registry.
        context.last_get_result = context.get_resource_type(unique)
    except (TypeError, ValueError) as exc:
        context.raised_error = exc


@when('I try to register a non-resext class as "{name}"')
def step_resext_register_non_type(context: Any, name: str) -> None:
    class NotAResourceType:
        pass

    unique = _unique_name(context, name)
    context.raised_error = None
    try:
        context.register_resource_type(unique, NotAResourceType)
    except TypeError as exc:
        context.raised_error = exc


@when('I try to register another resext class as "{name}"')
def step_resext_register_duplicate(context: Any, name: str) -> None:
    cls = _make_resext_class()
    unique = _unique_name(context, name)
    context.raised_error = None
    try:
        context.register_resource_type(unique, cls)
    except ValueError as exc:
        context.raised_error = exc


@then('ResourceStatus should have value "{value}"')
def step_resext_status_has_value(context: Any, value: str) -> None:
    assert hasattr(context.ResourceStatus, value)
    assert context.ResourceStatus[value].value == value


@then("each ResourceStatus value should be a string")
def step_resext_status_values_are_strings(context: Any) -> None:
    for member in context.ResourceStatus:
        assert isinstance(member.value, str)


@then('the ResourceConfig name should be "{name}"')
def step_resext_config_name(context: Any, name: str) -> None:
    assert context.resource_config.name == name


@then('the ResourceConfig resource_type should be "{rtype}"')
def step_resext_config_type(context: Any, rtype: str) -> None:
    assert context.resource_config.resource_type == rtype


@then("the ResourceConfig properties should be an empty dict by default")
def step_resext_config_empty_properties(context: Any) -> None:
    assert context.resource_config.properties == {}


@then('the ResourceConfig properties should contain "{key}" with value "{value}"')
def step_resext_config_property(context: Any, key: str, value: str) -> None:
    props = context.resource_config.properties
    assert key in props
    assert props[key] == value


@then("ResourceType should be an abstract class")
def step_resext_type_is_abstract(context: Any) -> None:
    assert hasattr(context.ResourceType, "__abstractmethods__")
    assert len(context.ResourceType.__abstractmethods__) > 0


@then("a resource extension TypeError should be raised")
def step_resext_type_error_raised(context: Any) -> None:
    assert context.raised_error is not None
    assert isinstance(context.raised_error, TypeError)


@then("a resource extension ValueError should be raised")
def step_resext_value_error_raised(context: Any) -> None:
    assert context.raised_error is not None
    assert isinstance(context.raised_error, ValueError)


@then("the resext class instantiation should raise TypeError")
def step_resext_instantiation_raises_type_error(context: Any) -> None:
    assert context.raised_error is not None
    assert isinstance(context.raised_error, TypeError)


@then("the resext result should be a ResourceStatus")
def step_resext_result_is_status(context: Any) -> None:
    assert isinstance(context.result, context.ResourceStatus)


@then("the resext result should be a list")
def step_resext_result_is_list(context: Any) -> None:
    assert isinstance(context.result, list)


@then("the resext result should be a dict")
def step_resext_result_is_dict(context: Any) -> None:
    assert isinstance(context.result, dict)


@then('the resext registry should contain "{name}"')
def step_resext_registry_contains(context: Any, name: str) -> None:
    unique = _unique_name(context, name)
    names = context.list_resource_types()
    assert unique in names, f"'{unique}' not in registry"


@then('resext get_resource_type("{name}") should return the class')
def step_resext_get_returns_class(context: Any, name: str) -> None:
    unique = _unique_name(context, name)
    result = context.get_resource_type(unique)
    assert result is not None, f"get_resource_type('{unique}') returned None"
    assert isinstance(result, type), f"Expected type, got {type(result)}"
    from cleveragents.resources import ResourceType as RT

    assert issubclass(result, RT), f"Expected subclass of ResourceType, got {result}"


@then('resext get_resource_type("{name}") should return None')
def step_resext_get_returns_none(context: Any, name: str) -> None:
    unique = _unique_name(context, name)
    result = context.get_resource_type(unique)
    assert result is None


@then('resext list_resource_types should include "{name}"')
def step_resext_list_includes(context: Any, name: str) -> None:
    unique = _unique_name(context, name)
    names = context.list_resource_types()
    assert unique in names, f"'{unique}' not in registry list"


@then("ResourceType should be importable from cleveragents.resources")
def step_resext_type_importable(context: Any) -> None:
    from cleveragents.resources import ResourceType

    assert ResourceType is not None


@then("ResourceConfig should be importable from cleveragents.resources")
def step_resext_config_importable(context: Any) -> None:
    from cleveragents.resources import ResourceConfig

    assert ResourceConfig is not None


@then("ResourceStatus should be importable from cleveragents.resources")
def step_resext_status_importable(context: Any) -> None:
    from cleveragents.resources import ResourceStatus

    assert ResourceStatus is not None


@then("register_resource_type should be importable from cleveragents.resources")
def step_resext_register_importable(context: Any) -> None:
    from cleveragents.resources import register_resource_type

    assert register_resource_type is not None


@then("get_resource_type should be importable from cleveragents.resources")
def step_resext_get_importable(context: Any) -> None:
    from cleveragents.resources import get_resource_type

    assert get_resource_type is not None


@then("list_resource_types should be importable from cleveragents.resources")
def step_resext_list_importable(context: Any) -> None:
    from cleveragents.resources import list_resource_types

    assert list_resource_types is not None
