"""
BDD steps for Result object and output features.
"""

from behave import given, when, then
import os


@given("a simple workflow file with an agent")
def step_given_simple_workflow(context):
    """Load or create a simple workflow for testing."""
    context.workflow_file = "examples/04-basics-simple-agent.tac"
    assert os.path.exists(context.workflow_file), f"{context.workflow_file} should exist"


@given("a simple workflow file with agents")
def step_given_workflow_with_agents(context):
    """Load a workflow with agents."""
    context.workflow_file = "examples/04-basics-simple-agent.tac"
    assert os.path.exists(context.workflow_file), f"{context.workflow_file} should exist"


@given("a Lua procedure that calls an agent")
def step_given_lua_procedure_calls_agent(context):
    """Define that we have a procedure calling an agent."""
    context.has_agent_call = True


@given("a procedure that logs the result data")
def step_given_procedure_logs_result(context):
    """Define procedure that logs result data."""
    context.logs_result = True


@given("a workflow with an agent that has output defined")
def step_given_workflow_with_output(context):
    """Load workflow with output."""
    context.workflow_file = "examples/12-feature-structured-output.tac"
    assert os.path.exists(context.workflow_file), f"{context.workflow_file} should exist"


@given('the example file "{filename}"')
def step_given_example_file(context, filename):
    """Load an example file."""
    from pathlib import Path

    examples_dir = Path("examples")

    # Try exact match first
    workflow_file = examples_dir / filename
    if workflow_file.exists():
        context.workflow_file = str(workflow_file)
        return

    # Extract the base name without extension for fuzzy matching
    base_name = filename.replace(".tac", "").replace(".lua", "")

    # Split into words for matching
    base_words = set(base_name.replace("-", " ").split())

    # Try to find file with word-based matching
    # e.g., "hello-world.tac" -> "01-basics-hello-world.tac"
    # or "structured-output-demo.tac" -> "12-feature-structured-output.tac"
    best_match = None
    best_score = 0

    for example_file in examples_dir.glob("*.tac"):
        file_base = example_file.stem  # filename without extension

        # Exact substring match (highest priority)
        if base_name in file_base or file_base.endswith(base_name):
            context.workflow_file = str(example_file)
            return

        # Word-based matching (fallback)
        file_words = set(file_base.replace("-", " ").split())
        overlap = base_words & file_words
        score = len(overlap)

        if score > best_score:
            best_score = score
            best_match = example_file

    # Use best match if we found at least 2 matching words
    if best_match and best_score >= 2:
        context.workflow_file = str(best_match)
        return

    # Not found
    raise AssertionError(f"Example {filename} not found in {examples_dir}")


@when("the procedure executes")
def step_when_procedure_executes(context):
    """Mark that procedure would execute."""
    context.procedure_executed = True


@when("the workflow is validated")
def step_when_workflow_validated(context):
    """Validate the workflow."""
    import subprocess
    import sys

    result = subprocess.run(
        [sys.executable, "-m", "tactus.cli.app", "validate", context.workflow_file],
        capture_output=True,
        text=True,
    )
    context.validation_exit_code = result.returncode
    context.validation_output = result.stdout + result.stderr


@when("the file is validated")
def step_when_file_validated(context):
    """Validate the workflow file."""
    step_when_workflow_validated(context)


@when("the workflow is parsed")
def step_when_workflow_parsed(context):
    """Parse the workflow."""
    from tactus.core.runtime import TactusRuntime

    try:
        # Use TactusRuntime to parse
        runtime = TactusRuntime(context.workflow_file)
        context.parsed_config = runtime.config
        context.parsing_succeeded = True
        context.runtime_instance = runtime
    except Exception as e:
        context.parsing_succeeded = False
        context.parsing_error = str(e)


@then("the agent should return a result object")
def step_then_returns_result_object(context):
    """Verify agent returns result object."""
    assert context.has_agent_call, "Agent should be called"
    # This is verified by the fact that turn() returns ResultPrimitive


@then("the result should have a data property")
def step_then_result_has_data(context):
    """Result should have data property."""
    assert True, "ResultPrimitive has data property"


@then("the result should have usage information")
def step_then_result_has_usage(context):
    """Result should have usage info."""
    assert True, "ResultPrimitive has usage property"


@then("usage should include {field}")
def step_then_usage_includes(context, field):
    """Check usage has field."""
    valid_fields = ["total_tokens", "prompt_tokens", "completion_tokens"]
    assert field in valid_fields, f"Usage should have {field}"


@then("the result should have {method} method")
def step_then_result_has_method(context, method):
    """Check result has method."""
    method_name = method.replace("()", "")
    assert True, f"ResultPrimitive has {method_name} method"


@then("the procedure should complete successfully")
def step_then_procedure_succeeds(context):
    """Procedure completes successfully."""
    assert context.procedure_executed, "Procedure should execute"

    # Verify procedure result and additional error context
    # Only check for procedure_result if we actually ran a real procedure
    # (not just set the flag in a mock scenario)
    if hasattr(context, "builder") and hasattr(context, "procedure_result"):
        if context.procedure_result is None:
            # If parse_error exists, raise it to provide context
            if hasattr(context, "parse_error") and context.parse_error is not None:
                raise AssertionError(f"Procedure failed to execute: {context.parse_error}")
            raise AssertionError("Procedure did not produce a result")


@then("the output should contain result information")
def step_then_output_contains_info(context):
    """Output contains result info."""
    assert context.logs_result, "Should log result"


@then("the workflow validates successfully")
def step_then_workflow_validates(context):
    """Workflow validation should pass."""
    assert context.validation_exit_code == 0, f"Validation failed: {context.validation_output}"


@then("it should parse successfully")
def step_then_parse_succeeds(context):
    """Parsing succeeds."""
    assert context.validation_exit_code == 0, f"Parsing failed: {context.validation_output}"


@then("it should have an agent with output")
def step_then_has_output_agent(context):
    """Agent has output."""
    # This is implicit if the file validates and has output defined
    assert "output" in context.validation_output or context.validation_exit_code == 0


@then("the schema should be converted to a Pydantic model")
def step_then_schema_to_pydantic(context):
    """Schema converts to Pydantic model."""
    assert context.parsing_succeeded, "Parsing should succeed for conversion"
    # The runtime will have converted output to Pydantic internally
    assert hasattr(context, "runtime_instance"), "Runtime should exist"


@then("the model should have the defined fields")
def step_then_model_has_fields(context):
    """Model has fields."""
    assert context.parsed_config is not None, "Config should be parsed"
    # Verify that agents have output if defined
    agents = context.parsed_config.get("agents", {})
    assert len(agents) > 0, "Should have agents defined"


@given("a workflow with output fields")
def step_given_output_fields(context):
    """Define workflow with output fields."""
    context.workflow_file = "examples/12-feature-structured-output.tac"
    assert os.path.exists(context.workflow_file), f"{context.workflow_file} should exist"


@given("a workflow with output including:")
def step_given_output_with_types(context):
    """Define output with various types."""
    context.outputs = []
    for row in context.table:
        context.outputs.append(row["type"])


@then("all field types should be recognized")
def step_then_types_recognized(context):
    """All types should be recognized."""
    valid_types = ["string", "number", "integer", "boolean", "object", "array"]
    for type_name in context.outputs:
        assert type_name in valid_types, f"Type {type_name} should be recognized"


@then("the types should map correctly")
def step_then_types_map(context):
    """Types should map to Python types."""
    # _map_type_string function does this mapping
    assert True, "Types should map correctly to Python types"
