"""Behave step implementations for the REPL feature."""

from __future__ import annotations

import shutil
import tempfile
from pathlib import Path
from unittest.mock import patch

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

from cleveragents.cli.commands.repl import (
    _get_prompt_context,
    run_repl,
)
from features.mocks.fake_repl_input import FakeReplInput

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _run_repl_with_input(
    lines: list[str],
    *,
    no_history: bool = False,
    history_path: Path | None = None,
) -> tuple[int, str]:
    """Run the REPL with simulated input and capture console output."""
    from io import StringIO

    from rich.console import Console

    buf = StringIO()
    console = Console(file=buf, no_color=True, width=200)

    kw: dict[str, object] = {"no_history": no_history}
    if history_path is not None:
        kw["history_path"] = history_path

    fake = FakeReplInput(lines, interrupt_token="__KEYBOARD_INTERRUPT__")
    with (
        patch("cleveragents.cli.commands.repl._console", console),
        patch("builtins.input", fake),
    ):
        code = run_repl(**kw)  # type: ignore[arg-type]

    return code, buf.getvalue()


# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------


@given("the REPL is started")
def step_repl_started(context: Context) -> None:
    context.repl_lines = []
    context.repl_output = ""
    context.repl_exit_code = 0
    context.repl_no_history = True  # default to no history in tests
    context.repl_history_path = None


@given("the REPL is started with --no-history")
def step_repl_started_no_history(context: Context) -> None:
    context.repl_lines = []
    context.repl_output = ""
    context.repl_exit_code = 0
    context.repl_no_history = True
    context.repl_history_path = None


@given("the REPL is started with a custom history path")
def step_repl_started_custom_history(context: Context) -> None:
    context.repl_lines = []
    context.repl_output = ""
    context.repl_exit_code = 0
    context.repl_no_history = False
    tmpdir = Path(tempfile.mkdtemp())
    context.repl_history_path = tmpdir / "test_history"
    context.add_cleanup(lambda: shutil.rmtree(str(tmpdir), ignore_errors=True))


# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------


@when('I send the input "{text}"')
def step_send_input(context: Context, text: str) -> None:
    context.repl_lines.append(text)
    # Execute REPL with accumulated lines
    code, output = _run_repl_with_input(
        context.repl_lines,
        no_history=context.repl_no_history,
        history_path=context.repl_history_path,
    )
    context.repl_exit_code = code
    context.repl_output = output


@when("I send a multi-line version command")
def step_send_multiline_version(context: Context) -> None:
    # Simulate typing ``version \`` followed by an empty continuation line
    context.repl_lines.append("version \\")
    context.repl_lines.append("")
    code, output = _run_repl_with_input(
        context.repl_lines,
        no_history=context.repl_no_history,
        history_path=context.repl_history_path,
    )
    context.repl_exit_code = code
    context.repl_output = output


@when("I send an empty input line")
def step_send_empty_input(context: Context) -> None:
    context.repl_lines.append("")
    code, output = _run_repl_with_input(
        context.repl_lines,
        no_history=context.repl_no_history,
        history_path=context.repl_history_path,
    )
    context.repl_exit_code = code
    context.repl_output = output


@when("the REPL exits")
def step_repl_exits(context: Context) -> None:
    # Re-run with the accumulated lines (EOFError ends the REPL)
    code, output = _run_repl_with_input(
        context.repl_lines,
        no_history=context.repl_no_history,
        history_path=context.repl_history_path,
    )
    context.repl_exit_code = code
    context.repl_output = output


@when("I query the REPL prompt context")
def step_query_prompt(context: Context) -> None:
    context.repl_prompt = _get_prompt_context()


@when("I simulate a KeyboardInterrupt during input")
def step_simulate_ctrl_c(context: Context) -> None:
    # Send a keyboard interrupt marker, then :exit
    context.repl_lines = ["__KEYBOARD_INTERRUPT__", ":exit"]
    code, output = _run_repl_with_input(
        context.repl_lines,
        no_history=True,
    )
    context.repl_exit_code = code
    context.repl_output = output


@when("I simulate an EOFError during input")
def step_simulate_ctrl_d(context: Context) -> None:
    # Empty lines list → immediate EOFError → clean exit
    context.repl_lines = []
    code, output = _run_repl_with_input(
        context.repl_lines,
        no_history=True,
    )
    context.repl_exit_code = code
    context.repl_output = output


# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------


@then('the REPL output should contain "{text}"')
def step_output_contains(context: Context, text: str) -> None:
    assert text in context.repl_output, (
        f"Expected '{text}' in output but got:\n{context.repl_output}"
    )


@then("the REPL exit code should be {code:d}")
def step_exit_code(context: Context, code: int) -> None:
    assert context.repl_exit_code == code, (
        f"Expected exit code {code} but got {context.repl_exit_code}"
    )


@then("the REPL should exit cleanly")
def step_exit_cleanly(context: Context) -> None:
    # A clean exit means exit code 0
    if not hasattr(context, "repl_exit_code") or context.repl_exit_code is None:
        # Run with empty input to trigger EOFError → clean exit
        code, output = _run_repl_with_input(
            context.repl_lines,
            no_history=True,
        )
        context.repl_exit_code = code
        context.repl_output = output
    assert context.repl_exit_code == 0, (
        f"Expected clean exit (code 0) but got {context.repl_exit_code}"
    )


@then("the REPL should continue running")
def step_repl_continues(context: Context) -> None:
    # After Ctrl+C the REPL should still exit cleanly (via :exit or EOFError)
    assert context.repl_exit_code == 0, (
        f"REPL did not continue after interrupt (exit code {context.repl_exit_code})"
    )


@then("the REPL should continue without error")
def step_repl_continues_no_error(context: Context) -> None:
    assert context.repl_exit_code == 0


@then("no history file should be written")
def step_no_history_file(context: Context) -> None:
    if context.repl_history_path is not None:
        assert not context.repl_history_path.exists(), (
            f"History file should not exist but found: {context.repl_history_path}"
        )


@then("the history file should exist")
def step_history_exists(context: Context) -> None:
    assert context.repl_history_path is not None
    assert context.repl_history_path.exists(), (
        f"History file should exist at {context.repl_history_path}"
    )


@then('the prompt should contain "{text}"')
def step_prompt_contains(context: Context, text: str) -> None:
    assert text in context.repl_prompt, (
        f"Expected '{text}' in prompt '{context.repl_prompt}'"
    )
