"""Step definitions for resource-type inheritance: schema, chain, cycle, depth, guard.

Covers ADR-042 sections:
1. Schema validation
4. Chain resolution
6. Cycle detection
7. Depth limit
8. Parent removal guard
"""

from __future__ import annotations

from typing import Any

from _inheritance_test_helpers import (
    _ensure_registry,
    _make_child_entry,
    _make_root_entry,
    _names_from_csv,
)
from behave import given, then, when

from cleveragents.resource.inheritance import (
    ResourceTypeCircularInheritanceError,
    ResourceTypeInheritanceDepthError,
    ResourceTypeParentNotFoundError,
    ResourceTypeParentRemovalError,
    find_subtypes,
    resolve_inheritance_chain,
    validate_chain,
)
from cleveragents.resource.schema import ResourceTypeConfigSchema

# ═══════════════════════════════════════════════════════════════
# 1. Schema validation — GIVEN / WHEN / THEN
# ═══════════════════════════════════════════════════════════════


@given('a type-inherit YAML string with inherits "{parent}"')
def step_yaml_with_inherits(context: Any, parent: str) -> None:
    """Prepare a YAML string that declares an inherits field."""
    context.type_inherit_yaml = (  # type: ignore[attr-defined]
        f"name: acme/child-type\n"
        f"resource_kind: physical\n"
        f"sandbox_strategy: none\n"
        f"inherits: {parent}\n"
    )


@given("a type-inherit YAML string with no inherits")
def step_yaml_without_inherits(context: Any) -> None:
    """Prepare a YAML string with no inherits field."""
    context.type_inherit_yaml = (  # type: ignore[attr-defined]
        "name: acme/root-type\nresource_kind: physical\nsandbox_strategy: none\n"
    )


@given('a type-inherit YAML string where name equals inherits "{name}"')
def step_yaml_self_inherit(context: Any, name: str) -> None:
    """Prepare a YAML string where name == inherits (self-loop)."""
    context.type_inherit_yaml = (  # type: ignore[attr-defined]
        f"name: {name}\n"
        f"resource_kind: physical\n"
        f"sandbox_strategy: none\n"
        f"inherits: {name}\n"
    )


@given('a type-inherit built-in YAML inheriting from custom "{parent}"')
def step_yaml_builtin_inherits_custom(context: Any, parent: str) -> None:
    """Prepare a YAML for a built-in type inheriting from a custom type."""
    context.type_inherit_yaml = (  # type: ignore[attr-defined]
        "name: my-builtin\n"
        "resource_kind: physical\n"
        "sandbox_strategy: none\n"
        "built_in: true\n"
        f"inherits: {parent}\n"
    )


@given("a type-inherit YAML string with cli_args_replace true")
def step_yaml_with_replace_flag(context: Any) -> None:
    """Prepare a YAML string with a replace flag enabled."""
    context.type_inherit_yaml = (  # type: ignore[attr-defined]
        "name: acme/child-type\n"
        "resource_kind: physical\n"
        "sandbox_strategy: none\n"
        "inherits: container-instance\n"
        "cli_args_replace: true\n"
    )


@when("I parse the type-inherit YAML via ResourceTypeConfigSchema")
def step_parse_yaml(context: Any) -> None:
    """Parse the prepared YAML via the schema model."""
    context.type_inherit_schema = ResourceTypeConfigSchema.from_yaml(  # type: ignore[attr-defined]
        context.type_inherit_yaml  # type: ignore[attr-defined]
    )


@when("I attempt to parse the type-inherit YAML expecting an error")
def step_parse_yaml_error(context: Any) -> None:
    """Attempt to parse YAML and capture any validation error."""
    try:
        ResourceTypeConfigSchema.from_yaml(
            context.type_inherit_yaml  # type: ignore[attr-defined]
        )
        context.type_inherit_parse_error = None  # type: ignore[attr-defined]
    except (ValueError, TypeError) as exc:
        context.type_inherit_parse_error = exc  # type: ignore[attr-defined]


@then('the type-inherit schema inherits field is "{value}"')
def step_schema_inherits_value(context: Any, value: str) -> None:
    """Assert the parsed schema has the expected inherits value."""
    schema = context.type_inherit_schema  # type: ignore[attr-defined]
    assert schema.inherits == value, (
        f"Expected inherits={value!r}, got {schema.inherits!r}"
    )


@then("the type-inherit schema inherits field is null")
def step_schema_inherits_null(context: Any) -> None:
    """Assert the parsed schema has inherits=None."""
    schema = context.type_inherit_schema  # type: ignore[attr-defined]
    assert schema.inherits is None, f"Expected inherits=None, got {schema.inherits!r}"


@then('the type-inherit parse error mentions "{fragment}"')
def step_parse_error_mentions(context: Any, fragment: str) -> None:
    """Assert the captured parse error contains the expected fragment."""
    err = context.type_inherit_parse_error  # type: ignore[attr-defined]
    assert err is not None, "Expected a parse error but none was raised"
    assert fragment.lower() in str(err).lower(), (
        f"Expected error to mention {fragment!r}, got: {err}"
    )


@then("the type-inherit schema cli_args_replace is true")
def step_schema_cli_args_replace(context: Any) -> None:
    """Assert the replace flag is True."""
    schema = context.type_inherit_schema  # type: ignore[attr-defined]
    assert schema.cli_args_replace is True, (
        f"Expected cli_args_replace=True, got {schema.cli_args_replace!r}"
    )


# ═══════════════════════════════════════════════════════════════
# 4. Chain resolution — GIVEN / WHEN / THEN
# ═══════════════════════════════════════════════════════════════


@given('a type-inherit registry with "{name}" as root')
def step_registry_root(context: Any, name: str) -> None:
    """Add a root type (no inherits) to the registry."""
    reg = _ensure_registry(context)
    reg[name] = _make_root_entry()


@given('a type-inherit type "{child}" inheriting from "{parent}"')
def step_registry_child(context: Any, child: str, parent: str) -> None:
    """Add a child type to the registry."""
    reg = _ensure_registry(context)
    reg[child] = _make_child_entry(parent)


@given('a type-inherit registry also has "{name}" as root')
def step_registry_additional_root(context: Any, name: str) -> None:
    """Add another root type to the existing registry."""
    reg = _ensure_registry(context)
    reg[name] = _make_root_entry()


@when('I resolve the type-inherit chain for "{name}"')
def step_resolve_chain(context: Any, name: str) -> None:
    """Resolve the inheritance chain for the named type."""
    reg = _ensure_registry(context)
    context.type_inherit_chain = resolve_inheritance_chain(name, reg)  # type: ignore[attr-defined]


@when('I resolve the type-inherit chain for "{name}" expecting error')
def step_resolve_chain_error(context: Any, name: str) -> None:
    """Attempt to resolve a chain and capture the raised exception."""
    reg = _ensure_registry(context)
    try:
        resolve_inheritance_chain(name, reg)
        context.type_inherit_chain_error = None  # type: ignore[attr-defined]
    except (
        ResourceTypeParentNotFoundError,
        ResourceTypeCircularInheritanceError,
        ResourceTypeInheritanceDepthError,
    ) as exc:
        context.type_inherit_chain_error = exc  # type: ignore[attr-defined]


@when('I validate type-inherit chain for "{name}" inheriting "{parent}"')
def step_validate_chain(context: Any, name: str, parent: str) -> None:
    """Run validate_chain for a new type."""
    reg = _ensure_registry(context)
    context.type_inherit_validated_chain = validate_chain(name, parent, reg)  # type: ignore[attr-defined]


@when(
    'I validate type-inherit chain for "{name}" inheriting "{parent}" expecting error'
)
def step_validate_chain_error(context: Any, name: str, parent: str) -> None:
    """Run validate_chain and capture any error."""
    reg = _ensure_registry(context)
    try:
        validate_chain(name, parent, reg)
        context.type_inherit_validation_error = None  # type: ignore[attr-defined]
    except (ResourceTypeParentNotFoundError, ValueError) as exc:
        context.type_inherit_validation_error = exc  # type: ignore[attr-defined]


@then('the type-inherit chain is "{expected_csv}"')
def step_chain_value(context: Any, expected_csv: str) -> None:
    """Assert the chain matches the expected comma-separated list."""
    chain = context.type_inherit_chain  # type: ignore[attr-defined]
    expected = _names_from_csv(expected_csv)
    assert chain == expected, f"Expected chain {expected}, got {chain}"


@then('the type-inherit validated chain is "{expected_csv}"')
def step_validated_chain_value(context: Any, expected_csv: str) -> None:
    """Assert the validated chain matches."""
    chain = context.type_inherit_validated_chain  # type: ignore[attr-defined]
    expected = _names_from_csv(expected_csv)
    assert chain == expected, f"Expected validated chain {expected}, got {chain}"


@then("the type-inherit validation error is ResourceTypeParentNotFoundError")
def step_validation_error_parent_not_found(context: Any) -> None:
    """Assert the validation raised ResourceTypeParentNotFoundError."""
    err = context.type_inherit_validation_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, ResourceTypeParentNotFoundError), (
        f"Expected ResourceTypeParentNotFoundError, got {type(err).__name__}: {err}"
    )


# ═══════════════════════════════════════════════════════════════
# 6. Cycle detection — GIVEN / WHEN / THEN
# ═══════════════════════════════════════════════════════════════


@given('a type-inherit registry with "{name}" inheriting from "{parent}"')
def step_registry_entry_with_inherits(context: Any, name: str, parent: str) -> None:
    """Add a type that inherits from another (may create a cycle)."""
    reg = _ensure_registry(context)
    reg[name] = {"inherits": parent}


@then("the type-inherit chain error is ResourceTypeCircularInheritanceError")
def step_chain_error_circular(context: Any) -> None:
    """Assert the chain resolution raised a circular-inheritance error."""
    err = context.type_inherit_chain_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, ResourceTypeCircularInheritanceError), (
        f"Expected ResourceTypeCircularInheritanceError, got {type(err).__name__}: {err}"
    )


# ═══════════════════════════════════════════════════════════════
# 7. Depth limit — GIVEN / WHEN / THEN
# ═══════════════════════════════════════════════════════════════


@given("a type-inherit registry with a chain of depth {depth:d}")
def step_registry_deep_chain(context: Any, depth: int) -> None:
    """Build a linear chain of the requested depth.

    depth=5 means 5 types: level-0 (root), level-1, ... level-4.
    depth=6 means 6 types: level-0 (root), level-1, ... level-5.
    """
    reg = _ensure_registry(context)
    for i in range(depth):
        name = f"acme/level-{i}" if i > 0 else "level-0"
        if i == 0:
            reg[name] = _make_root_entry(description=f"Level {i}")
        else:
            parent = f"acme/level-{i - 1}" if i > 1 else "level-0"
            reg[name] = _make_child_entry(parent, description=f"Level {i}")
    # Store the deepest type name for subsequent steps.
    deepest = f"acme/level-{depth - 1}" if depth > 1 else "level-0"
    context.type_inherit_deepest = deepest  # type: ignore[attr-defined]


@when("I resolve the type-inherit chain for the deepest type expecting error")
def step_resolve_deepest_error(context: Any) -> None:
    """Resolve chain for the deepest type, expecting an error."""
    reg = _ensure_registry(context)
    deepest = context.type_inherit_deepest  # type: ignore[attr-defined]
    try:
        resolve_inheritance_chain(deepest, reg)
        context.type_inherit_chain_error = None  # type: ignore[attr-defined]
    except ResourceTypeInheritanceDepthError as exc:
        context.type_inherit_chain_error = exc  # type: ignore[attr-defined]


@when("I resolve the type-inherit chain for the deepest type")
def step_resolve_deepest(context: Any) -> None:
    """Resolve chain for the deepest type (should succeed)."""
    reg = _ensure_registry(context)
    deepest = context.type_inherit_deepest  # type: ignore[attr-defined]
    context.type_inherit_chain = resolve_inheritance_chain(deepest, reg)  # type: ignore[attr-defined]


@then("the type-inherit chain error is ResourceTypeInheritanceDepthError")
def step_chain_error_depth(context: Any) -> None:
    """Assert the chain resolution raised a depth-limit error."""
    err = context.type_inherit_chain_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, ResourceTypeInheritanceDepthError), (
        f"Expected ResourceTypeInheritanceDepthError, got {type(err).__name__}: {err}"
    )


@then("the type-inherit chain length is {length:d}")
def step_chain_length(context: Any, length: int) -> None:
    """Assert the resolved chain has the expected length."""
    chain = context.type_inherit_chain  # type: ignore[attr-defined]
    assert len(chain) == length, f"Expected chain length {length}, got {len(chain)}"


# ═══════════════════════════════════════════════════════════════
# 8. Parent removal guard — WHEN / THEN
# ═══════════════════════════════════════════════════════════════


@when('I attempt to type-inherit remove "{name}"')
def step_attempt_remove(context: Any, name: str) -> None:
    """Attempt to remove a type and raise if subtypes exist.

    Mirrors the guard in ``ResourceRegistryService.remove_type()``
    (ADR-042 Rule 5) using the in-memory registry.  We cannot call the
    real service here because these BDD tests are DB-free engine tests.
    """
    reg = _ensure_registry(context)
    try:
        subtypes = find_subtypes(name, reg)
        if subtypes:
            raise ResourceTypeParentRemovalError(
                f"Cannot remove '{name}': subtypes exist: {subtypes}"
            )
        del reg[name]
        context.type_inherit_removal_error = None  # type: ignore[attr-defined]
    except ResourceTypeParentRemovalError as exc:
        context.type_inherit_removal_error = exc  # type: ignore[attr-defined]


@then("the type-inherit removal error is ResourceTypeParentRemovalError")
def step_removal_error(context: Any) -> None:
    """Assert the removal raised ResourceTypeParentRemovalError."""
    err = context.type_inherit_removal_error  # type: ignore[attr-defined]
    assert err is not None, "Expected an error but none was raised"
    assert isinstance(err, ResourceTypeParentRemovalError), (
        f"Expected ResourceTypeParentRemovalError, got {type(err).__name__}: {err}"
    )


# ═══════════════════════════════════════════════════════════════
# Side-effect verification — THEN steps
# ═══════════════════════════════════════════════════════════════


@then('the type-inherit registry still contains "{name}"')
def step_registry_still_contains(context: Any, name: str) -> None:
    """Assert the registry was not modified by the error (side-effect check)."""
    reg = _ensure_registry(context)
    assert name in reg, f"Expected '{name}' in registry, got keys: {list(reg.keys())}"


@then("the type-inherit registry size is {size:d}")
def step_registry_size(context: Any, size: int) -> None:
    """Assert the registry has the expected number of entries."""
    reg = _ensure_registry(context)
    assert len(reg) == size, f"Expected {size} entries, got {len(reg)}"
