#!/usr/bin/env python3
# ruff: noqa: RUF001, RUF003
"""Mock Claude Code CLI for integration testing.

Simulates real Claude Code permission prompts: trust-folder setup,
bash-tool permission dialogs, allow-menu variants, and subagent spawning.

Usage:
    python mock_claude.py [--scenario SCENARIO]

Scenarios:
    trust_folder    - initial workspace trust prompt
    bash_permission - bash command permission dialog (numbered menu)
    allow_menu      - allow-once/session/always menu (unnumbered)
    allow_numbered  - numbered allow menu (newer Claude Code format)
    subagent_chain  - parent spawns two subagents, each needs permission
    overnight_loop  - 3 iterations each needing different permissions
    all             - run all scenarios sequentially (default)

Exit codes:
    0  all prompts received correct responses
    1  wrong response or timeout
"""

import sys
import time

TIMEOUT = 10  # seconds to wait for a response


def _read_line(timeout=TIMEOUT):
    """Read one line from stdin with timeout.  Returns stripped text or None."""
    import select

    ready, _, _ = select.select([sys.stdin], [], [], timeout)
    if not ready:
        return None
    line = sys.stdin.readline()
    if not line:
        return None
    return line.rstrip("\r\n")


def _expect(accepted, timeout=TIMEOUT):
    """Wait for one of the accepted responses.  Returns True on success."""
    resp = _read_line(timeout)
    if resp is None:
        print(f"\n[mock] TIMEOUT waiting for {accepted!r}", file=sys.stderr)
        return False
    if resp not in accepted:
        print(f"\n[mock] WRONG response {resp!r}, expected one of {accepted!r}", file=sys.stderr)
        return False
    return True


# ------------------------------------------------------------------
# individual prompt scenarios
# ------------------------------------------------------------------


def scenario_trust_folder():
    """Initial workspace trust prompt — uses real Claude Code 2.x TUI encoding.

    Claude Code 2.x renders menus using ESC[1C (cursor-forward-1) as word
    separators instead of literal spaces.  auto-yes expands these to spaces
    before pattern matching (fixed in _ansi.py).
    """
    # Simulate real Claude Code 2.x PTY output format:
    # cursor-forward (\x1b[1C) replaces spaces between words
    sys.stdout.write(
        "\r\n\r\r\n"
        "\x1b[1C\x1b[38;5;153m\u276f"   # ❯ (U+276F, cursor-forward for spacing)
        "\x1b[1C\x1b[38;5;246m1."
        "\x1b[1C\x1b[38;5;153mYes,"
        "\x1b[1CI"
        "\x1b[1Ctrust"
        "\x1b[1Cthis"
        "\x1b[1Cfolder\x1b[39m"
        "\r\r\n"
        "\x1b[3C\x1b[38;5;246m2.\x1b[1C\x1b[39mNo,\x1b[1Cexit"
        "\r\r\n\r\r\n"
        "\x1b[1C\x1b[38;5;246mEnter\x1b[1Cto\x1b[1Cconfirm"
        "\x1b[1C\u00b7\x1b[1CEsc\x1b[1Cto\x1b[1Ccancel\x1b[39m"
        "\r\r\n"
    )
    sys.stdout.flush()
    return _expect(["", "y", "Y", "1"])


def scenario_bash_permission():
    """Bash command permission dialog with numbered yes/no menu."""
    sys.stdout.write(
        "\n"
        " \x1b[1mBash command\x1b[0m\n"
        "\n"
        "   ls -la /workspace\n"
        "   List workspace files\n"
        "\n"
        " Permission rule  Bash requires confirmation for this command.\n"
        "\n"
        " Do you want to proceed?\n"
        " \x1b[32m❯ 1. Yes\x1b[0m\n"
        "   2. No\n"
        "\n"
    )
    sys.stdout.flush()
    return _expect(["", "y", "Y", "1"])


def scenario_allow_menu():
    """Unnumbered allow-once/session/always menu (tool permission popup)."""
    sys.stdout.write(
        "\n"
        " \x1b[1mClaude wants to run a command:\x1b[0m\n"
        "\n"
        "   bash: git log --oneline -20\n"
        "\n"
        " \x1b[32m❯ Allow once\x1b[0m\n"
        "   Allow for this session\n"
        "   Always allow\n"
        "   No\n"
        "\n"
    )
    sys.stdout.flush()
    return _expect(["", "y", "Y", "1"])


def scenario_allow_numbered():
    """Numbered allow menu (newer Claude Code format with 4 options)."""
    sys.stdout.write(
        "\n"
        " \x1b[1mClaude wants to run a bash command:\x1b[0m\n"
        "\n"
        "   git commit -am 'auto-generated fix'\n"
        "\n"
        " \x1b[32m❯ 1. Allow once\x1b[0m\n"
        "   2. Allow for this session\n"
        "   3. Always allow for this project\n"
        "   4. No\n"
        "\n"
    )
    sys.stdout.flush()
    return _expect(["", "y", "Y", "1"])


def scenario_subagent_chain():
    """Parent agent spawns two subagents; each needs bash permission."""
    # Parent agent output
    sys.stdout.write(
        "\n"
        "[Agent] Starting task: refactor and test\n"
        "[Agent] Spawning subagent-1: code-refactor\n"
        "[Agent] Spawning subagent-2: test-runner\n"
        "\n"
    )
    sys.stdout.flush()
    time.sleep(0.1)

    # Subagent-1 needs permission
    sys.stdout.write(
        "[Subagent-1] Bash command\n"
        "\n"
        "   python -m py_compile src/main.py\n"
        "\n"
        " Do you want to proceed?\n"
        " \x1b[32m❯ 1. Yes\x1b[0m\n"
        "   2. No\n"
        "\n"
    )
    sys.stdout.flush()
    if not _expect(["", "y", "Y", "1"]):
        return False

    time.sleep(0.1)

    # Subagent-2 needs permission
    sys.stdout.write(
        "[Subagent-2] Bash command\n"
        "\n"
        "   python -m pytest tests/ -q 2>&1\n"
        "\n"
        " Do you want to proceed?\n"
        " \x1b[32m❯ 1. Yes\x1b[0m\n"
        "   2. No\n"
        "\n"
    )
    sys.stdout.flush()
    if not _expect(["", "y", "Y", "1"]):
        return False

    sys.stdout.write("[Agent] Both subagents completed.\n")
    sys.stdout.flush()
    return True


def scenario_overnight_loop():
    """3-iteration overnight loop — each iteration needs different permission."""
    for i in range(1, 4):
        sys.stdout.write(
            f"\n=== Overnight loop iteration {i}/3 ===\n"
            "Analyzing codebase...\n"
            f"Generating patch {i}...\n"
            "\n"
        )
        sys.stdout.flush()
        time.sleep(0.05)

        if i == 1:
            # First iter: trust folder
            ok = scenario_trust_folder()
        elif i == 2:
            # Second iter: bash permission
            ok = scenario_bash_permission()
        else:
            # Third iter: allow menu
            ok = scenario_allow_menu()

        if not ok:
            return False

        sys.stdout.write(f"[Loop] Iteration {i} complete.\n")
        sys.stdout.flush()
        time.sleep(0.05)

    sys.stdout.write("\n[Loop] All iterations done. Overnight run successful.\n")
    sys.stdout.flush()
    return True


# ------------------------------------------------------------------
# main
# ------------------------------------------------------------------

def scenario_long_overnight_loop():
    """10-iteration overnight loop — stress test for sustained auto-yes.

    Cycles through all 4 prompt types to maximise coverage.
    """
    prompt_fns = [
        scenario_trust_folder,
        scenario_bash_permission,
        scenario_allow_menu,
        scenario_allow_numbered,
    ]
    for i in range(1, 11):
        sys.stdout.write(
            f"\n=== Long overnight loop iteration {i}/10 ===\n"
            "Analyzing codebase...\n"
            f"Generating patch {i}...\n"
            "\n"
        )
        sys.stdout.flush()
        time.sleep(0.02)

        ok = prompt_fns[(i - 1) % len(prompt_fns)]()
        if not ok:
            return False

        sys.stdout.write(f"[Loop] Iteration {i} complete.\n")
        sys.stdout.flush()
        time.sleep(0.02)

    sys.stdout.write("\n[Loop] All 10 iterations done. Long overnight run successful.\n")
    sys.stdout.flush()
    return True


SCENARIOS = {
    "trust_folder": scenario_trust_folder,
    "bash_permission": scenario_bash_permission,
    "allow_menu": scenario_allow_menu,
    "allow_numbered": scenario_allow_numbered,
    "subagent_chain": scenario_subagent_chain,
    "overnight_loop": scenario_overnight_loop,
    "long_overnight_loop": scenario_long_overnight_loop,
}


def main():
    import argparse

    p = argparse.ArgumentParser(description="Mock Claude Code CLI for integration tests")
    p.add_argument(
        "--scenario",
        default="all",
        choices=[*SCENARIOS, "all"],
        help="which scenario to run (default: all)",
    )
    args = p.parse_args()

    if args.scenario == "all":
        names = list(SCENARIOS.keys())
    else:
        names = [args.scenario]

    ok = True
    for name in names:
        sys.stdout.write(f"\n--- Running scenario: {name} ---\n")
        sys.stdout.flush()
        result = SCENARIOS[name]()
        if not result:
            print(f"[mock] FAILED: scenario '{name}'", file=sys.stderr)
            ok = False

    sys.exit(0 if ok else 1)


if __name__ == "__main__":
    main()
