"""Step definitions for actor YAML examples validation.

Tests for features/actor_examples.feature — validates that all actor examples
are correct, loadable, and demonstrate the expected patterns.

Note: Most validation steps are reused from actor_schema_steps.py.
Only examples-specific steps are defined here.
"""

from __future__ import annotations

from pathlib import Path

from behave import given, then  # type: ignore[import-untyped]
from behave.runner import Context  # type: ignore[import-untyped]

# ────────────────────────────────────────────────────────────
# Examples-Specific Given Steps
# ────────────────────────────────────────────────────────────


@given("the CleverAgents actor schema validation is available")
def step_given_schema_available(context: Context) -> None:
    """Verify that ActorConfigSchema is importable and ready."""
    from cleveragents.actor.schema import ActorConfigSchema

    context.schema_available = ActorConfigSchema is not None
    assert context.schema_available


@given('the actor examples YAML file "{file_path}"')
def step_given_actor_examples_yaml_file(context: Context, file_path: str) -> None:
    """Load actor YAML file path for validation (examples-specific)."""
    context.actor_yaml_file = file_path
    context.actor_yaml_string = None


@given("the examples/actors directory")
def step_given_examples_directory(context: Context) -> None:
    """Set examples directory path."""
    project_root = Path(__file__).resolve().parents[2]
    context.examples_dir = project_root / "examples" / "actors"
    assert context.examples_dir.exists(), (
        f"Examples directory not found: {context.examples_dir}"
    )


@given('the documentation file "{doc_path}"')
def step_given_documentation_file(context: Context, doc_path: str) -> None:
    """Load documentation file for verification."""
    project_root = Path(__file__).resolve().parents[2]
    context.doc_file = project_root / doc_path


# Generic step for all "Given an actor YAML string ..." patterns
@given("an actor YAML string representing a strategist actor:")
@given("an actor YAML string representing an executor actor:")
@given("an actor YAML string representing a reviewer actor:")
@given("an actor YAML string with tool-only configuration:")
@given("an actor YAML string with git tools:")
@given("an actor YAML string representing a validator actor:")
@given("an actor YAML string with linear graph workflow:")
@given("an actor YAML string with conditional routing examples:")
@given("an actor YAML string with tool nodes examples:")
@given("an actor YAML string with subgraph examples:")
@given("an actor YAML string with retry logic:")
@given("an actor YAML string with multi-level hierarchy:")
@given("an actor YAML string missing required name field for examples:")
@given("an actor YAML string with invalid name for examples:")
@given("an actor YAML string for LLM without model for examples:")
@given("an actor YAML string for TOOL without tools for examples:")
@given("an actor YAML string for GRAPH without route for examples:")
@given("an actor YAML string with cyclic graph for examples:")
def step_given_actor_yaml_string_pattern(context: Context) -> None:
    """Load actor YAML string from docstring (generic for all patterns)."""
    context.actor_yaml_string = context.text
    context.actor_yaml_file = None


# ────────────────────────────────────────────────────────────
# Additional Then Steps for Actor Examples (not in actor_schema_steps.py)
# ────────────────────────────────────────────────────────────


@then('the actor config description should contain "{expected_text}"')
def step_then_description_contains(context: Context, expected_text: str) -> None:
    """Verify actor description contains text."""
    assert context.actor_config is not None
    assert expected_text.lower() in context.actor_config.description.lower()


@then('the actor config context_view should be "{expected_view}"')
def step_then_config_context_view_is(context: Context, expected_view: str) -> None:
    """Verify context_view matches (with 'config' in step name)."""
    assert context.actor_config is not None
    assert context.actor_config.context_view == expected_view


@then("the actor config should have read-only tools")
def step_then_has_readonly_tools(context: Context) -> None:
    """Verify actor has read-only tools."""
    assert context.actor_config is not None
    assert context.actor_config.tools is not None
    # Check for typical read-only tool patterns
    readonly_patterns = ["read", "list", "get", "show", "view"]
    tools_str = " ".join(str(t) for t in context.actor_config.tools).lower()
    assert any(p in tools_str for p in readonly_patterns)


@then("the actor config should have write-capable tools")
def step_then_has_write_tools(context: Context) -> None:
    """Verify actor has write-capable tools."""
    assert context.actor_config is not None
    assert context.actor_config.tools is not None
    write_patterns = ["write", "create", "update", "delete", "modify"]
    tools_str = " ".join(str(t) for t in context.actor_config.tools).lower()
    assert any(p in tools_str for p in write_patterns)


@then("the actor config should have linting tools")
def step_then_has_linting_tools(context: Context) -> None:
    """Verify actor has linting tools."""
    assert context.actor_config is not None
    assert context.actor_config.tools is not None
    lint_patterns = ["lint", "format", "check", "ruff"]
    tools_str = " ".join(str(t) for t in context.actor_config.tools).lower()
    assert any(p in tools_str for p in lint_patterns)


@then("the actor config should have validation tools")
def step_then_has_validation_tools(context: Context) -> None:
    """Verify actor has validation tools."""
    assert context.actor_config is not None
    assert context.actor_config.tools is not None
    validation_patterns = ["validate", "verify", "test", "check"]
    tools_str = " ".join(str(t) for t in context.actor_config.tools).lower()
    assert any(p in tools_str for p in validation_patterns)


@then("the actor config should not have model field")
def step_then_no_model(context: Context) -> None:
    """Verify actor doesn't have model field."""
    assert context.actor_config is not None
    assert context.actor_config.model is None or context.actor_config.model == ""


@then("the actor config should not have system_prompt field")
def step_then_no_system_prompt(context: Context) -> None:
    """Verify actor doesn't have system_prompt field."""
    assert context.actor_config is not None
    assert (
        context.actor_config.system_prompt is None
        or context.actor_config.system_prompt == ""
    )


@then('the actor route entry_node should be "{expected_node}"')
def step_then_entry_node_is(context: Context, expected_node: str) -> None:
    """Verify entry node ID."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert context.actor_config.route.entry_node == expected_node


@then("the actor route should have {count:d} exit node")
@then("the actor route should have {count:d} exit nodes")
def step_then_route_exit_node_count(context: Context, count: int) -> None:
    """Verify number of exit nodes."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert len(context.actor_config.route.exit_nodes) == count


@then("the actor route should have conditional node")
def step_then_has_conditional_node(context: Context) -> None:
    """Verify route has conditional node."""
    from cleveragents.actor.schema import NodeType

    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert any(n.type == NodeType.CONDITIONAL for n in context.actor_config.route.nodes)


@then("the actor route should have tool node")
def step_then_has_tool_node(context: Context) -> None:
    """Verify route has tool node."""
    from cleveragents.actor.schema import NodeType

    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert any(n.type == NodeType.TOOL for n in context.actor_config.route.nodes)


@then("the actor route should have agent nodes")
def step_then_has_agent_nodes(context: Context) -> None:
    """Verify route has agent nodes."""
    from cleveragents.actor.schema import NodeType

    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert any(n.type == NodeType.AGENT for n in context.actor_config.route.nodes)


@then("the actor route should have subgraph nodes")
def step_then_has_subgraph_nodes(context: Context) -> None:
    """Verify route has subgraph nodes."""
    from cleveragents.actor.schema import NodeType

    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert any(n.type == NodeType.SUBGRAPH for n in context.actor_config.route.nodes)


@then("the actor route should have multiple exit nodes")
def step_then_has_multiple_exit_nodes(context: Context) -> None:
    """Verify route has multiple exit nodes."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert len(context.actor_config.route.exit_nodes) > 1


@then("the actor route should have retry loop")
def step_then_has_retry_loop(context: Context) -> None:
    """Verify route has retry logic (nodes/edges for retry)."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    # Check for nodes with "retry" in name or description
    node_texts = [
        n.name.lower() + (n.description or "").lower()
        for n in context.actor_config.route.nodes
    ]
    assert any("retry" in text for text in node_texts)


@then("the actor route should have escalation path")
def step_then_has_escalation_path(context: Context) -> None:
    """Verify route has escalation path."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    node_texts = [
        n.name.lower() + (n.description or "").lower()
        for n in context.actor_config.route.nodes
    ]
    assert any("escalat" in text for text in node_texts)


@then("the actor route should have multiple subgraph levels")
def step_then_has_multiple_subgraph_levels(context: Context) -> None:
    """Verify route has multiple levels of subgraphs."""
    from cleveragents.actor.schema import NodeType

    assert context.actor_config is not None
    assert context.actor_config.route is not None
    subgraph_count = sum(
        1 for n in context.actor_config.route.nodes if n.type == NodeType.SUBGRAPH
    )
    assert subgraph_count >= 2


@then("the actor route should have parallel execution config")
@then("the actor route should support parallel execution")
def step_then_has_parallel_execution(context: Context) -> None:
    """Verify route supports parallel execution."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None

    # Check for two things:
    # 1. Nodes with parallel config
    # 2. Nodes with multiple outgoing edges (parallel branching)
    from collections import defaultdict

    # Check for parallel config in subgraph nodes
    has_parallel_config = False
    for node in context.actor_config.route.nodes:
        if node.type == "subgraph" and hasattr(node, "config") and node.config:
            # Config might be a dict or an object
            if isinstance(node.config, dict):
                has_parallel_config = node.config.get("parallel", False)
            else:
                has_parallel_config = getattr(node.config, "parallel", False)
            if has_parallel_config:
                break

    # Check for nodes with multiple outgoing edges
    edge_counts = defaultdict(int)
    for edge in context.actor_config.route.edges:
        edge_counts[edge.from_node] += 1
    has_multiple_edges = any(count > 1 for count in edge_counts.values())

    # Either condition satisfies parallel execution
    assert has_parallel_config or has_multiple_edges, (
        f"Route should have parallel execution via config or multiple outgoing edges "
        f"(found parallel_config={has_parallel_config}, multiple_edges={has_multiple_edges})"
    )


@then("the actor memory max_messages should be at least {minimum:d}")
def step_then_memory_max_messages_at_least(context: Context, minimum: int) -> None:
    """Verify memory.max_messages is at least minimum."""
    assert context.actor_config is not None
    assert context.actor_config.memory is not None
    assert context.actor_config.memory.max_messages >= minimum


@then("the actor context max_context_tokens should be {expected:d}")
def step_then_context_max_tokens(context: Context, expected: int) -> None:
    """Verify context.max_context_tokens value."""
    assert context.actor_config is not None
    assert context.actor_config.context is not None
    assert context.actor_config.context.max_context_tokens == expected


@then('the actor config name should match pattern "{pattern}"')
def step_then_name_matches_pattern(context: Context, pattern: str) -> None:
    """Verify actor name matches regex pattern."""
    import re

    assert context.actor_config is not None
    assert re.match(pattern, context.actor_config.name)


@then("the actor route should have at least {minimum:d} nodes")
def step_then_route_has_min_nodes(context: Context, minimum: int) -> None:
    """Verify route has minimum number of nodes."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert len(context.actor_config.route.nodes) >= minimum


@then("the actor route should have at least {minimum:d} edges")
def step_then_route_has_min_edges(context: Context, minimum: int) -> None:
    """Verify route has minimum number of edges."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert len(context.actor_config.route.edges) >= minimum


@then("the actor route should have an entry_node")
def step_then_route_has_entry_node(context: Context) -> None:
    """Verify route has entry_node."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert context.actor_config.route.entry_node is not None


@then("the actor route should have exit_nodes")
def step_then_route_has_exit_nodes(context: Context) -> None:
    """Verify route has exit_nodes."""
    assert context.actor_config is not None
    assert context.actor_config.route is not None
    assert context.actor_config.route.exit_nodes is not None
    assert len(context.actor_config.route.exit_nodes) > 0


@then("the actor config should have environment variables")
def step_then_has_env_vars(context: Context) -> None:
    """Verify actor has environment variables."""
    assert context.actor_config is not None
    assert context.actor_config.env_vars is not None
    assert len(context.actor_config.env_vars) > 0


# ────────────────────────────────────────────────────────────
# Examples-Specific Then Steps
# ────────────────────────────────────────────────────────────


@then("there should be exactly {count:d} example YAML files")
def step_then_count_example_files(context: Context, count: int) -> None:
    """Verify the number of example YAML files."""
    yaml_files = list(context.examples_dir.glob("*.yaml"))
    assert len(yaml_files) == count, (
        f"Expected {count} YAML files in examples/actors/, "
        f"found {len(yaml_files)}: {[f.name for f in yaml_files]}"
    )


@then('the example files should include "{filename}"')
def step_then_example_file_exists(context: Context, filename: str) -> None:
    """Verify specific example file exists."""
    file_path = context.examples_dir / filename
    assert file_path.exists(), f"Expected example file not found: {file_path}"


@then('the documentation should reference "{filename}"')
def step_then_doc_references_file(context: Context, filename: str) -> None:
    """Verify documentation references the example file."""
    assert hasattr(context, "doc_file"), "Documentation file not loaded"
    content = context.doc_file.read_text()
    assert filename in content, f"Documentation should reference {filename}"


@then("the documentation should have examples for strategist actors")
def step_then_doc_has_strategist_examples(context: Context) -> None:
    """Verify documentation has strategist examples."""
    content = context.doc_file.read_text()
    assert "strategist" in content.lower() or "strategy" in content.lower()


@then("the documentation should have examples for executor actors")
def step_then_doc_has_executor_examples(context: Context) -> None:
    """Verify documentation has executor examples."""
    content = context.doc_file.read_text()
    assert "executor" in content.lower() or "execution" in content.lower()


@then("the documentation should have examples for reviewer actors")
def step_then_doc_has_reviewer_examples(context: Context) -> None:
    """Verify documentation has reviewer examples."""
    content = context.doc_file.read_text()
    assert "reviewer" in content.lower() or "review" in content.lower()


@then("the documentation should have examples for tool-only actors")
def step_then_doc_has_tool_only_examples(context: Context) -> None:
    """Verify documentation has tool-only examples."""
    content = context.doc_file.read_text()
    assert "tool-only" in content.lower() or "tool collection" in content.lower()


@then("the documentation should have examples for validation actors")
def step_then_doc_has_validation_examples(context: Context) -> None:
    """Verify documentation has validation examples."""
    content = context.doc_file.read_text()
    assert "validation" in content.lower() or "validator" in content.lower()


@then("the documentation should have examples for hierarchical graphs")
def step_then_doc_has_hierarchical_examples(context: Context) -> None:
    """Verify documentation has hierarchical graph examples."""
    content = context.doc_file.read_text()
    assert "hierarchical" in content.lower() or "subgraph" in content.lower()
