/**
 * Tests for .opencode/tools/post-review.ts — the post-review custom tool.
 *
 * Mirror of tests/test_commit_tool.ts / test_create_pr_tool.ts:
 * the tool is a spawnSync wrapper around `gh pr comment` with verdict enum
 * validation and deterministic comment heading generation.
 *
 * 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_post_review_tool.py
 * via the JS loader tests/_ts_loader.mjs (exec_stub_json mode for multi-arg
 * tools). This file documents the intended TS-side test cases and is
 * runnable under `bun test` once a bun runtime is available on the host.
 *
 * Test cases (mirror tests/test_post_review_tool.py):
 *   - test_valid_approve — valid APPROVE verdict → "Review posted"
 *   - test_valid_request_changes — valid REQUEST_CHANGES verdict → "Review posted"
 *   - test_valid_needs_discussion — valid NEEDS_DISCUSSION verdict → "Review posted"
 *   - test_invalid_verdict — invalid verdict "APROVE" (typo) → error
 *   - test_comment_has_heading — comment body contains "## Code Review Summary"
 *   - test_comment_has_verdict — comment body contains "### Verdict: <verdict>"
 *   - test_spawnsync_args — spawnSync called with correct gh args
 */

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", "post-review.ts")

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

const COMMENT_OK = { status: 0, stdout: "https://github.com/slaid098/opencode-config/issues/45#issuecomment-1\n", stderr: "" }

describe("post-review tool", () => {
  test("test_valid_approve — APPROVE verdict succeeds", async () => {
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (_cmd, args) => {
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({
      pr_number: 45,
      verdict: "APPROVE",
      body: "Good changes.\n\n### Positives\n- Clean code",
    }, ctx())
    expect(result).toBe("Review posted on PR #45: verdict=APPROVE")
    // comment body is the arg after --body
    const bodyIdx = capturedArgs.indexOf("--body") + 1
    expect(capturedArgs[bodyIdx]).toContain("## Code Review Summary")
    expect(capturedArgs[bodyIdx]).toContain("### Verdict: APPROVE")
  })

  test("test_valid_request_changes — REQUEST_CHANGES verdict succeeds", async () => {
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (_cmd, args) => {
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({
      pr_number: 45,
      verdict: "REQUEST_CHANGES",
      body: "### Critical\n- file.py:10 bug",
    }, ctx())
    expect(result).toBe("Review posted on PR #45: verdict=REQUEST_CHANGES")
    const bodyIdx = capturedArgs.indexOf("--body") + 1
    expect(capturedArgs[bodyIdx]).toContain("### Verdict: REQUEST_CHANGES")
  })

  test("test_valid_needs_discussion — NEEDS_DISCUSSION verdict succeeds", async () => {
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (_cmd, args) => {
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({
      pr_number: 45,
      verdict: "NEEDS_DISCUSSION",
      body: "### Questions\n- Why this approach?",
    }, ctx())
    expect(result).toBe("Review posted on PR #45: verdict=NEEDS_DISCUSSION")
    const bodyIdx = capturedArgs.indexOf("--body") + 1
    expect(capturedArgs[bodyIdx]).toContain("### Verdict: NEEDS_DISCUSSION")
  })

  test("test_invalid_verdict — typo 'APROVE' → error", async () => {
    mock.module("child_process", () => ({
      spawnSync: () => COMMENT_OK,
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    // Invalid verdict passed directly — at runtime zod would reject this,
    // but the loader shim does no validation. The tool builds the comment
    // regardless. The enum validation happens at the opencode layer (zod),
    // not inside execute(). This test documents that the tool itself does
    // not validate verdicts (delegated to zod schema).
    const result = await mod.default.execute({
      pr_number: 45,
      verdict: "APROVE",
      body: "typo verdict",
    }, ctx())
    // Tool still runs (no inline validation) — comment built with bad verdict.
    // The real guard is the zod enum in tool.schema.enum(VERDICTS).
    expect(result).toBe("Review posted on PR #45: verdict=APROVE")
  })

  test("test_comment_has_heading — comment body contains '## Code Review Summary'", async () => {
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (_cmd, args) => {
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    await mod.default.execute({
      pr_number: 45,
      verdict: "APPROVE",
      body: "Review body",
    }, ctx())
    const bodyIdx = capturedArgs.indexOf("--body") + 1
    const comment = capturedArgs[bodyIdx]
    expect(comment.startsWith("## Code Review Summary\n")).toBe(true)
  })

  test("test_comment_has_verdict — comment body contains '### Verdict: APPROVE'", async () => {
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (_cmd, args) => {
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    await mod.default.execute({
      pr_number: 45,
      verdict: "APPROVE",
      body: "Review body",
    }, ctx())
    const bodyIdx = capturedArgs.indexOf("--body") + 1
    const comment = capturedArgs[bodyIdx]
    expect(comment).toContain("### Verdict: APPROVE")
  })

  test("test_spawnsync_args — spawnSync called with correct gh args (no --repo)", async () => {
    let capturedCmd
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (cmd, args) => {
        capturedCmd = cmd
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    await mod.default.execute({
      pr_number: 45,
      verdict: "APPROVE",
      body: "Review body",
    }, ctx())
    expect(capturedCmd).toBe("gh")
    expect(capturedArgs[0]).toBe("pr")
    expect(capturedArgs[1]).toBe("comment")
    expect(capturedArgs[2]).toBe("45")
    expect(capturedArgs).toContain("--body")
    // --repo MUST NOT be present — gh auto-detects from cwd (PR#60).
    expect(capturedArgs).not.toContain("--repo")
    // args end with --body <comment> (no trailing --repo slaid098/...).
    const lastIdx = capturedArgs.length - 1
    expect(capturedArgs[lastIdx - 1]).toBe("--body")
    expect(capturedArgs[lastIdx].startsWith("## Code Review Summary\n")).toBe(true)
  })

  test("test_repo_explicit — repo='foo/bar' prepends --repo to gh argv", async () => {
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (_cmd, args) => {
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({
      pr_number: 45,
      verdict: "APPROVE",
      body: "Review body",
      repo: "foo/bar",
    }, ctx())
    expect(result).toBe("Review posted on PR #45: verdict=APPROVE")
    expect(capturedArgs[0]).toBe("--repo")
    expect(capturedArgs[1]).toBe("foo/bar")
    expect(capturedArgs[2]).toBe("pr")
  })

  test("test_repo_omitted — no --repo flag (auto-detect from cwd)", async () => {
    let capturedArgs
    mock.module("child_process", () => ({
      spawnSync: (_cmd, args) => {
        capturedArgs = args
        return COMMENT_OK
      },
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    await mod.default.execute({
      pr_number: 45,
      verdict: "APPROVE",
      body: "Review body",
    }, ctx())
    // Backward-compat: --repo MUST NOT be present when repo arg omitted.
    expect(capturedArgs).not.toContain("--repo")
    expect(capturedArgs[0]).toBe("pr")
  })

  test("test_repo_invalid — invalid repo + gh failure → tool error", async () => {
    mock.module("child_process", () => ({
      spawnSync: () => ({ status: 1, stdout: "", stderr: 'expected the "owner/repo" format' }),
    }))
    const mod = await import(TOOL_SRC + "?t=" + Date.now())
    const result = await mod.default.execute({
      pr_number: 45,
      verdict: "APPROVE",
      body: "Review body",
      repo: "not-a-valid-repo",
    }, ctx())
    expect(result).toContain("post-review failed")
    expect(result).toContain("exit 1")
  })
})