"""Step definitions for AutoDebug integration tests."""

import os
from unittest.mock import MagicMock, Mock, patch

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

from cleveragents.application.container import get_container, reset_container


@given('I have a current plan with instruction "{instruction}"')
def step_create_plan_with_instruction(context: Context, instruction: str) -> None:
    """Create a plan with given instruction."""
    from cleveragents.cli.commands.plan import tell_command

    # Ensure we have a project first
    if not hasattr(context, "project"):
        container = get_container()
        project_service = container.project_service()
        context.project = project_service.get_current_project()

    tell_command(prompt=instruction, name=None)

    # Get the plan from container
    container = get_container()
    plan_service = container.plan_service()
    context.plan = plan_service.get_current_plan(context.project)


@given("the AI provider will generate code with a syntax error")
def step_ai_generates_syntax_error(context: Context) -> None:
    """Configure mock AI to generate code with syntax error."""
    context.mock_error_type = "syntax"
    context.mock_build_error = "SyntaxError: invalid syntax on line 5"
    context.mock_generated_code = (
        "def foo():\n    print('hello'\n"  # Missing closing paren
    )


@given("the AutoDebugAgent can fix the syntax error")
def step_autodebug_can_fix_syntax(context: Context) -> None:
    """Configure AutoDebugAgent to successfully fix syntax error."""
    context.autodebug_can_fix = True
    context.autodebug_fix_code = "def foo():\n    print('hello')\n"


@given("the AI provider will generate code with an import error")
def step_ai_generates_import_error(context: Context) -> None:
    """Configure mock AI to generate code with import error."""
    context.mock_error_type = "import"
    context.mock_build_error = "ImportError: No module named 'nonexistent'"
    context.mock_generated_code = "import nonexistent\n\ndef foo():\n    pass\n"


@given("the AutoDebugAgent can fix the import error")
def step_autodebug_can_fix_import(context: Context) -> None:
    """Configure AutoDebugAgent to successfully fix import error."""
    context.autodebug_can_fix = True
    context.autodebug_fix_code = "# Removed bad import\n\ndef foo():\n    pass\n"


@given("the AI provider will generate code with an unfixable error")
def step_ai_generates_unfixable_error(context: Context) -> None:
    """Configure mock AI to generate code with unfixable error."""
    context.mock_error_type = "unfixable"
    context.mock_build_error = "RuntimeError: System configuration error"
    context.mock_generated_code = "# This error cannot be fixed by code changes\n"


@given("the AutoDebugAgent cannot fix the error")
def step_autodebug_cannot_fix(context: Context) -> None:
    """Configure AutoDebugAgent to fail at fixing error."""
    context.autodebug_can_fix = False


@given("the AI provider will generate code with a validation error")
def step_ai_generates_validation_error(context: Context) -> None:
    """Configure mock AI to generate code with validation error."""
    context.mock_error_type = "validation"
    context.mock_build_error = "ValidationError: Code does not meet requirements"
    context.mock_generated_code = "# Code that fails validation\n"


@when("I run auto_debug_build with max attempts {max_attempts:d}")
def step_run_auto_debug_build(context: Context, max_attempts: int) -> None:
    """Run auto_debug_build with specified max attempts."""
    from cleveragents.application.container import get_container

    container = get_container()
    plan_service = container.plan_service()

    # Mock the build_plan to simulate failure
    original_build = plan_service.build_plan

    def mock_build(project, progress_callback=None):
        if hasattr(context, "mock_build_error") and not getattr(
            context, "build_succeeded", False
        ):
            # First attempt fails
            if getattr(context, "autodebug_can_fix", False):
                # Mark that next attempt should succeed
                context.build_succeeded = True
            raise Exception(context.mock_build_error)
        # Subsequent attempts succeed if AutoDebug can fix
        return original_build(project, progress_callback=progress_callback)

    # Create mock LLM that returns deterministic responses
    mock_llm = MagicMock()
    mock_response = Mock()
    mock_response.content = "Mock LLM response"
    mock_llm.invoke = MagicMock(return_value=mock_response)

    # Inject mock LLM into the plan service so AutoDebugAgent receives it
    plan_service._llm = mock_llm

    # Patch build_plan and the AutoDebugAgent class
    with (
        patch.object(plan_service, "build_plan", side_effect=mock_build),
        patch("cleveragents.agents.AutoDebugAgent") as agent_cls,
    ):
        agent_instance = MagicMock()
        agent_instance.invoke.return_value = {
            "result": {"success": True, "fix": {}},
        }
        agent_cls.return_value = agent_instance
        try:
            success, changes, error = plan_service.auto_debug_build(
                project=context.project, max_attempts=max_attempts
            )
            context.auto_debug_success = success
            context.auto_debug_changes = changes
            context.auto_debug_error = error
        except Exception as e:
            context.auto_debug_success = False
            context.auto_debug_error = str(e)
            context.auto_debug_changes = []


@then("the build should succeed after debugging")
def step_build_succeeds(context: Context) -> None:
    """Verify build succeeded after auto-debug."""
    assert context.auto_debug_success, f"Build failed: {context.auto_debug_error}"


@then("the build should fail after {attempts:d} attempts")
def step_build_fails_after_attempts(context: Context, attempts: int) -> None:
    """Verify build failed after specified attempts."""
    assert not context.auto_debug_success, "Build should have failed but succeeded"


@then("debug attempts should be saved to database")
def step_debug_attempts_saved(context: Context) -> None:
    """Verify debug attempts were saved to database."""
    from cleveragents.application.container import get_container

    container = get_container()
    with container.unit_of_work().transaction() as uow:
        attempts = uow.debug_attempts.get_for_plan(context.plan.id)
        assert len(attempts) > 0, "No debug attempts saved to database"
        context.saved_debug_attempts = attempts


@then("the final attempt should be marked as successful")
def step_final_attempt_successful(context: Context) -> None:
    """Verify final debug attempt is marked successful."""
    assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"

    # Find the highest attempt number
    max_attempt = max(a.attempt_number for a in context.saved_debug_attempts)
    final_attempt = next(
        a for a in context.saved_debug_attempts if a.attempt_number == max_attempt
    )

    assert final_attempt.success, "Final attempt not marked as successful"


@then("at least {count:d} debug attempt should exist")
def step_at_least_n_attempts(context: Context, count: int) -> None:
    """Verify at least N debug attempts exist."""
    assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
    assert len(context.saved_debug_attempts) >= count, (
        f"Expected at least {count} attempts, got {len(context.saved_debug_attempts)}"
    )


@then("all {count:d} debug attempts should be marked as unsuccessful")
def step_all_attempts_unsuccessful(context: Context, count: int) -> None:
    """Verify all attempts are marked unsuccessful."""
    assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"
    assert len(context.saved_debug_attempts) == count, (
        f"Expected {count} attempts, got {len(context.saved_debug_attempts)}"
    )

    for attempt in context.saved_debug_attempts:
        assert not attempt.success, (
            f"Attempt {attempt.attempt_number} should be unsuccessful"
        )


@then("each attempt should have an error message")
def step_attempts_have_error_messages(context: Context) -> None:
    """Verify each attempt has an error message."""
    assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"

    for attempt in context.saved_debug_attempts:
        assert attempt.error_message, (
            f"Attempt {attempt.attempt_number} missing error message"
        )


@then("each attempt should have an attempt number")
def step_attempts_have_numbers(context: Context) -> None:
    """Verify each attempt has an attempt number."""
    assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"

    for attempt in context.saved_debug_attempts:
        assert attempt.attempt_number is not None, "Attempt missing attempt number"
        assert attempt.attempt_number >= 1, (
            f"Invalid attempt number: {attempt.attempt_number}"
        )


@then("each attempt should have a timestamp")
def step_attempts_have_timestamps(context: Context) -> None:
    """Verify each attempt has a timestamp."""
    assert hasattr(context, "saved_debug_attempts"), "No debug attempts found"

    for attempt in context.saved_debug_attempts:
        assert attempt.created_at, f"Attempt {attempt.attempt_number} missing timestamp"


def after_scenario(context: Context, scenario) -> None:
    """Clean up after each scenario."""
    # Change back to original directory
    if hasattr(context, "original_cwd"):
        os.chdir(context.original_cwd)

    # Clean up temp directory
    if hasattr(context, "test_dir"):
        import shutil

        shutil.rmtree(context.test_dir, ignore_errors=True)

    # Reset container
    reset_container()

    # Clean up environment
    os.environ.pop("CLEVERAGENTS_TESTING_USE_MOCK_AI", None)
