"""Step definitions for CLI coverage tests."""

import shlex
import subprocess
import sys
from contextlib import redirect_stdout
from io import StringIO
from unittest.mock import patch

from behave import then, when
from click.exceptions import Exit as ClickExit
from typer import Exit

from cleveragents.cli.main import (
    _print_basic_help,
    app,
    build,
    convert_exit_code,
    ensure_cli_commands_registered,
    main,
    tell,
)


@when('I run shell command "{command}"')
def step_run_shell_command(context, command):
    """Run a CLI command using subprocess shell."""
    try:
        # Check if we have a test directory to run from
        cwd = context.test_dir if hasattr(context, "test_dir") else None

        # Replace 'cleveragents' with the current Python interpreter running the module
        # This ensures we use the same Python (from nox venv) that has all dependencies
        if command.startswith("cleveragents"):
            # Handle both "cleveragents --arg" and "cleveragents command --arg"
            # Use cleveragents.cli.main since cleveragents.cli is a package without __main__.py
            python_cmd = f'"{sys.executable}" -m cleveragents.cli.main'
            if command == "cleveragents":
                command = python_cmd
            else:
                command = python_cmd + command[len("cleveragents") :]

        result = subprocess.run(
            shlex.split(command), capture_output=True, text=True, timeout=120, cwd=cwd
        )
        context.exit_code = result.returncode
        context.output = result.stdout + result.stderr

        # Check if command failed due to missing AI provider
        # If so, skip the scenario as this is expected in test environments
        if context.exit_code != 0:
            output = context.output.lower()
            if "no ai provider configured" in output or (
                "ai provider" in output and "not" in output
            ):
                context.scenario.skip(
                    "Skipping test - no AI provider configured (expected in test env)"
                )
    except subprocess.TimeoutExpired:
        context.exit_code = -1
        context.output = "Command timed out"
    except Exception as e:
        context.exit_code = -1
        context.output = str(e)


@then("the shell command should succeed")
def step_shell_command_succeeds(context):
    """Check that shell command succeeded."""
    assert context.exit_code == 0, f"Expected exit code 0, got {context.exit_code}"


@then("the command should fail with exit code {code:d}")
def step_command_fails_with_code(context, code):
    """Check that command failed with specific code."""
    assert context.exit_code == code, (
        f"Expected exit code {code}, got {context.exit_code}"
    )


@then('the shell output should contain "{text}"')
def step_shell_output_contains(context, text):
    """Check that shell output contains text."""
    assert text in context.output, f"'{text}' not found in output: {context.output}"


@when("I call the main function directly with no args")
def step_call_main_no_args(context):
    """Call main function with no arguments."""
    with patch("sys.argv", ["cleveragents"]):
        context.exit_code = main()


@when('I call the main function directly with "{args}"')
def step_call_main_with_args(context, args):
    """Call main function with specific arguments."""
    argv = ["cleveragents", *args.split()]
    with patch("sys.argv", argv):
        context.exit_code = main()


@then("it should return {code:d}")
def step_check_return_code(context, code):
    """Check the return code."""
    assert context.exit_code == code, f"Expected {code}, got {context.exit_code}"


@when("I run a command that gets interrupted")
def step_run_interrupted_command(context):
    """Simulate a command that gets interrupted."""
    with patch("cleveragents.cli.main.app") as mock_app:
        mock_app.side_effect = KeyboardInterrupt()
        context.exit_code = main(["info"])


@then("it should handle the KeyboardInterrupt gracefully")
def step_check_keyboard_interrupt_handled(context):
    """Check that KeyboardInterrupt was handled."""
    assert context.exit_code == 130, (
        f"Expected 130 for KeyboardInterrupt, got {context.exit_code}"
    )


@when("I run a command that calls sys.exit(1)")
def step_run_sys_exit_command(context):
    """Simulate a command that calls sys.exit."""
    with patch("cleveragents.cli.main.app") as mock_app:
        mock_app.side_effect = SystemExit(1)
        context.exit_code = main(["info"])


@then("it should propagate the exit code correctly")
def step_check_exit_code_propagated(context):
    """Check that exit code was propagated."""
    assert context.exit_code == 1, f"Expected 1, got {context.exit_code}"


# Additional steps to increase coverage


@when("I test convert_exit_code with various inputs")
def step_test_convert_exit_code(context):
    """Test the convert_exit_code function."""
    context.results = []
    # Test with int
    context.results.append(convert_exit_code(0))
    context.results.append(convert_exit_code(1))
    context.results.append(convert_exit_code(-1))
    # Test with None
    context.results.append(convert_exit_code(None))
    # Test with Exit exception
    context.results.append(convert_exit_code(Exit(2)))
    # Test with ClickExit exception
    context.results.append(convert_exit_code(ClickExit(3)))
    # Test with other object
    context.results.append(convert_exit_code("string"))


@then("convert_exit_code should handle all types correctly")
def step_check_convert_exit_code(context):
    """Check convert_exit_code results."""
    expected = [0, 1, -1, 0, 2, 3, 1]  # string returns 1, not 0
    assert context.results == expected, f"Expected {expected}, got {context.results}"


@when("I test the CLI app commands")
def step_test_cli_commands(context):
    """Test CLI app commands directly."""
    import tempfile
    from pathlib import Path

    from typer.testing import CliRunner

    runner = CliRunner()

    context.results = []
    # Test version
    result = runner.invoke(app, ["--version"])
    context.results.append((result.exit_code, "1.0.0" in result.stdout))

    # Test help
    result = runner.invoke(app, ["--help"])
    context.results.append((result.exit_code, "Usage" in result.stdout))

    # Test info
    result = runner.invoke(app, ["info"])
    context.results.append((result.exit_code, "Environment" in result.stdout))

    # Test diagnostics
    result = runner.invoke(app, ["diagnostics"])
    context.results.append((result.exit_code, "Checks" in result.stdout))

    # Test init - use temp directory to avoid conflicts
    import os

    from cleveragents.application.container import reset_container
    from cleveragents.config.settings import Settings

    with tempfile.TemporaryDirectory() as tmpdir:
        orig_cwd = Path.cwd()
        try:
            # Reset container and settings so init uses the temp directory's
            # database path instead of any previously-cached state
            reset_container()
            Settings._instance = None

            os.chdir(tmpdir)
            result = runner.invoke(app, ["init", "testproj"])
            context.results.append(
                (result.exit_code, "initialized successfully" in result.stdout)
            )
        finally:
            os.chdir(orig_cwd)
            # Reset again so subsequent tests don't use temp directory state
            reset_container()
            Settings._instance = None


@then("all CLI commands should work correctly")
def step_check_cli_commands(context):
    """Check CLI command results."""
    for idx, (exit_code, output_check) in enumerate(context.results):
        # All commands should succeed now
        assert exit_code == 0, f"Command {idx} failed with exit code {exit_code}"
        assert output_check, f"Command {idx} output check failed"


@when("I test exception handling in main")
def step_test_exception_handling(context):
    """Test exception handling in main function."""
    context.results = []

    # Test CleverAgentsError
    from cleveragents.core.exceptions import CleverAgentsError

    with patch("cleveragents.cli.main.app") as mock_app:
        mock_app.side_effect = CleverAgentsError("Test error")
        result = main(["info"])
        context.results.append(result)

    # Test generic Exception
    with patch("cleveragents.cli.main.app") as mock_app:
        mock_app.side_effect = Exception("Generic error")
        result = main(["info"])
        context.results.append(result)

    # Test Exit with code
    with patch("cleveragents.cli.main.app") as mock_app:
        mock_app.side_effect = Exit(5)
        result = main(["info"])
        context.results.append(result)


@then("exception handling should return correct codes")
def step_check_exception_handling(context):
    """Check exception handling results."""
    expected = [1, 1, 5]
    assert context.results == expected, f"Expected {expected}, got {context.results}"


@when("I trigger the basic CLI help output")
def step_trigger_basic_help_output(context):
    """Capture the lightweight CLI help output."""
    buffer = StringIO()
    with redirect_stdout(buffer):
        _print_basic_help()
    context.basic_help_output = buffer.getvalue()


@then("the basic help output should list common commands")
def step_check_basic_help_output(context):
    """Verify the lightweight help includes key commands."""
    output = context.basic_help_output
    assert "CleverAgents - AI-powered development assistant" in output
    for keyword in ["project", "context", "plan", "auto-debug", "version"]:
        assert keyword in output, f"{keyword} missing from help output"


@when("I ensure CLI subcommands are registered lazily")
def step_ensure_cli_commands_registered(context):
    """Ensure subcommand registration sets the flag once."""
    import importlib

    cli_main = importlib.import_module("cleveragents.cli.main")
    original_state = cli_main._subcommands_registered
    cli_main._subcommands_registered = False
    try:
        ensure_cli_commands_registered()
        first_state = cli_main._subcommands_registered
        ensure_cli_commands_registered()
        second_state = cli_main._subcommands_registered
    finally:
        cli_main._subcommands_registered = original_state

    context.registration_states = (first_state, second_state)


@then("the registration flag should stay enabled")
def step_check_registration_flag(context):
    """Check registration state is preserved."""
    first_state, second_state = context.registration_states
    assert first_state is True
    assert second_state is True


@when("I invoke the tell shortcut with an actor override")
def step_call_tell_with_overrides(context):
    """Invoke tell shortcut ensuring actor override is forwarded."""
    with patch("cleveragents.cli.commands.plan.tell") as mock_tell:
        tell(
            prompt="Explain coverage improvements",
            name="coverage-plan",
            actor="coverage-actor",
            stream=True,
        )
        context.tell_kwargs = mock_tell.call_args.kwargs


@then("the tell shortcut should forward the actor override")
def step_check_tell_overrides(context):
    """Validate tell forwards actor override."""
    expected = {
        "prompt": "Explain coverage improvements",
        "stream": True,
        "name": "coverage-plan",
        "actor": "coverage-actor",
    }
    assert context.tell_kwargs == expected, (
        f"Expected {expected}, got {context.tell_kwargs}"
    )


@when("I invoke the build shortcut with an actor override")
def step_call_build_with_overrides(context):
    """Invoke build shortcut ensuring actor override is forwarded."""
    with patch("cleveragents.cli.commands.plan.build") as mock_build:
        build(verbose=True, actor="build-actor")
        context.build_kwargs = mock_build.call_args.kwargs


@then("the build shortcut should forward the actor override")
def step_check_build_overrides(context):
    """Validate build forwards actor override."""
    expected = {"verbose": True, "actor": "build-actor"}
    assert context.build_kwargs == expected, (
        f"Expected {expected}, got {context.build_kwargs}"
    )
