#!/usr/bin/env python3
"""Fake ``qwen --output-format stream-json --approval-mode plan -p ...``.

# recorded-from: qwen 0.23.3

Reads a JSON events spec from ``$NVSH_FAKE_EVENTS`` (a list of
``{"kind": ..., "text": ..., "error": ...}`` dicts -- see
``tests/_fake_adapters.py``) and emits envelope lines shaped like a real
``qwen --output-format stream-json --approval-mode plan -p "<prompt>"``
run (verified locally against qwen 0.23.3; home paths and the session id
from that run are redacted/replaced with placeholders here), so
``nvsh.agent.qwen.QwenAgent`` can be exercised end to end without a real
``qwen`` binary on PATH.

The real CLI's ``system``/``init`` line carries a lot more (``tools``,
``mcp_servers``, ``cwd``, ...) than this fake reproduces -- only the
``type``/``subtype`` shape ``QwenAgent._parse_line`` actually reads is kept,
plus enough surrounding fields (``uuid``, ``session_id``) to look like a
real line to anything eyeballing a fixture. Thinking/tool_use/tool_result
content parts are exercised directly by ``tests/test_agent_qwen.py``, not
through this generic events file -- ``tests/_fake_adapters.py`` only
serializes ``kind``/``text``/``error`` (no ``tool``/``args``), so this fake
sticks to the four kinds the shared conformance suite scripts: ``status``,
``text_delta``, ``error`` and ``done``.

Two more knobs, for the reliable-agent-stop tests:

* ``NVSH_FAKE_IGNORE_CANCEL=1`` -- once the scripted events are done, the
  process does not exit: it blocks reading stdin instead, silently
  dropping anything it reads, including a host's stream-json interrupt
  request. This is what a harness that has stopped honouring its own
  cancel protocol looks like from the client's side. ``QwenAgent`` (print
  mode) has no duplex channel at all -- it spawns this fake with its stdin
  closed (``DEVNULL``) -- so a closed/already-EOF stdin falls back to a
  plain sleep instead of returning immediately: real print-mode qwen has no
  cancel protocol either (task t8), so "ignoring cancel" for it just means
  the process keeps running until a signal ends it.
* ``NVSH_FAKE_GRANDCHILD=1`` -- start a ``sleep 600`` child at launch and
  write ``{"harness": <this pid>, "grandchild": <sleep's pid>}`` to
  ``$NVSH_FAKE_PID_FILE``.
"""

from __future__ import annotations

import json
import os
import subprocess  # nosec B404 - fixed argv below, test fixture only
import sys
import time
from pathlib import Path

_SESSION_ID = "00000000-0000-4000-8000-000000000000"


def spawn_grandchild_if_requested() -> None:
    """``NVSH_FAKE_GRANDCHILD=1``: start a sleeping child and record both pids."""
    if os.environ.get("NVSH_FAKE_GRANDCHILD") != "1":
        return
    pid_file = os.environ.get("NVSH_FAKE_PID_FILE")
    child = subprocess.Popen(["sleep", "600"])  # nosec B603 B607 - fixed argv
    if pid_file:
        Path(pid_file).write_text(
            json.dumps({"harness": os.getpid(), "grandchild": child.pid}), encoding="utf-8"
        )


def hang_if_ignoring_cancel() -> None:
    """``NVSH_FAKE_IGNORE_CANCEL=1``: never let the turn end on its own."""
    if os.environ.get("NVSH_FAKE_IGNORE_CANCEL") != "1":
        return
    line = sys.stdin.readline()
    if not line:
        # stdin is closed/DEVNULL, exactly how QwenAgent's print-mode spawn
        # runs it -- there is no pipe to drop an interrupt on, so hang the
        # same way a stuck real qwen invocation would: only a signal ends it.
        time.sleep(600)
        return
    while sys.stdin.readline():
        pass


def main() -> int:
    spawn_grandchild_if_requested()
    events_path = os.environ.get("NVSH_FAKE_EVENTS")
    events = json.loads(open(events_path, encoding="utf-8").read()) if events_path else []

    exit_code = 0
    for event in events:
        kind = event.get("kind")
        if kind == "status":
            line = {
                "type": "system",
                "subtype": event.get("text", "") or "init",
                "uuid": _SESSION_ID,
                "session_id": _SESSION_ID,
                "qwen_code_version": "0.23.3",
            }
        elif kind == "text_delta":
            line = {
                "type": "assistant",
                "uuid": _SESSION_ID,
                "session_id": _SESSION_ID,
                "message": {
                    "role": "assistant",
                    "content": [{"type": "text", "text": event.get("text", "")}],
                },
            }
        elif kind == "error":
            print(
                json.dumps(
                    {
                        "type": "result",
                        "subtype": "error",
                        "is_error": True,
                        "session_id": _SESSION_ID,
                        "result": event.get("error", ""),
                    }
                )
            )
            sys.stdout.flush()
            exit_code = 1
            break
        elif kind == "done":
            print(
                json.dumps(
                    {
                        "type": "result",
                        "subtype": "success",
                        "is_error": False,
                        "session_id": _SESSION_ID,
                    }
                )
            )
            sys.stdout.flush()
            exit_code = 0
            break
        else:
            continue
        print(json.dumps(line))
        sys.stdout.flush()
    hang_if_ignoring_cancel()
    return exit_code


if __name__ == "__main__":
    sys.exit(main())
