/**
 * Tests for resolveRgBinary() and memory-search keyword degradation.
 *
 * resolveRgBinary() lives in .opencode/tools/_memory-shared.ts and resolves
 * the ripgrep binary via the @vscode/ripgrep npm package first, then
 * (optionally) a system `rg` on $PATH. memory-search.ts must warn to stderr
 * when both are unavailable instead of silently returning no keyword hits.
 *
 * 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).
 * 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:
 *   - test_resolve_npm_package — rgPath exists → returns path
 *   - test_resolve_system_fallback — npm require throws, allowSystemFallback
 *     true, system rg on $PATH → returns "rg"
 *   - test_resolve_null_when_nothing_available — npm throws, no system rg,
 *     allowSystemFallback true → null
 *   - test_resolve_null_no_fallback — npm throws, allowSystemFallback false
 *     → null (no system rg probe)
 *   - test_memory_search_warns_when_rg_null — rgBin===null → stderr warning
 */

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

const SHARED_SRC = path.resolve(
  import.meta.dir,
  "..",
  ".opencode",
  "tools",
  "_memory-shared.ts"
)
const SEARCH_SRC = path.resolve(
  import.meta.dir,
  "..",
  ".opencode",
  "tools",
  "memory-search.ts"
)

describe("resolveRgBinary", () => {
  test("test_resolve_npm_package — @vscode/ripgrep resolves to existing binary", async () => {
    const fakeRgPath = "/tmp/opencode/fake-rg-bin"
    mock.module("node:module", () => ({
      createRequire: () => () => ({ rgPath: fakeRgPath }),
    }))
    mock.module("fs", () => ({
      ...fs,
      existsSync: (p: string) => p === fakeRgPath,
    }))
    const mod = await import(SHARED_SRC + "?t=" + Date.now())
    const result = mod.resolveRgBinary()
    expect(result).toBe(fakeRgPath)
  })

  test("test_resolve_system_fallback — npm require throws, allowSystemFallback true, system rg works → 'rg'", async () => {
    mock.module("node:module", () => ({
      createRequire: () => () => {
        throw new Error("Cannot find module '@vscode/ripgrep'")
      },
    }))
    mock.module("child_process", () => ({
      spawnSync: () => ({ status: 0, stdout: "ripgrep 13.0.0\n", stderr: "" }),
    }))
    const mod = await import(SHARED_SRC + "?t=" + Date.now())
    const result = mod.resolveRgBinary({ allowSystemFallback: true })
    expect(result).toBe("rg")
  })

  test("test_resolve_null_when_nothing_available — npm throws, no system rg, allowSystemFallback true → null", async () => {
    mock.module("node:module", () => ({
      createRequire: () => () => {
        throw new Error("Cannot find module '@vscode/ripgrep'")
      },
    }))
    mock.module("child_process", () => ({
      spawnSync: () => ({ status: 1, stdout: "", stderr: "rg: not found" }),
    }))
    const mod = await import(SHARED_SRC + "?t=" + Date.now())
    const result = mod.resolveRgBinary({ allowSystemFallback: true })
    expect(result).toBeNull()
  })

  test("test_resolve_null_no_fallback — npm throws, allowSystemFallback false → null (no system rg probe)", async () => {
    let spawnCalled = false
    mock.module("node:module", () => ({
      createRequire: () => () => {
        throw new Error("Cannot find module '@vscode/ripgrep'")
      },
    }))
    mock.module("child_process", () => ({
      spawnSync: () => {
        spawnCalled = true
        return { status: 0, stdout: "ripgrep 13.0.0\n", stderr: "" }
      },
    }))
    const mod = await import(SHARED_SRC + "?t=" + Date.now())
    const result = mod.resolveRgBinary()
    expect(result).toBeNull()
    expect(spawnCalled).toBe(false)
  })
})

describe("memory-search keyword degradation", () => {
  test("test_memory_search_warns_when_rg_null — rgBin===null writes stderr warning", async () => {
    const stderrWrites: string[] = []
    mock.module("node:module", () => ({
      createRequire: () => () => {
        throw new Error("Cannot find module '@vscode/ripgrep'")
      },
    }))
    mock.module("child_process", () => ({
      spawnSync: () => ({ status: 1, stdout: "", stderr: "rg: not found" }),
    }))
    mock.module("fs", () => ({
      ...fs,
      existsSync: () => true,
      readFileSync: () =>
        "---\ntitle: test\nsummary: s\n---\n\n# test\n\nbody text\n",
      readdirSync: () => [],
      statSync: () => ({ size: 0, mtime: new Date() }),
    }))
    const origStderrWrite = process.stderr.write
    process.stderr.write = ((chunk: string | Uint8Array) => {
      stderrWrites.push(typeof chunk === "string" ? chunk : chunk.toString())
      return true
    }) as typeof process.stderr.write
    try {
      const mod = await import(SEARCH_SRC + "?t=" + Date.now())
      await mod.default.execute(
        { query: "nonexistent" },
        {
          sessionID: "t",
          messageID: "t",
          agent: "t",
          directory: ".",
          worktree: ".",
          abort: new AbortController().signal,
          metadata() {},
          async ask() {},
        }
      )
    } finally {
      process.stderr.write = origStderrWrite
    }
    const combined = stderrWrites.join("")
    expect(combined).toContain("ripgrep not resolvable")
    expect(combined).toContain("keyword search disabled")
  })
})