"""Step definitions for tui_shell_exec_coverage.feature.

These steps target specific uncovered lines in tui/input/shell_exec.py:
- Lines 48-49: empty command returns ShellResult with exit_code=2
- Lines 52-56: CLEVERAGENTS_DISABLE_SHELL_MODE env var disables shell mode
- Line 61: confirm_dangerous callback is invoked for dangerous commands
- Lines 77-82: subprocess.TimeoutExpired is caught and returns exit_code=124
"""

import os
import subprocess
from unittest.mock import patch

from behave import given, then, when

from cleveragents.tui.input.shell_exec import (
    ShellResult,
    looks_dangerous,
    run_shell_command,
)


# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the shell_exec module is imported")
def step_shell_exec_imported(context):
    """Verify the module is importable and key symbols exist."""
    assert run_shell_command is not None
    assert ShellResult is not None
    assert looks_dangerous is not None


# ---------------------------------------------------------------------------
# Empty / whitespace command  (lines 48-49)
# ---------------------------------------------------------------------------
@when("I run a shell command with an empty string")
def step_run_empty_command(context):
    """Run run_shell_command with an empty string."""
    context.shell_result = run_shell_command("")


@when('I run a shell command with only whitespace "{whitespace}"')
def step_run_whitespace_command(context, whitespace):
    """Run run_shell_command with whitespace-only input."""
    context.shell_result = run_shell_command(whitespace)


# ---------------------------------------------------------------------------
# Shell mode disabled via env var  (lines 52-56)
# ---------------------------------------------------------------------------
@given('the environment variable CLEVERAGENTS_DISABLE_SHELL_MODE is set to "{value}"')
def step_set_disable_env_var(context, value):
    """Set the CLEVERAGENTS_DISABLE_SHELL_MODE environment variable."""
    context.original_env_value = os.environ.get("CLEVERAGENTS_DISABLE_SHELL_MODE")
    os.environ["CLEVERAGENTS_DISABLE_SHELL_MODE"] = value

    def restore_env():
        if context.original_env_value is None:
            os.environ.pop("CLEVERAGENTS_DISABLE_SHELL_MODE", None)
        else:
            os.environ["CLEVERAGENTS_DISABLE_SHELL_MODE"] = context.original_env_value

    context.add_cleanup(restore_env)


@when('I run a shell command "{command}"')
def step_run_shell_command(context, command):
    """Run run_shell_command with the given command string."""
    context.shell_result = run_shell_command(command)


# ---------------------------------------------------------------------------
# Dangerous command with confirm callback  (line 61)
# ---------------------------------------------------------------------------
@given("a confirm_dangerous callback that returns True")
def step_confirm_callback_true(context):
    """Create a confirm_dangerous callback that approves dangerous commands."""
    context.confirm_called = False

    def confirm_true(cmd):
        context.confirm_called = True
        return True

    context.confirm_callback = confirm_true


@given("a confirm_dangerous callback that returns False")
def step_confirm_callback_false(context):
    """Create a confirm_dangerous callback that rejects dangerous commands."""
    context.confirm_called = False

    def confirm_false(cmd):
        context.confirm_called = True
        return False

    context.confirm_callback = confirm_false


@when('I run a dangerous command "{command}" with the callback')
def step_run_dangerous_with_callback(context, command):
    """Run a dangerous command with the confirm_dangerous callback.

    We patch subprocess.run to avoid actually executing dangerous commands
    when the callback returns True.
    """
    fake_proc = subprocess.CompletedProcess(
        args=command, returncode=0, stdout="mocked output", stderr=""
    )
    with patch(
        "cleveragents.tui.input.shell_exec.subprocess.run", return_value=fake_proc
    ):
        context.shell_result = run_shell_command(
            command, confirm_dangerous=context.confirm_callback
        )


@then("the dangerous command should have been confirmed")
def step_verify_confirm_called(context):
    """Verify that the confirm_dangerous callback was invoked."""
    assert context.confirm_called is True, "confirm_dangerous callback was not called"


@then("the shell result should reflect the executed command")
def step_verify_executed_result(context):
    """Verify the result reflects a successfully executed (mocked) command."""
    assert context.shell_result.exit_code == 0
    assert context.shell_result.stdout == "mocked output"


# ---------------------------------------------------------------------------
# Timeout  (lines 77-82)
# ---------------------------------------------------------------------------
@given("subprocess run is mocked to raise TimeoutExpired")
def step_mock_timeout(context):
    """Prepare a mock that raises subprocess.TimeoutExpired."""
    context.timeout_mock_active = True


@when('I run a shell command "{command}" with a {seconds:d} second timeout')
def step_run_with_timeout(context, command, seconds):
    """Run a command with a short timeout, mocking TimeoutExpired."""
    with patch(
        "cleveragents.tui.input.shell_exec.subprocess.run",
        side_effect=subprocess.TimeoutExpired(cmd=command, timeout=seconds),
    ):
        context.shell_result = run_shell_command(command, timeout_seconds=seconds)


# ---------------------------------------------------------------------------
# Common assertions
# ---------------------------------------------------------------------------
@then("the shell result exit code should be {code:d}")
def step_verify_exit_code(context, code):
    """Verify the exit code of the shell result."""
    assert context.shell_result.exit_code == code, (
        f"Expected exit_code={code}, got {context.shell_result.exit_code}"
    )


@then('the shell result stderr should be "{expected}"')
def step_verify_stderr(context, expected):
    """Verify the stderr of the shell result matches exactly."""
    assert context.shell_result.stderr == expected, (
        f"Expected stderr={expected!r}, got {context.shell_result.stderr!r}"
    )


@then("the shell result stdout should be empty")
def step_verify_stdout_empty(context):
    """Verify the stdout of the shell result is empty."""
    assert context.shell_result.stdout == "", (
        f"Expected stdout='', got {context.shell_result.stdout!r}"
    )


@then('the shell result stderr should contain "{fragment}"')
def step_verify_stderr_contains(context, fragment):
    """Verify the stderr of the shell result contains the given fragment."""
    assert fragment in context.shell_result.stderr, (
        f"Expected stderr to contain {fragment!r}, got {context.shell_result.stderr!r}"
    )
