"""Step definitions for Action domain model tests."""

from typing import Any

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

from cleveragents.domain.models.core.action import (
    Action,
    ActionArgument,
    ActionState,
    ArgumentRequirement,
    ArgumentType,
)
from cleveragents.domain.models.core.plan import NamespacedName

# ---------------------------------------------------------------------------
# Helper
# ---------------------------------------------------------------------------


def _create_default_action(
    name: str = "local/test-action",
    definition_of_done: str = "Tests pass",
    **kwargs: Any,
) -> Action:
    """Helper to create an action with defaults."""
    parsed_name = NamespacedName.parse(name)
    return Action(
        namespaced_name=parsed_name,
        description=kwargs.pop("description", "Default test action"),
        definition_of_done=definition_of_done,
        strategy_actor=kwargs.pop("strategy_actor", "openai/gpt-4"),
        execution_actor=kwargs.pop("execution_actor", "openai/gpt-4"),
        **kwargs,
    )


def _full_config(**overrides: Any) -> dict[str, Any]:
    """Return a minimal valid config dict for Action.from_config, with overrides."""
    cfg: dict[str, Any] = {
        "name": "local/my-action",
        "description": "Run tests",
        "strategy_actor": "openai/gpt-4",
        "execution_actor": "openai/gpt-4",
        "definition_of_done": "All tests green",
    }
    cfg.update(overrides)
    return cfg


# ---------------------------------------------------------------------------
# ActionArgument Parsing Steps
# ---------------------------------------------------------------------------


@when('I parse the action argument "{arg_string}"')
def step_parse_action_argument(context: Context, arg_string: str) -> None:
    """Parse an action argument string."""
    context.argument = ActionArgument.parse(arg_string)
    context.parse_error = None


@when('I try to parse the action argument "{arg_string}"')
def step_try_parse_action_argument(context: Context, arg_string: str) -> None:
    """Attempt to parse an action argument string that might fail."""
    context.parse_error = None
    try:
        context.argument = ActionArgument.parse(arg_string)
    except ValueError as e:
        context.parse_error = e


@then('the argument name should be "{expected}"')
def step_check_argument_name(context: Context, expected: str) -> None:
    """Check the argument name matches expected."""
    assert context.argument.name == expected, (
        f"Expected name '{expected}', got '{context.argument.name}'"
    )


@then('the argument type should be "{expected}"')
def step_check_argument_type(context: Context, expected: str) -> None:
    """Check the argument type matches expected."""
    actual = context.argument.arg_type.value
    assert actual == expected, f"Expected type '{expected}', got '{actual}'"


@then("the argument should be required")
def step_check_argument_required(context: Context) -> None:
    """Check the argument is required."""
    assert context.argument.requirement == ArgumentRequirement.REQUIRED, (
        f"Expected argument to be required, got {context.argument.requirement}"
    )


@then("the argument should be optional")
def step_check_argument_optional(context: Context) -> None:
    """Check the argument is optional."""
    assert context.argument.requirement == ArgumentRequirement.OPTIONAL, (
        f"Expected argument to be optional, got {context.argument.requirement}"
    )


@then('the argument description should be "{expected}"')
def step_check_argument_description(context: Context, expected: str) -> None:
    """Check the argument description matches expected."""
    assert context.argument.description == expected, (
        f"Expected description '{expected}', got '{context.argument.description}'"
    )


@then("the argument description should be empty")
def step_check_argument_description_empty(context: Context) -> None:
    """Check the argument description is empty."""
    assert context.argument.description == "", (
        f"Expected empty description, got '{context.argument.description}'"
    )


@then("an argument parse error should be raised")
def step_check_argument_parse_error(context: Context) -> None:
    """Verify that a parse error was raised."""
    assert context.parse_error is not None, "Expected a parse error"


@when('I try to create an action argument with invalid name "{name}"')
def step_try_create_argument_with_invalid_name(context: Context, name: str) -> None:
    """Attempt to create an action argument with an invalid identifier name."""
    context.error = None
    try:
        context.argument = ActionArgument(
            name=name,
            arg_type=ArgumentType.STRING,
            requirement=ArgumentRequirement.REQUIRED,
            description="Invalid name",
        )
    except ValidationError as e:
        context.error = e


@when("I stringify the action argument")
def step_stringify_action_argument(context: Context) -> None:
    """Convert the action argument to its definition string."""
    context.argument_string = str(context.argument)


@then('the argument definition string should be "{expected}"')
def step_check_argument_definition_string(context: Context, expected: str) -> None:
    """Verify the string representation of the action argument."""
    assert context.argument_string == expected, (
        f"Expected definition string '{expected}', got '{context.argument_string}'"
    )


# ---------------------------------------------------------------------------
# Action Creation Steps
# ---------------------------------------------------------------------------


@when('I create an action with name "{name}" and definition of done "{dod}"')
def step_create_action_with_name_dod(context: Context, name: str, dod: str) -> None:
    """Create an action with specified name and definition of done."""
    context.action = _create_default_action(name=name, definition_of_done=dod)


@when("I create a new action")
def step_create_new_action(context: Context) -> None:
    """Create a new action with default values."""
    context.action = _create_default_action()


@given("I have action arguments")
def step_have_action_arguments(context: Context) -> None:
    """Store action arguments from table."""
    context.action_arguments = []
    assert context.table is not None, "Expected table data for action arguments"
    for row in context.table:
        arg = ActionArgument(
            name=row["name"],
            arg_type=ArgumentType(row["type"]),
            requirement=ArgumentRequirement(row["requirement"]),
            description=row["description"],
        )
        context.action_arguments.append(arg)


@when("I create an action with these arguments")
def step_create_action_with_arguments(context: Context) -> None:
    """Create an action with the stored arguments."""
    context.action = _create_default_action(arguments=context.action_arguments)


@then("the action should be created")
def step_check_action_created(context: Context) -> None:
    """Verify the action was created."""
    assert context.action is not None, "Action should be created"


@then('the action namespace should be "{expected}"')
def step_check_action_namespace(context: Context, expected: str) -> None:
    """Check the action namespace matches expected."""
    actual = context.action.namespaced_name.namespace
    assert actual == expected, f"Expected namespace '{expected}', got '{actual}'"


@then('the action name should be "{expected}"')
def step_check_action_name(context: Context, expected: str) -> None:
    """Check the action name matches expected."""
    actual = context.action.namespaced_name.name
    assert actual == expected, f"Expected name '{expected}', got '{actual}'"


@then('the action state should be "{expected}"')
def step_check_action_state(context: Context, expected: str) -> None:
    """Check the action state matches expected."""
    actual = context.action.state.value
    assert actual == expected, f"Expected state '{expected}', got '{actual}'"


@then("the action should have {count:d} arguments")
def step_check_argument_count(context: Context, count: int) -> None:
    """Check the number of arguments."""
    actual = len(context.action.arguments)
    assert actual == count, f"Expected {count} arguments, got {actual}"


@then("the action should have {count:d} required argument")
def step_check_required_argument_count(context: Context, count: int) -> None:
    """Check the number of required arguments."""
    actual = len(context.action.required_arguments)
    assert actual == count, f"Expected {count} required arguments, got {actual}"


@then("the action should have {count:d} optional argument")
def step_check_optional_argument_count(context: Context, count: int) -> None:
    """Check the number of optional arguments."""
    actual = len(context.action.optional_arguments)
    assert actual == count, f"Expected {count} optional arguments, got {actual}"


# ---------------------------------------------------------------------------
# Argument Validation Steps
# ---------------------------------------------------------------------------


@given('I have an action with required argument "{arg_string}"')
def step_have_action_with_required_arg(context: Context, arg_string: str) -> None:
    """Create an action with a required argument."""
    arg = ActionArgument.parse(arg_string)
    context.action = _create_default_action(arguments=[arg])


@when("I validate arguments with target {value:d}")
def step_validate_arguments_with_target(context: Context, value: int) -> None:
    """Validate arguments with target value."""
    context.errors = context.action.validate_arguments({"target": value})


@when("I validate arguments without target")
def step_validate_arguments_without_target(context: Context) -> None:
    """Validate arguments without providing target."""
    context.errors = context.action.validate_arguments({})


@when('I validate arguments with target {value:d} and extra "{extra}"')
def step_validate_arguments_with_extra(
    context: Context, value: int, extra: str
) -> None:
    """Validate arguments with an extra unknown argument."""
    context.errors = context.action.validate_arguments(
        {
            "target": value,
            "extra": extra,
        }
    )


@when('I validate arguments with target as string "{value}"')
def step_validate_arguments_with_string_target(context: Context, value: str) -> None:
    """Validate arguments with target as wrong type."""
    context.errors = context.action.validate_arguments({"target": value})


@given(
    'I have an action with bounded integer argument "{name}" min {min_value:d} max {max_value:d}'
)
def step_have_action_with_bounded_integer(
    context: Context, name: str, min_value: int, max_value: int
) -> None:
    """Create an action with a bounded integer argument."""
    arg = ActionArgument(
        name=name,
        arg_type=ArgumentType.INTEGER,
        requirement=ArgumentRequirement.REQUIRED,
        description="Bounded integer argument",
        min_value=min_value,
        max_value=max_value,
    )
    context.action = _create_default_action(arguments=[arg])


@given('I have an action with argument "{name}" of type "{arg_type}"')
def step_have_action_with_argument_type(
    context: Context, name: str, arg_type: str
) -> None:
    """Create an action with an argument of a specific type."""
    arg = ActionArgument(
        name=name,
        arg_type=ArgumentType(arg_type),
        requirement=ArgumentRequirement.REQUIRED,
        description=f"{arg_type} argument",
    )
    context.action = _create_default_action(arguments=[arg])


@when('I validate arguments with "{name}" set to {value:d}')
def step_validate_arguments_with_named_int(
    context: Context, name: str, value: int
) -> None:
    """Validate arguments with a named integer value."""
    context.errors = context.action.validate_arguments({name: value})


@when('I validate arguments with "{name}" set to "{value}"')
def step_validate_arguments_with_named_string(
    context: Context, name: str, value: str
) -> None:
    """Validate arguments with a named string value."""
    context.errors = context.action.validate_arguments({name: value})


@then("the validation should pass")
def step_check_validation_pass(context: Context) -> None:
    """Check that validation passed."""
    assert len(context.errors) == 0, (
        f"Expected validation to pass, got errors: {context.errors}"
    )


@then('the validation should fail with "{expected_msg}"')
def step_check_validation_fail(context: Context, expected_msg: str) -> None:
    """Check that validation failed with expected message."""
    assert len(context.errors) > 0, "Expected validation errors"
    error_text = " ".join(context.errors)
    assert expected_msg in error_text, (
        f"Expected error containing '{expected_msg}', got: {context.errors}"
    )


# ---------------------------------------------------------------------------
# Action State Steps
# ---------------------------------------------------------------------------


@given("I have an available action")
def step_have_available_action(context: Context) -> None:
    """Create an action in available state."""
    context.action = _create_default_action()
    context.action.state = ActionState.AVAILABLE


@when("I set the action state to archived")
def step_set_action_archived(context: Context) -> None:
    """Set action state to archived."""
    context.action.state = ActionState.ARCHIVED


# ---------------------------------------------------------------------------
# Actor Configuration Steps
# ---------------------------------------------------------------------------


@when("I try to create an action without strategy actor")
def step_try_create_action_without_strategy_actor(context: Context) -> None:
    """Attempt to create an action without strategy actor."""
    context.error = None
    try:
        data = {
            "namespaced_name": NamespacedName.parse("local/test"),
            "description": "Test action",
            "definition_of_done": "Test",
            # Missing strategy_actor
            "execution_actor": "openai/gpt-4",
        }
        context.action = Action.model_validate(data)
    except ValidationError as e:
        context.error = e
        context.action = None


# Note: "a validation error should be raised" step is defined in domain_models_steps.py


@when('I create an action with review actor "{actor}"')
def step_create_action_with_review_actor(context: Context, actor: str) -> None:
    """Create an action with a review actor."""
    context.action = _create_default_action(review_actor=actor)


@when('I create an action with estimation actor "{actor}"')
def step_create_action_with_estimation_actor(context: Context, actor: str) -> None:
    """Create an action with an estimation actor."""
    context.action = _create_default_action(estimation_actor=actor)


@then("the action should have a review actor")
def step_check_has_review_actor(context: Context) -> None:
    """Check the action has a review actor."""
    assert context.action.review_actor is not None, "Expected review actor"


@then("the action should have an estimation actor")
def step_check_has_estimation_actor(context: Context) -> None:
    """Check the action has an estimation actor."""
    assert context.action.estimation_actor is not None, "Expected estimation actor"


# ---------------------------------------------------------------------------
# Reusability Steps
# ---------------------------------------------------------------------------


@when("I create a non-reusable action")
def step_create_non_reusable_action(context: Context) -> None:
    """Create a non-reusable action."""
    context.action = _create_default_action(reusable=False)


@then("the action should be reusable")
def step_check_action_reusable(context: Context) -> None:
    """Check the action is reusable."""
    assert context.action.reusable is True, "Expected action to be reusable"


@then("the action should not be reusable")
def step_check_action_not_reusable(context: Context) -> None:
    """Check the action is not reusable."""
    assert context.action.reusable is False, "Expected action to not be reusable"


# ---------------------------------------------------------------------------
# Read-only Steps
# ---------------------------------------------------------------------------


@when("I create a read-only action")
def step_create_read_only_action(context: Context) -> None:
    """Create a read-only action."""
    context.action = _create_default_action(read_only=True)


@then("the action should be read-only")
def step_check_action_read_only(context: Context) -> None:
    """Check the action is read-only."""
    assert context.action.read_only is True, "Expected action to be read-only"


@then("the action should not be read-only")
def step_check_action_not_read_only(context: Context) -> None:
    """Check the action is not read-only."""
    assert context.action.read_only is False, "Expected action to not be read-only"


# ---------------------------------------------------------------------------
# Invariants Sanitization Steps
# ---------------------------------------------------------------------------


@when(
    "I create an action model with invariants "
    '"  no secrets  " and "" and "  no secrets" and "keep data safe"'
)
def step_create_action_model_with_invariants(context: Context) -> None:
    """Create an action with invariants that need sanitization."""
    context.action = _create_default_action(
        invariants=["  no secrets  ", "", "  no secrets", "keep data safe"],
    )


@then('the action model invariants should be "{first}" and "{second}"')
def step_check_action_model_invariants(
    context: Context, first: str, second: str
) -> None:
    """Check the sanitized invariants list."""
    assert context.action.invariants == [first, second], (
        f"Expected [{first}, {second}], got {context.action.invariants}"
    )


@when("I create an action model with null invariants")
def step_create_action_model_with_null_invariants(context: Context) -> None:
    """Create an action with None invariants (should default to [])."""
    context.action = _create_default_action(invariants=None)


@then("the action model invariants count should be {count:d}")
def step_check_action_model_invariants_count(context: Context, count: int) -> None:
    """Check the invariants list length."""
    actual = len(context.action.invariants)
    assert actual == count, f"Expected {count} invariants, got {actual}"


# ---------------------------------------------------------------------------
# from_config Factory Steps
# ---------------------------------------------------------------------------


@when(
    'I create an action model from config with name "{name}" '
    'and description "{desc}" and dod "{dod}"'
)
def step_create_action_model_from_config(
    context: Context, name: str, desc: str, dod: str
) -> None:
    """Create an action via from_config."""
    config = _full_config(name=name, description=desc, definition_of_done=dod)
    context.action = Action.from_config(config)


@then('the action model description should be "{expected}"')
def step_check_action_model_description(context: Context, expected: str) -> None:
    """Check the action description."""
    assert context.action.description == expected, (
        f"Expected description '{expected}', got '{context.action.description}'"
    )


@when("I create an action model from config with arguments")
def step_create_action_model_from_config_with_args(context: Context) -> None:
    """Create an action via from_config with argument mappings."""
    config = _full_config(
        arguments=[
            {
                "name": "target",
                "type": "integer",
                "required": True,
                "description": "Target value",
            },
            {
                "name": "mode",
                "type": "string",
                "required": False,
                "description": "Mode",
                "default": "fast",
            },
        ],
    )
    context.action = Action.from_config(config)


@when('I try to create an action model from config missing "{field}"')
def step_try_create_action_model_from_config_missing(
    context: Context, field: str
) -> None:
    """Try from_config with a missing required field."""
    config = _full_config()
    del config[field]
    context.action_model_config_error = None
    try:
        context.action = Action.from_config(config)
    except ValueError as e:
        context.action_model_config_error = e


@then('an action model config error should be raised with "{field}"')
def step_check_action_model_config_error(context: Context, field: str) -> None:
    """Check that a config error mentioning the field was raised."""
    assert context.action_model_config_error is not None, (
        f"Expected ValueError for missing '{field}'"
    )
    assert field in str(context.action_model_config_error), (
        f"Expected error to mention '{field}', got: {context.action_model_config_error}"
    )


# ---------------------------------------------------------------------------
# as_cli_dict Steps
# ---------------------------------------------------------------------------


@when("I create an action model and call as_cli_dict")
def step_create_action_model_and_cli_dict(context: Context) -> None:
    """Create an action and get its CLI dict."""
    context.action = _create_default_action()
    context.action_model_cli_dict = context.action.as_cli_dict()


@then('the action model cli dict should have key "{key}"')
def step_check_action_model_cli_dict_key(context: Context, key: str) -> None:
    """Check the CLI dict contains a key."""
    assert key in context.action_model_cli_dict, (
        f"Expected key '{key}' in cli dict, keys: {list(context.action_model_cli_dict)}"
    )


@when("I create an action model with review actor and call as_cli_dict")
def step_create_action_model_with_review_and_cli_dict(context: Context) -> None:
    """Create an action with review actor and get CLI dict."""
    context.action = _create_default_action(review_actor="local/reviewer")
    context.action_model_cli_dict = context.action.as_cli_dict()


@when("I create an action model with arguments and call as_cli_dict")
def step_create_action_model_with_args_and_cli_dict(context: Context) -> None:
    """Create an action with arguments and get CLI dict."""
    arg = ActionArgument(
        name="target",
        arg_type=ArgumentType.INTEGER,
        requirement=ArgumentRequirement.REQUIRED,
        description="Target value",
    )
    context.action = _create_default_action(arguments=[arg])
    context.action_model_cli_dict = context.action.as_cli_dict()


@then("the action model cli dict arguments count should be {count:d}")
def step_check_action_model_cli_dict_args_count(context: Context, count: int) -> None:
    """Check the number of arguments in the CLI dict."""
    actual = len(context.action_model_cli_dict["arguments"])
    assert actual == count, f"Expected {count} arguments in cli dict, got {actual}"


# ---------------------------------------------------------------------------
# from_mapping Steps (ActionArgument)
# ---------------------------------------------------------------------------


@when(
    'I create an action argument from basic mapping "{name}" '
    'type "{arg_type}" required {req}'
)
def step_create_action_argument_from_mapping(
    context: Context, name: str, arg_type: str, req: str
) -> None:
    """Create an ActionArgument via from_mapping."""
    required = req.lower() == "true"
    context.argument = ActionArgument.from_mapping(
        {"name": name, "type": arg_type, "required": required}
    )


@when(
    'I create an action argument from mapping with default "{name}" '
    'type "{arg_type}" required {req} default "{default}"'
)
def step_create_action_argument_from_mapping_with_default(
    context: Context, name: str, arg_type: str, req: str, default: str
) -> None:
    """Create an ActionArgument via from_mapping with a default value."""
    required = req.lower() == "true"
    context.argument = ActionArgument.from_mapping(
        {"name": name, "type": arg_type, "required": required, "default": default}
    )


@then('the action model argument default value should be "{expected}"')
def step_check_action_model_argument_default(context: Context, expected: str) -> None:
    """Check the argument default value."""
    assert context.argument.default_value == expected, (
        f"Expected default '{expected}', got '{context.argument.default_value}'"
    )


@when("I try to create an action argument from mapping missing name")
def step_try_create_argument_from_mapping_missing_name(context: Context) -> None:
    """Try from_mapping without 'name'."""
    context.action_model_arg_mapping_error = None
    try:
        ActionArgument.from_mapping({"type": "string"})
    except ValueError as e:
        context.action_model_arg_mapping_error = e


@when("I try to create an action argument from mapping missing type")
def step_try_create_argument_from_mapping_missing_type(context: Context) -> None:
    """Try from_mapping without 'type'."""
    context.action_model_arg_mapping_error = None
    try:
        ActionArgument.from_mapping({"name": "foo"})
    except ValueError as e:
        context.action_model_arg_mapping_error = e


@then('an action model argument mapping error should be raised with "{field}"')
def step_check_action_model_arg_mapping_error(context: Context, field: str) -> None:
    """Check that a mapping error mentioning the field was raised."""
    assert context.action_model_arg_mapping_error is not None, (
        f"Expected ValueError for missing '{field}'"
    )
    assert field in str(context.action_model_arg_mapping_error), (
        f"Expected error to mention '{field}', "
        f"got: {context.action_model_arg_mapping_error}"
    )


# ---------------------------------------------------------------------------
# coerce_value Steps
# ---------------------------------------------------------------------------


@given('I have an action model string argument named "{name}"')
def step_have_action_model_string_argument(context: Context, name: str) -> None:
    """Create a string ActionArgument."""
    context.action_model_arg = ActionArgument(
        name=name,
        arg_type=ArgumentType.STRING,
        requirement=ArgumentRequirement.REQUIRED,
    )


@given('I have an action model integer argument named "{name}"')
def step_have_action_model_integer_argument(context: Context, name: str) -> None:
    """Create an integer ActionArgument."""
    context.action_model_arg = ActionArgument(
        name=name,
        arg_type=ArgumentType.INTEGER,
        requirement=ArgumentRequirement.REQUIRED,
    )


@given('I have an action model float argument named "{name}"')
def step_have_action_model_float_argument(context: Context, name: str) -> None:
    """Create a float ActionArgument."""
    context.action_model_arg = ActionArgument(
        name=name,
        arg_type=ArgumentType.FLOAT,
        requirement=ArgumentRequirement.REQUIRED,
    )


@given('I have an action model boolean argument named "{name}"')
def step_have_action_model_boolean_argument(context: Context, name: str) -> None:
    """Create a boolean ActionArgument."""
    context.action_model_arg = ActionArgument(
        name=name,
        arg_type=ArgumentType.BOOLEAN,
        requirement=ArgumentRequirement.REQUIRED,
    )


@given('I have an action model list argument named "{name}"')
def step_have_action_model_list_argument(context: Context, name: str) -> None:
    """Create a list ActionArgument."""
    context.action_model_arg = ActionArgument(
        name=name,
        arg_type=ArgumentType.LIST,
        requirement=ArgumentRequirement.REQUIRED,
    )


@when('I coerce action model value "{raw}" for the argument')
def step_coerce_action_model_value(context: Context, raw: str) -> None:
    """Coerce a raw string value using the stored argument."""
    context.coerced_action_model_value = context.action_model_arg.coerce_value(raw)


@when('I try to coerce action model value "{raw}" for the argument')
def step_try_coerce_action_model_value(context: Context, raw: str) -> None:
    """Try to coerce a value that may fail."""
    context.action_model_coerce_error = None
    try:
        context.coerced_action_model_value = context.action_model_arg.coerce_value(raw)
    except ValueError as e:
        context.action_model_coerce_error = e


@then('the coerced action model value should be string "{expected}"')
def step_check_coerced_action_model_string(context: Context, expected: str) -> None:
    """Check the coerced value is the expected string."""
    assert context.coerced_action_model_value == expected, (
        f"Expected '{expected}', got '{context.coerced_action_model_value}'"
    )


@then("the coerced action model value should be integer {expected:d}")
def step_check_coerced_action_model_integer(context: Context, expected: int) -> None:
    """Check the coerced value is the expected integer."""
    assert context.coerced_action_model_value == expected, (
        f"Expected {expected}, got {context.coerced_action_model_value}"
    )
    assert isinstance(context.coerced_action_model_value, int)


@then("the coerced action model value should be float {expected:g}")
def step_check_coerced_action_model_float(context: Context, expected: float) -> None:
    """Check the coerced value is the expected float."""
    assert abs(context.coerced_action_model_value - expected) < 1e-9, (
        f"Expected {expected}, got {context.coerced_action_model_value}"
    )


@then("the coerced action model value should be boolean true")
def step_check_coerced_action_model_bool_true(context: Context) -> None:
    """Check the coerced value is True."""
    assert context.coerced_action_model_value is True, (
        f"Expected True, got {context.coerced_action_model_value}"
    )


@then("the coerced action model value should be boolean false")
def step_check_coerced_action_model_bool_false(context: Context) -> None:
    """Check the coerced value is False."""
    assert context.coerced_action_model_value is False, (
        f"Expected False, got {context.coerced_action_model_value}"
    )


@then("an action model coerce error should be raised")
def step_check_action_model_coerce_error(context: Context) -> None:
    """Check that a coerce error was raised."""
    assert context.action_model_coerce_error is not None, (
        "Expected ValueError from coerce_value"
    )


@then("the coerced action model value should be list with {count:d} items")
def step_check_coerced_action_model_list(context: Context, count: int) -> None:
    """Check the coerced value is a list of the expected length."""
    assert isinstance(context.coerced_action_model_value, list), (
        f"Expected list, got {type(context.coerced_action_model_value)}"
    )
    actual = len(context.coerced_action_model_value)
    assert actual == count, f"Expected {count} items, got {actual}"


# ---------------------------------------------------------------------------
# render_template Steps
# ---------------------------------------------------------------------------


@when('I create an action model with description "{desc}"')
def step_create_action_model_with_description(context: Context, desc: str) -> None:
    """Create an action with a template description."""
    context.action = _create_default_action(description=desc)


@when('I render action model description with target "{target}" and module "{module}"')
def step_render_action_model_description(
    context: Context, target: str, module: str
) -> None:
    """Render the description template."""
    context.rendered_action_model_desc = context.action.render_description(
        {"target": target, "module": module}
    )


@then('the rendered action model description should be "{expected}"')
def step_check_rendered_action_model_description(
    context: Context, expected: str
) -> None:
    """Check the rendered description."""
    assert context.rendered_action_model_desc == expected, (
        f"Expected '{expected}', got '{context.rendered_action_model_desc}'"
    )


@when('I try to render action model description with only target "{target}"')
def step_try_render_action_model_desc_incomplete(context: Context, target: str) -> None:
    """Try to render the description with missing placeholders."""
    context.action_model_template_error = None
    try:
        context.action.render_description({"target": target})
    except ValueError as e:
        context.action_model_template_error = e


@then("an action model template error should be raised")
def step_check_action_model_template_error(context: Context) -> None:
    """Check that a template rendering error was raised."""
    assert context.action_model_template_error is not None, (
        "Expected ValueError from render_template"
    )


@when("I create an action model with dod template for module and target")
def step_create_action_model_with_dod_template(context: Context) -> None:
    """Create an action with a template definition of done."""
    context.action = _create_default_action(
        definition_of_done="All tests in ${module} pass with ${target}% coverage",
    )


@when('I render action model dod with target "{target}" and module "{module}"')
def step_render_action_model_dod(context: Context, target: str, module: str) -> None:
    """Render the definition of done template."""
    context.rendered_action_model_dod = context.action.render_definition_of_done(
        {"target": target, "module": module}
    )


@then('the rendered action model dod should be "{expected}"')
def step_check_rendered_action_model_dod(context: Context, expected: str) -> None:
    """Check the rendered definition of done."""
    assert context.rendered_action_model_dod == expected, (
        f"Expected '{expected}', got '{context.rendered_action_model_dod}'"
    )


# ---------------------------------------------------------------------------
# inputs_schema Validation Steps
# ---------------------------------------------------------------------------


@when(
    'I create an action model with inputs schema type "{schema_type}" '
    'and required "{req_field}"'
)
def step_create_action_model_with_inputs_schema(
    context: Context, schema_type: str, req_field: str
) -> None:
    """Create an action with a valid inputs_schema dict."""
    context.action = _create_default_action(
        inputs_schema={"type": schema_type, "required": [req_field]},
    )


@when("I create an action model with null inputs schema")
def step_create_action_model_with_null_inputs_schema(context: Context) -> None:
    """Create an action with inputs_schema=None."""
    context.action = _create_default_action(inputs_schema=None)


@then("the action model inputs schema should not be none")
def step_check_action_model_inputs_schema_not_none(context: Context) -> None:
    """Check inputs_schema is set."""
    assert context.action.inputs_schema is not None, "Expected inputs_schema to be set"


@then("the action model inputs schema should be none")
def step_check_action_model_inputs_schema_none(context: Context) -> None:
    """Check inputs_schema is None."""
    assert context.action.inputs_schema is None, (
        f"Expected inputs_schema to be None, got {context.action.inputs_schema}"
    )


@when("I try to create an action model with inputs schema as string")
def step_try_create_action_model_with_bad_inputs_schema(context: Context) -> None:
    """Try to create an action with inputs_schema as a non-dict."""
    context.error = None
    try:
        context.action = _create_default_action(inputs_schema="not a dict")
    except ValidationError as e:
        context.error = e


# ---------------------------------------------------------------------------
# automation_profile Validation Steps
# ---------------------------------------------------------------------------


@when('I create an action model with automation profile "{profile}"')
def step_create_action_model_with_automation_profile(
    context: Context, profile: str
) -> None:
    """Create an action with an automation_profile."""
    context.action = _create_default_action(automation_profile=profile)


@then('the action model automation profile should be "{expected}"')
def step_check_action_model_automation_profile(context: Context, expected: str) -> None:
    """Check the automation_profile value."""
    assert context.action.automation_profile == expected, (
        f"Expected '{expected}', got '{context.action.automation_profile}'"
    )


@then("the action model automation profile should be none")
def step_check_action_model_automation_profile_none(context: Context) -> None:
    """Check that automation_profile is None."""
    assert context.action.automation_profile is None, (
        f"Expected None, got '{context.action.automation_profile}'"
    )


@when("I create an action model with null automation profile")
def step_create_action_model_with_null_automation_profile(context: Context) -> None:
    """Create an action with automation_profile=None."""
    context.action = _create_default_action(automation_profile=None)
