/**
 * Tests for .opencode/tools/tunnel.ts — the tunnel custom tool.
 *
 * The tool is a thin spawnSync wrapper around `bash .opencode/scripts/tunnel.sh`
 * (toggle: 1st call starts, 2nd call stops). The toggle/PID-file logic lives
 * in the bash script and is covered by tests/test_tunnel_tool.py (which runs
 * the script directly with a fake cloudflared). These TS tests stub spawnSync
 * to verify the tool wiring: it invokes `bash <script>` with cwd from context,
 * returns stdout on success, and formats the canonical `⚠️ tunnel failed` error
 * on non-zero exit.
 *
 * Runtime note: opencode ships a standalone binary with Bun bundled inside;
 * there is no separate `bun` CLI on the host (CI runner uses node + pytest).
 * The CI runs the equivalent Python tests in tests/test_tunnel_tool.py.
 *
 * Test cases (mirror tests/test_tunnel_tool.py success/error wiring):
 *   - test_starts_with_bash_script — spawnSync called with bash + script path
 *   - test_success_returns_stdout — exit 0 + stdout → returns trimmed stdout
 *   - test_failure_returns_error — exit non-zero → "⚠️ tunnel failed (exit K): ..."
 *   - test_uses_cwd_from_context — spawnSync opts.cwd == context.worktree
 */

import { describe, test, expect, mock } from "bun:test" with { type: "'bun-test'" }
import { spawnSync } from "child_process"
import path from "path"

const TOOL_SRC = path.resolve(import.meta.dir, "..", ".opencode", "tools", "tunnel.ts")

function ctx() {
  return {
    sessionID: "t", messageID: "t", agent: "t",
    directory: ".", worktree: ".",
    abort: new AbortController().signal,
    metadata() {}, async ask() {},
  }
}

describe("tunnel tool", () => {
  test("test_starts_with_bash_script — spawnSync called with bash + script path", async () => {
    let capturedCmd
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (cmd, args) => {
        capturedCmd = cmd
        capturedArgs = args
        return { status: 0, stdout: "started (PID: 12345)\n", stderr: "" }
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({}, ctx())
    expect(capturedCmd).toBe("bash")
    // The single arg is the path to .opencode/scripts/tunnel.sh (resolved
    // relative to the tool's own directory via import.meta.dir/../scripts).
    expect(capturedArgs).toHaveLength(1)
    expect(capturedArgs[0]).toContain("tunnel.sh")
    expect(result).toBe("started (PID: 12345)")
  })

  test("test_success_returns_stdout — exit 0 + stdout → returns trimmed stdout", async () => {
    mock.module("child_process", () => ({
      spawnSync: () => ({ status: 0, stdout: "stopped\n", stderr: "" }),
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({}, ctx())
    expect(result).toBe("stopped")
  })

  test("test_failure_returns_error — exit non-zero → '⚠️ tunnel failed (exit K): ...'", async () => {
    mock.module("child_process", () => ({
      spawnSync: () => ({ status: 1, stdout: "", stderr: "❌ CLOUDFLARE_TUNNEL_TOKEN is not set" }),
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({}, ctx())
    expect(result).toContain("⚠️ tunnel failed")
    expect(result).toContain("exit 1")
    expect(result).toContain("CLOUDFLARE_TUNNEL_TOKEN is not set")
  })

  test("test_uses_cwd_from_context — spawnSync opts.cwd == context.worktree", async () => {
    let capturedOpts
    mock.module("child_process", () => ({
      spawnSync: (_cmd, _args, opts) => {
        capturedOpts = opts
        return { status: 0, stdout: "started (PID: 1)\n", stderr: "" }
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    await mod.default.execute({}, ctx())
    expect(capturedOpts).not.toBeNull()
    expect(capturedOpts).toHaveProperty("cwd")
    expect(capturedOpts.cwd).toBe(".")
  })
})