from __future__ import annotations

import io
import json
import os
import time
import tempfile
from pathlib import Path
from types import SimpleNamespace

import pytest
from typer.testing import CliRunner

from worai import cli as root_cli
from worai.agent.skills import build_skill_text
from worai.agent import mcp_server as mcp_server_mod
from worai.agent.mcp_server import McpTransport, SessionDefaults, WoraiMcpServer
from worai.commands import agent as agent_cmd


def test_root_cli_includes_agent_command() -> None:
    result = CliRunner().invoke(root_cli.app, ["--help"])
    assert result.exit_code == 0
    assert "agent" in result.output
    assert "Launch external agent CLIs with worai MCP" in result.output
    assert "skill guidance" in result.output
    assert "pass agent flags after" in result.output


def test_agent_dry_run_codex_wires_assets(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    monkeypatch.setattr(
        agent_cmd.subprocess,
        "run",
        lambda *_args, **_kwargs: SimpleNamespace(returncode=0, stdout="[]", stderr=""),
    )

    result = CliRunner().invoke(
        root_cli.app,
        ["agent", "--agent-cli", "codex", "--dry-run", "--profile", "acme", "--config", "worai.toml"],
    )
    assert result.exit_code == 0
    assert "skills:" in result.output
    assert "mcp_config:" in result.output
    assert "launch:" in result.output

    mcp_file = tmp_path / "tmp" / "mcp.json"
    payload = json.loads(mcp_file.read_text(encoding="utf-8"))
    assert payload["mcpServers"]["worai"]["type"] == "http"
    assert payload["mcpServers"]["worai"]["url"].startswith("http://127.0.0.1:")
    assert payload["mcpServers"]["wordlift"]["url"].startswith("https://")


def test_agent_run_invokes_subprocess_and_exits(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    seen: dict[str, object] = {}

    def _stub_run(command, env=None, check=False, stdout=None, stderr=None):
        seen["command"] = command
        seen["env"] = env
        return SimpleNamespace(returncode=0)

    monkeypatch.setattr(agent_cmd.subprocess, "run", _stub_run)
    result = CliRunner().invoke(root_cli.app, ["agent", "--agent-cli", "claude"])
    assert result.exit_code == 0
    assert seen["command"][0] == "claude"
    assert "--mcp-config" in seen["command"]


def test_agent_dry_run_claude_wires_mcp_config(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")

    result = CliRunner().invoke(
        root_cli.app,
        ["agent", "--agent-cli", "claude", "--dry-run", "--profile", "acme", "--config", "worai.toml"],
    )
    assert result.exit_code == 0
    assert "mcp_config:" in result.output
    assert "--mcp-config" in result.output

    mcp_file = tmp_path / "tmp" / "mcp.json"
    payload = json.loads(mcp_file.read_text(encoding="utf-8"))
    assert payload["mcpServers"]["worai"]["type"] == "http"
    assert payload["mcpServers"]["worai"]["url"].startswith("http://127.0.0.1:")
    assert payload["mcpServers"]["wordlift"]["type"] in {"http", "sse"}
    assert payload["mcpServers"]["wordlift"]["url"].startswith("https://")


def test_agent_dry_run_forwards_passthrough_args_after_double_dash(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    monkeypatch.setattr(
        agent_cmd.subprocess,
        "run",
        lambda *_args, **_kwargs: SimpleNamespace(returncode=0, stdout="[]", stderr=""),
    )

    result = CliRunner().invoke(
        root_cli.app,
        ["agent", "--agent-cli", "codex", "--dry-run", "--", "--yolo", "--search"],
    )

    assert result.exit_code == 0
    assert "--yolo" in result.output
    assert "--search" in result.output
    assert "launch:" in result.output


def test_agent_codex_uses_real_home_and_skips_configured_mcp_servers(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    seen: dict[str, object] = {}

    def _stub_run(command, env=None, check=False, stdout=None, stderr=None, capture_output=False, text=False):
        del check, stdout, stderr, capture_output, text
        if command[:4] == ["codex", "mcp", "list", "--json"]:
            return SimpleNamespace(
                returncode=0,
                stdout=json.dumps([{"name": "worai"}, {"name": "wordlift"}]),
                stderr="",
            )
        seen["command"] = command
        seen["env"] = env
        return SimpleNamespace(returncode=0)

    monkeypatch.setattr(agent_cmd.subprocess, "run", _stub_run)
    result = CliRunner().invoke(root_cli.app, ["agent", "--agent-cli", "codex"])
    assert result.exit_code == 0
    assert seen["command"][0] == "codex"
    assert "-c" not in seen["command"]
    assert seen["env"]["HOME"] == os.environ["HOME"]


def test_agent_gemini_launches_directly(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    """Gemini is launched directly — no gemini mcp add/remove bootstrap calls."""
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    monkeypatch.setattr(agent_cmd, "_setup_gemini_home", lambda temp_dir: temp_dir / "gemini-home")
    calls: list[list[str]] = []

    def _stub_run(command, **_kwargs):
        calls.append(command)
        return SimpleNamespace(returncode=0, stdout="", stderr="")

    monkeypatch.setattr(agent_cmd.subprocess, "run", _stub_run)
    result = CliRunner().invoke(root_cli.app, ["agent", "--agent-cli", "gemini"])
    assert result.exit_code == 0
    # Only one subprocess call: the gemini launch itself
    assert len(calls) == 1
    assert calls[0] == ["gemini"]


def test_agent_gemini_settings_written_with_mcp_servers_stdio(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    """settings.json is written with mcpServers (stdio) and mcp.allowed — no bootstrap needed."""
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    gemini_home = tmp_path / "tmp" / "gemini-home"
    monkeypatch.setattr(agent_cmd, "_setup_gemini_home", lambda _td: gemini_home)
    monkeypatch.setattr(
        agent_cmd.subprocess, "run", lambda *_a, **_kw: SimpleNamespace(returncode=0)
    )

    result = CliRunner().invoke(
        root_cli.app,
        ["agent", "--agent-cli", "gemini", "--mcp-transport", "stdio", "--profile", "acme", "--keep-temp"],
    )
    assert result.exit_code == 0

    settings = json.loads((gemini_home / ".gemini" / "settings.json").read_text())
    assert settings["mcp"]["allowed"] == ["worai", "wordlift"]
    assert "worai" in settings["mcpServers"]
    assert "wordlift" in settings["mcpServers"]
    worai_entry = settings["mcpServers"]["worai"]
    assert "command" in worai_entry
    assert "worai.agent.mcp_server" in " ".join(worai_entry.get("args", []))
    assert "--profile" in worai_entry.get("args", [])
    assert "acme" in worai_entry.get("args", [])
    assert settings["contextFileName"] == "WORDLIFT.md"


def test_agent_gemini_settings_written_with_mcp_servers_http(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    """settings.json uses HTTP URL entry when mcp-transport=http."""
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    gemini_home = tmp_path / "tmp" / "gemini-home"
    monkeypatch.setattr(agent_cmd, "_setup_gemini_home", lambda _td: gemini_home)

    class _RunningHttp:
        url = "http://127.0.0.1:45123/mcp"

        def stop(self) -> None:
            pass

    monkeypatch.setattr(agent_cmd, "start_http_server", lambda **_kw: _RunningHttp())
    monkeypatch.setattr(
        agent_cmd.subprocess, "run", lambda *_a, **_kw: SimpleNamespace(returncode=0)
    )

    result = CliRunner().invoke(
        root_cli.app, ["agent", "--agent-cli", "gemini", "--mcp-transport", "http", "--keep-temp"]
    )
    assert result.exit_code == 0

    settings = json.loads((gemini_home / ".gemini" / "settings.json").read_text())
    # Gemini forces stdio even when --mcp-transport http is passed
    worai_entry = settings["mcpServers"]["worai"]
    assert "command" in worai_entry


def test_agent_gemini_env_sets_gemini_cli_home(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    """GEMINI_CLI_HOME is set to the isolated home dir in the subprocess env."""
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: "/usr/bin/fake")
    (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr(agent_cmd, "_create_session_dir", lambda: tmp_path / "tmp")
    gemini_home = tmp_path / "tmp" / "gemini-home"
    monkeypatch.setattr(agent_cmd, "_setup_gemini_home", lambda _td: gemini_home)
    captured_env: dict[str, str] = {}

    def _stub_run(command, env=None, **_kw):
        if command == ["gemini"]:
            captured_env.update(env or {})
        return SimpleNamespace(returncode=0)

    monkeypatch.setattr(agent_cmd.subprocess, "run", _stub_run)
    result = CliRunner().invoke(root_cli.app, ["agent", "--agent-cli", "gemini"])
    assert result.exit_code == 0
    assert captured_env.get("GEMINI_CLI_HOME") == str(gemini_home)


def test_build_skill_text_loads_repo_skill_templates() -> None:
    with tempfile.TemporaryDirectory(prefix="worai-skills-") as raw_dir:
        root = Path(raw_dir)
        skills_dir = root / "skills"
        (skills_dir / "worai-base").mkdir(parents=True)
        (skills_dir / "zzz-extra").mkdir(parents=True)
        (skills_dir / "worai-base" / "SKILL.md").write_text("base", encoding="utf-8")
        (skills_dir / "zzz-extra" / "SKILL.md").write_text("extra", encoding="utf-8")

        text = build_skill_text(project_root=root)
        assert text == "base\n\n---\n\nextra"


def test_agent_missing_binary_returns_usage_error(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(agent_cmd, "shutil_which", lambda _name: None)
    result = CliRunner().invoke(root_cli.app, ["agent", "--agent-cli", "codex", "--dry-run"])
    assert result.exit_code == 1
    assert result.exception is not None
    assert "Executable not found" in str(result.exception)


def test_mcp_transport_reads_line_delimited_json(monkeypatch: pytest.MonkeyPatch) -> None:
    stdin = SimpleNamespace(buffer=io.BytesIO(b'{"jsonrpc":"2.0","id":1,"method":"ping"}\n'))
    monkeypatch.setattr(mcp_server_mod.sys, "stdin", stdin)
    McpTransport._line_delimited_mode = None
    message = McpTransport.read_message()
    assert message is not None
    assert message["method"] == "ping"
    assert McpTransport._line_delimited_mode is True


def test_mcp_transport_writes_line_delimited_json_after_line_mode(monkeypatch: pytest.MonkeyPatch) -> None:
    stdin = SimpleNamespace(buffer=io.BytesIO(b'{"jsonrpc":"2.0","id":1,"method":"ping"}\n'))
    stdout_buffer = io.BytesIO()
    stdout = SimpleNamespace(buffer=stdout_buffer)
    monkeypatch.setattr(mcp_server_mod.sys, "stdin", stdin)
    monkeypatch.setattr(mcp_server_mod.sys, "stdout", stdout)
    McpTransport._line_delimited_mode = None

    _ = McpTransport.read_message()
    McpTransport.write_message({"jsonrpc": "2.0", "id": 1, "result": {}})
    payload = stdout_buffer.getvalue()
    assert payload.endswith(b"\n")
    assert b"Content-Length" not in payload


def test_mcp_server_help_and_exec_tools(monkeypatch: pytest.MonkeyPatch) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))

    seen = {"argv": None}

    def _stub_invoke_sync(*, argv, config_path, profile, max_output_chars):
        del config_path, profile, max_output_chars
        seen["argv"] = argv
        return {"ok": True, "command": ["worai", *argv], "output": "ok", "exit_code": 0}

    monkeypatch.setattr(server._executor, "invoke_sync", _stub_invoke_sync)

    help_payload = server._tool_worai_help({"command_path": ["graph", "validate"]})
    assert help_payload["ok"] is True
    assert seen["argv"][-1] == "--help"

    exec_payload = server._tool_worai_cli_exec({"argv": ["graph", "validate", "a.ttl"]})
    assert exec_payload["ok"] is True
    assert exec_payload["command"][-3:] == ["graph", "validate", "a.ttl"]


def test_mcp_server_exposes_typed_tools() -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    tools = {tool["name"] for tool in server._tools()}
    assert "worai_version" in tools
    assert "worai_graph_audit" in tools
    assert "worai_graph_reset" in tools
    assert "worai_graph_validate" in tools
    assert "worai_graph_export" in tools
    assert "worai_graph_sync_run_start" in tools
    assert "worai_structured_data_validate_page" in tools
    assert "worai_structured_data_inventory_start" in tools
    assert "worai_web_pages_classify_types" in tools
    assert "worai_job_status" in tools
    assert "worai_job_output" in tools
    assert "worai_job_events" in tools
    assert "worai_job_cancel" in tools
    assert "worai_cli_exec" in tools


def test_mcp_server_typed_graph_audit_builds_expected_argv(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    seen: dict = {}

    def _stub_invoke_sync(*, argv, config_path, profile, max_output_chars):
        del config_path, profile, max_output_chars
        seen["argv"] = argv
        return {"ok": True, "command": ["worai", *argv], "output": "ok", "exit_code": 0}

    monkeypatch.setattr(server._executor, "invoke_sync", _stub_invoke_sync)

    payload = server._call_tool(
        "worai_graph_audit",
        {
            "file": "./graph.ttl",
            "depth": 2,
            "list_isolated_iris": True,
            "show_url_violations": True,
            "rich_snippets_granularity": "entities",
            "workers": 4,
            "builtin_shapes": ["google-article.ttl"],
            "shapes": ["./custom.ttl"],
            "issue_level": "error",
            "format": "json",
        },
    )

    assert payload["ok"] is True
    argv = seen["argv"]
    assert argv[:3] == ["graph", "audit", "./graph.ttl"]
    assert "--depth" in argv and argv[argv.index("--depth") + 1] == "2"
    assert "--list-isolated-iris" in argv
    assert "--show-url-violations" in argv
    assert "--rich-snippets-granularity" in argv
    assert argv[argv.index("--rich-snippets-granularity") + 1] == "entities"
    assert "--workers" in argv and argv[argv.index("--workers") + 1] == "4"
    assert "--builtin-shape" in argv and argv[argv.index("--builtin-shape") + 1] == "google-article.ttl"
    assert "--shape" in argv and argv[argv.index("--shape") + 1] == "./custom.ttl"
    assert "--issue-level" in argv and argv[argv.index("--issue-level") + 1] == "error"
    assert argv[-2:] == ["--format", "json"]


def test_mcp_server_typed_graph_audit_minimal_argv(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    seen: dict = {}

    def _stub_invoke_sync(*, argv, config_path, profile, max_output_chars):
        del config_path, profile, max_output_chars
        seen["argv"] = argv
        return {"ok": True, "command": ["worai", *argv], "output": "ok", "exit_code": 0}

    monkeypatch.setattr(server._executor, "invoke_sync", _stub_invoke_sync)

    payload = server._call_tool("worai_graph_audit", {"file": "graph.ttl"})

    assert payload["ok"] is True
    assert seen["argv"] == ["graph", "audit", "graph.ttl"]


def test_mcp_server_typed_graph_reset_builds_expected_argv(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    seen: dict = {}

    def _stub_invoke_sync(*, argv, config_path, profile, max_output_chars):
        del config_path, profile, max_output_chars
        seen["argv"] = argv
        return {"ok": True, "command": ["worai", *argv], "output": "ok", "exit_code": 0}

    monkeypatch.setattr(server._executor, "invoke_sync", _stub_invoke_sync)

    payload = server._call_tool(
        "worai_graph_reset",
        {"keep_country": True, "keep_language": True, "keep_url": True},
    )

    assert payload["ok"] is True
    argv = seen["argv"]
    assert argv[:3] == ["graph", "reset", "--yes"]
    assert "--keep-country" in argv
    assert "--keep-language" in argv
    assert "--keep-url" in argv


def test_mcp_server_typed_graph_reset_minimal_argv(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    seen: dict = {}

    def _stub_invoke_sync(*, argv, config_path, profile, max_output_chars):
        del config_path, profile, max_output_chars
        seen["argv"] = argv
        return {"ok": True, "command": ["worai", *argv], "output": "ok", "exit_code": 0}

    monkeypatch.setattr(server._executor, "invoke_sync", _stub_invoke_sync)

    payload = server._call_tool("worai_graph_reset", {})

    assert payload["ok"] is True
    assert seen["argv"] == ["graph", "reset", "--yes"]


def test_mcp_server_typed_graph_validate_builds_expected_argv(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    seen = {"argv": None}

    def _stub_invoke_sync(*, argv, config_path, profile, max_output_chars):
        del config_path, profile, max_output_chars
        seen["argv"] = argv
        return {"ok": True, "command": ["worai", *argv], "output": "ok", "exit_code": 0}

    monkeypatch.setattr(server._executor, "invoke_sync", _stub_invoke_sync)
    payload = server._call_tool(
        "worai_graph_validate",
        {"inputs": ["./a.ttl"], "builtin_shapes": ["google-required"], "format": "json"},
    )
    assert payload["ok"] is True
    assert seen["argv"][:4] == ["graph", "validate", "./a.ttl", "--builtin-shape"]
    assert seen["argv"][-2:] == ["--format", "json"]


def test_skill_describes_mcp_tools() -> None:
    text = build_skill_text()
    assert "worai_graph_validate" in text
    assert "worai_graph_export" in text
    assert "worai_graph_sync_run_start" in text
    assert "worai_structured_data_validate_page" in text
    assert "worai_structured_data_inventory_start" in text
    assert "worai_web_pages_classify_types" in text
    assert "worai_job_status" in text
    assert "worai_job_output" in text
    assert "worai_job_events" in text
    assert "worai_job_cancel" in text
    assert "worai_cli_exec" in text


def test_mcp_server_typed_structured_data_inventory_start_builds_expected_argv(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    seen = {"command": None}

    def _stub_build_subprocess_command(*, argv, config_path, profile):
        seen["command"] = argv
        return (["python", "-m", "worai.cli", *argv], "/tmp/worai.toml", profile or "default")

    def _stub_start(*, command, effective_config_path, effective_profile):
        return {
            "job_id": "job-1",
            "state": "queued",
            "command": command,
            "effective_config_path": effective_config_path,
            "effective_profile": effective_profile,
        }

    monkeypatch.setattr(server._executor, "build_subprocess_command", _stub_build_subprocess_command)
    monkeypatch.setattr(server._jobs, "start", _stub_start)
    payload = server._call_tool(
        "worai_structured_data_inventory_start",
        {
            "source": "https://example.com/sitemap.xml",
            "output": "./inventory.csv",
            "ingest_source": "sitemap",
            "ingest_loader": "playwright",
            "ingest_passthrough_when_html": False,
            "concurrency": "4",
        },
    )
    assert payload["state"] == "queued"
    assert seen["command"][:3] == ["structured-data", "inventory", "https://example.com/sitemap.xml"]
    assert "--output" in seen["command"]
    assert "--ingest-source" in seen["command"]
    assert "--ingest-loader" in seen["command"]
    assert "--no-ingest-passthrough-when-html" in seen["command"]


def test_mcp_server_blocks_recursive_agent_mcp() -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    with pytest.raises(ValueError, match="blocked"):
        server._tool_worai_cli_exec({"argv": ["agent", "mcp", "serve"]})


def test_job_events_and_progress_from_inventory_event_line() -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))
    payload = server._jobs.start(
        command=["/bin/sh", "-lc", "printf 'WORAI_MCP_EVENT {\"event\":\"inventory.progress.updated\",\"meta\":{\"completed\":2,\"total\":5}}\\n'"],
        effective_config_path="/tmp/worai.toml",
        effective_profile="default",
    )
    job_id = payload["job_id"]

    for _ in range(100):
        status = server._jobs.status(job_id)
        if status["state"] in {"done", "failed"}:
            break
        time.sleep(0.01)

    status = server._jobs.status(job_id)
    assert status["state"] == "done"
    assert status["progress"]["completed"] == 2
    assert status["progress"]["total"] == 5
    assert status["progress"]["percent"] == 40.0
    assert status["event_cursor"] >= 2

    events = server._jobs.events(job_id, cursor=0, limit=10)
    assert events["events"]
    assert any(event["event"] == "inventory.progress.updated" for event in events["events"])


def test_mcp_jsonrpc_inventory_async_flow_exposes_events_and_output(monkeypatch: pytest.MonkeyPatch) -> None:
    server = WoraiMcpServer(SessionDefaults(config_path=None, profile=None))

    def _stub_build_subprocess_command(*, argv, config_path, profile):
        del argv, config_path, profile
        cmd = [
            "/bin/sh",
            "-lc",
            "printf 'WORAI_MCP_EVENT {\"event\":\"inventory.progress.started\",\"meta\":{\"total\":2}}\\n'; "
            "printf 'WORAI_MCP_EVENT {\"event\":\"inventory.progress.updated\",\"meta\":{\"completed\":1,\"total\":2}}\\n'; "
            "printf '{\"total\":2,\"output\":\"/tmp/out.csv\"}\\n'",
        ]
        return cmd, "/tmp/worai.toml", "default"

    monkeypatch.setattr(server._executor, "build_subprocess_command", _stub_build_subprocess_command)

    start_resp = server.handle_message(
        {
            "jsonrpc": "2.0",
            "id": 1,
            "method": "tools/call",
            "params": {
                "name": "worai_structured_data_inventory_start",
                "arguments": {"source": "https://example.com/sitemap_index.xml", "ingest_loader": "playwright"},
            },
        }
    )
    assert start_resp is not None
    job_id = start_resp["result"]["structuredContent"]["job_id"]

    for _ in range(100):
        status_resp = server.handle_message(
            {
                "jsonrpc": "2.0",
                "id": 2,
                "method": "tools/call",
                "params": {"name": "worai_job_status", "arguments": {"job_id": job_id}},
            }
        )
        assert status_resp is not None
        state = status_resp["result"]["structuredContent"]["state"]
        if state in {"done", "failed"}:
            break
        time.sleep(0.01)

    status_payload = status_resp["result"]["structuredContent"]
    assert status_payload["state"] == "done"
    assert status_payload["progress"]["completed"] == 1
    assert status_payload["progress"]["total"] == 2
    assert status_payload["event_cursor"] >= 2

    events_resp = server.handle_message(
        {
            "jsonrpc": "2.0",
            "id": 3,
            "method": "tools/call",
            "params": {"name": "worai_job_events", "arguments": {"job_id": job_id, "cursor": 0, "limit": 10}},
        }
    )
    assert events_resp is not None
    events_payload = events_resp["result"]["structuredContent"]
    names = [event["event"] for event in events_payload["events"]]
    assert "inventory.progress.started" in names
    assert "inventory.progress.updated" in names

    output_resp = server.handle_message(
        {
            "jsonrpc": "2.0",
            "id": 4,
            "method": "tools/call",
            "params": {"name": "worai_job_output", "arguments": {"job_id": job_id}},
        }
    )
    assert output_resp is not None
    output_payload = output_resp["result"]["structuredContent"]
    assert "\"total\":2" in output_payload["text"]
