import { spawnSync } from "child_process"
import fs from "fs"
import os from "os"
import path from "path"
import { tool } from "@opencode-ai/plugin"
import { resolveMemoryDir, resolveRgBinary, walkMd } from "./_memory-shared"

function mark(ok: boolean): string {
  return ok ? "✅" : "❌"
}

interface RipgrepCheck {
  lines: string[]
  rgWorks: boolean
}

function detectArchMismatch(rgBin: string): string | null {
  const plat = process.platform
  const lower = rgBin.toLowerCase()
  if (plat === "linux" && (lower.includes("darwin") || lower.includes("win32") || lower.includes("win64"))) {
    return "arch mismatch: binary platform != runtime platform (linux)"
  }
  if (plat === "darwin" && (lower.includes("linux") || lower.includes("win32") || lower.includes("win64"))) {
    return "arch mismatch: binary platform != runtime platform (darwin)"
  }
  if (plat === "win32" && (lower.includes("linux") || lower.includes("darwin"))) {
    return "arch mismatch: binary platform != runtime platform (win32)"
  }
  const arch = os.arch()
  if (arch === "arm64" && lower.includes("x64")) return "arch mismatch: binary is x64 but runtime is arm64"
  if ((arch === "x64" || arch === "x86_64") && lower.includes("arm64")) return "arch mismatch: binary is arm64 but runtime is x64"
  return null
}

function checkRipgrep(rgBin: string | null): RipgrepCheck {
  const lines: string[] = []
  if (rgBin) {
    const r = spawnSync(rgBin, ["--version"], { encoding: "utf-8" })
    const ver = (r.stdout || "").split("\n")[0].trim()
    const works = r.status === 0
    lines.push(
      `- ${mark(works)} ripgrep (keyword search): ${rgBin}${ver ? ` — ${ver}` : ""}`
    )
    if (!works) {
      const archMismatch = detectArchMismatch(rgBin)
      if (archMismatch) {
        lines.push(`  - ${archMismatch}`)
      }
      const errMsg = (r.stderr || "").trim()
      if (errMsg) lines.push(`  - binary execution failed: ${errMsg.split("\n")[0]}`)
    }
    return { lines, rgWorks: works }
  }
  // rgBin === null: npm package missing — try system rg as a hint.
  const sys = spawnSync("rg", ["--version"], { encoding: "utf-8" })
  if (sys.status === 0) {
    lines.push(`- ${mark(true)} ripgrep (keyword search): system rg on $PATH`)
    return { lines, rgWorks: true }
  }
  lines.push(
    "- ❌ ripgrep (keyword search): NOT resolvable — install @vscode/ripgrep or system rg"
  )
  lines.push(
    "  - npm package missing — run `npm ci` in .opencode/ (or `apt-get install ripgrep` for system fallback)"
  )
  return { lines, rgWorks: false }
}

function checkPythonMemory(worktree: string): { lines: string[]; ok: boolean } {
  const pyR = spawnSync("python3", ["-c", "import src.memory; print('ok')"], {
    encoding: "utf-8",
    cwd: worktree,
  })
  const lines = [
    `- ${mark(pyR.status === 0)} Python src.memory importable (semantic search): ${
      pyR.status === 0 ? "yes" : `no — ${(pyR.stderr || pyR.stdout || "").trim().split("\n").pop() || ""}`
    }`,
  ]
  return { lines, ok: pyR.status === 0 }
}

function checkEnvVars(): string[] {
  return [
    `- ${mark(!!process.env.OPENAI_BASE_URL)} OPENAI_BASE_URL set (embeddings endpoint): ${
      process.env.OPENAI_BASE_URL ? "✓" : "not set — semantic search disabled, keyword still works"
    }`,
    `- ${mark(!!process.env.OPENAI_API_KEY)} OPENAI_API_KEY set (embeddings auth): ${
      process.env.OPENAI_API_KEY ? "✓" : "not set"
    }`,
    `- ${mark(!!process.env.OPENCODE_MEMORY_REMOTE)} OPENCODE_MEMORY_REMOTE set (auto-push to git): ${
      process.env.OPENCODE_MEMORY_REMOTE ? "✓" : "not set — auto-push disabled"
    }`,
  ]
}

function checkIndex(memoryDir: string): string[] {
  const lines: string[] = []
  lines.push(
    `- ${mark(fs.existsSync(memoryDir))} OPENCODE_MEMORY_DIR exists: ${memoryDir}${
      fs.existsSync(memoryDir) ? "" : "— run memory-save to auto-setup"
    }`
  )
  if (!fs.existsSync(memoryDir)) return lines

  const gitDir = path.join(memoryDir, ".git")
  lines.push(
    `- ${mark(fs.existsSync(gitDir))} memory dir is a git repo: ${
      fs.existsSync(gitDir) ? "yes" : "no — will be init on first memory-save"
    }`
  )
  const indexJson = path.join(memoryDir, ".rag", "index.json")
  lines.push(
    `- ${mark(fs.existsSync(indexJson))} RAG index exists: ${
      fs.existsSync(indexJson)
        ? path.relative(memoryDir, indexJson)
        : "not built — first memory-save with OPENAI_BASE_URL will trigger reindex"
    }`
  )
  if (fs.existsSync(indexJson)) {
    try {
      const stat = fs.statSync(indexJson)
      const sizeMb = (stat.size / 1024 / 1024).toFixed(1)
      lines.push(`  (size: ${sizeMb} MB, modified: ${stat.mtime.toISOString().slice(0, 10)})`)
    } catch (e) {
      process.stderr.write(`[memory-doctor] stat index.json failed: ${(e as Error).message}\n`)
    }
  }
  try {
    const mdCount = walkMd(memoryDir).length
    lines.push(`- ℹ️ memory files (.md): ${mdCount}`)
  } catch (e) {
    process.stderr.write(`[memory-doctor] walkMd failed: ${(e as Error).message}\n`)
  }
  return lines
}

function formatDoctorReport(
  rgWorks: boolean,
  pyOk: boolean,
  memoryDir: string
): string {
  const indexJson = path.join(memoryDir, ".rag", "index.json")
  const allGreen =
    rgWorks && pyOk && fs.existsSync(memoryDir) && fs.existsSync(indexJson)
  if (allGreen) return "All green — both keyword and semantic search available."
  if (rgWorks) return "Keyword search works. Semantic search unavailable — see ❌ items above."
  return "ripgrep missing or broken — keyword search will return no results. Install @vscode/ripgrep or system rg."
}

export default tool({
  description:
    "Read-only diagnostic for the memory subsystem. Reports: ripgrep (keyword) availability, Python src.memory importability, " +
    "env vars (OPENAI_BASE_URL, OPENAI_API_KEY, OPENCODE_MEMORY_REMOTE), memory dir existence, RAG index existence. " +
    "Does NOT modify anything. Run when memory-search/memory-save misbehave or to verify setup.",
  args: {},
  async execute(_args, context) {
    const memoryDir = resolveMemoryDir()
    const lines: string[] = ["## Memory Doctor\n"]

    const rgBin = resolveRgBinary({ allowSystemFallback: true })
    const rg = checkRipgrep(rgBin)
    lines.push(...rg.lines)

    const py = checkPythonMemory(context.worktree)
    lines.push(...py.lines)

    lines.push(...checkEnvVars())
    lines.push(...checkIndex(memoryDir))

    lines.push("")
    lines.push(formatDoctorReport(rg.rgWorks, py.ok, memoryDir))
    return lines.join("\n")
  },
})