from __future__ import annotations

from pathlib import Path
import sys
import threading
import time

from bootstrap.runner import (
    CodexEvent,
    CodexInvocation,
    build_invocation,
    build_resume_invocation,
    run_and_stream,
)


def _collect_raw_events(events: list[CodexEvent]) -> list[dict[str, object]]:
    return [event.raw for event in events]


def test_run_and_stream_exposes_input_writer_and_streams_terminal_output(
    tmp_path: Path,
) -> None:
    invocation = CodexInvocation(
        args=[
            sys.executable,
            "-c",
            (
                "import sys; "
                "print('ready', flush=True); "
                "line = sys.stdin.readline(); "
                "print('echo:' + line.strip(), flush=True)"
            ),
        ],
        env={},
        workdir=tmp_path,
        prompt="noop",
    )

    events: list[CodexEvent] = []

    def _run() -> None:
        events.extend(run_and_stream(invocation))

    thread = threading.Thread(target=_run)
    thread.start()
    deadline = time.time() + 5
    while (
        invocation.input_writer is None and thread.is_alive() and time.time() < deadline
    ):
        time.sleep(0.01)

    assert invocation.input_writer is not None
    invocation.input_writer("hello")
    thread.join(timeout=5)
    assert not thread.is_alive()

    raw_events = _collect_raw_events(events)
    terminal_lines = [
        value
        for raw in raw_events
        for value in [raw.get("terminal_output")]
        if isinstance(value, str)
    ]
    assert any(line == "ready" for line in terminal_lines)
    assert any("echo:hello" in line for line in terminal_lines)

    exit_events = [
        raw for raw in raw_events if raw.get("bootstrap_debug") == "codex_exit"
    ]
    assert exit_events
    assert exit_events[-1]["exit_code"] == 0


def test_run_and_stream_parses_json_lines(tmp_path: Path) -> None:
    invocation = CodexInvocation(
        args=[
            sys.executable,
            "-c",
            "import json; print(json.dumps({'message': 'hello-json'}), flush=True)",
        ],
        env={},
        workdir=tmp_path,
        prompt="noop",
    )

    events = list(run_and_stream(invocation))
    raw_events = _collect_raw_events(events)

    assert any(raw.get("message") == "hello-json" for raw in raw_events)
    assert any(raw.get("bootstrap_debug") == "codex_exit" for raw in raw_events)


def test_build_invocation_writes_prompt_file_and_uses_stdin_dash_arg(
    tmp_path: Path,
) -> None:
    invocation = build_invocation(
        codex_executable=Path("/usr/local/bin/codex"),
        prompt="TASK PROMPT",
        workdir=tmp_path,
        env={"FOO": "bar"},
        extra_flags=("--sandbox", "workspace-write"),
    )

    assert invocation.args == [
        "/usr/local/bin/codex",
        "exec",
        "--json",
        "--cd",
        str(tmp_path),
        "--skip-git-repo-check",
        "--sandbox",
        "workspace-write",
        "-",
    ]
    assert invocation.prompt == "TASK PROMPT"
    assert invocation.prompt_file == tmp_path / "flywheel_prompt.txt"
    assert invocation.prompt_file.read_text(encoding="utf-8") == "TASK PROMPT"


def test_run_and_stream_seeds_initial_prompt_via_stdin_when_dash_arg_present(
    tmp_path: Path,
) -> None:
    prompt_file = tmp_path / "flywheel_prompt.txt"
    prompt_file.write_text("unused", encoding="utf-8")
    invocation = CodexInvocation(
        args=[
            sys.executable,
            "-c",
            "import sys; data = sys.stdin.read(); print('prompt=' + data, flush=True)",
            "-",
        ],
        env={},
        workdir=tmp_path,
        prompt="HELLO PROMPT",
        prompt_file=prompt_file,
    )

    events = list(run_and_stream(invocation))
    raw_events = _collect_raw_events(events)

    terminal_lines = [
        value
        for raw in raw_events
        for value in [raw.get("terminal_output")]
        if isinstance(value, str)
    ]
    assert any(line == "prompt=HELLO PROMPT" for line in terminal_lines)
    assert invocation.stdin_handle is None
    assert invocation.input_writer is None
    assert any(raw.get("bootstrap_debug") == "codex_exit" for raw in raw_events)


def test_build_resume_invocation_uses_exec_resume_args(tmp_path: Path) -> None:
    invocation = build_resume_invocation(
        codex_executable=Path("/usr/local/bin/codex"),
        session_id="thread-123",
        prompt="continue",
        workdir=tmp_path,
        env={"FOO": "bar"},
        extra_flags=("--sandbox", "workspace-write"),
    )

    assert invocation.args == [
        "/usr/local/bin/codex",
        "exec",
        "resume",
        "--json",
        "--skip-git-repo-check",
        "--sandbox",
        "workspace-write",
        "thread-123",
        "continue",
    ]
    assert invocation.workdir == tmp_path
