import { spawnSync } from "child_process"

type GhResult = { status: number | null; stdout: string; stderr: string }

/**
 * Build the `--repo <owner/repo>` argv fragment for `gh`.
 *
 * If `repo` is provided, returns `["--repo", repo]` (explicit target).
 * If omitted, returns `[]` so `gh` auto-detects the repo from its `cwd`
 * (set to `context.worktree` by `runGh`). Auto-detect is the default and
 * backward-compatible behaviour for all 4 GitHub tools (create-issue,
 * create-pr, post-review, merge-pr) — see ADR-027.
 */
export function parseRepo(repo?: string): string[] {
  if (repo) return ["--repo", repo]
  return []
}

/**
 * Resolve `owner/repo` for the Forgejo API path.
 *
 * `repo` is the explicit owner/name (the same string gh would receive via
 * `--repo`). When `repo` is omitted, infer it from the `origin` git remote
 * of the worktree at `opts.cwd` — this mirrors how `gh` auto-detects the
 * repo from cwd in GitHub mode. Returns `owner/name` or null if the remote
 * can't be parsed (caller surfaces an error).
 */
function resolveForgejoRepo(repo: string | undefined, opts?: { cwd?: string }): string | null {
  if (repo) return repo
  const cwd = opts?.cwd
  if (!cwd) return null
  const r = spawnSync("git", ["-C", cwd, "config", "--get", "remote.origin.url"], {
    encoding: "utf-8",
  })
  if (r.status !== 0) return null
  const url = r.stdout.trim()
  const m = url.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/)
  return m ? `${m[1]}/${m[2]}` : null
}

/**
 * Forgejo REST API helper. Returns a spawnSync-shaped result so the caller's
 * `formatResult` / status-check code works unchanged. `okStatus` is the HTTP
 * status treated as success (200 for GET/POST-create, 204 for merge). Non-2xx
 * is reported as a non-zero `status` with the response body in `stderr`.
 */
async function callForgejo(
  method: string,
  path: string,
  body: unknown,
  opts: { okStatus?: number; cwd?: string },
): Promise<GhResult> {
  const base = process.env.FORGEJO_URL
  const token = process.env.FORGEJO_TOKEN
  const okStatus = opts.okStatus ?? 200
  const init: RequestInit = {
    method,
    headers: {
      Authorization: `token ${token}`,
      Accept: "application/json",
      ...(body !== undefined ? { "Content-Type": "application/json" } : {}),
    },
  }
  if (body !== undefined) init.body = JSON.stringify(body)
  try {
    const res = await fetch(`${base}/api/v1${path}`, init)
    const text = await res.text()
    if (res.status === okStatus || (okStatus === 200 && res.status >= 200 && res.status < 300)) {
      return { status: 0, stdout: text, stderr: "" }
    }
    return { status: 1, stdout: "", stderr: `Forgejo API ${method} ${path} → HTTP ${res.status}: ${text}` }
  } catch (e) {
    return { status: 1, stdout: "", stderr: `Forgejo API ${method} ${path} failed: ${String(e)}` }
  }
}

/**
 * Run `gh` with the given subcommand args, optionally targeting an explicit
 * repo. When `repo` is omitted, `gh` auto-detects the repo from `opts.cwd`
 * (callers pass `context.worktree`). Returns the raw spawnSync result so the
 * caller can inspect `status`/`stdout`/`stderr` directly, or pass it to
 * `formatResult` for the standard error string.
 *
 * Dispatch (ADR-forgejo): when `FORGEJO_URL` is set, route to the Forgejo REST
 * API via `callForgejo` (translating the gh argv to the equivalent API call)
 * instead of spawning `gh`. GitHub users (no `FORGEJO_URL`) see byte-identical
 * behaviour — the `gh` path is untouched. The Forgejo result is shaped like a
 * spawnSync result (`{status, stdout, stderr}`) so callers don't branch.
 */
export async function runGh(args: string[], repo?: string, opts?: { cwd?: string }): Promise<GhResult> {
  if (process.env.FORGEJO_URL) {
    return callForgejoGh(args, repo, opts)
  }
  const fullArgs = [...parseRepo(repo), ...args]
  return spawnSync("gh", fullArgs, { encoding: "utf-8", cwd: opts?.cwd })
}

/**
 * Translate the supported gh argv shapes to Forgejo API calls. Only the
 * commands used by the 4 GitHub tools are dispatched (pr merge, pr create,
 * issue create, pr comment); any other argv falls back to a non-zero
 * "unsupported in Forgejo mode" error so the dispatch is explicit.
 */
async function callForgejoGh(
  args: string[],
  repo: string | undefined,
  opts?: { cwd?: string },
): Promise<GhResult> {
  const full = repo ?? resolveForgejoRepo(repo, opts)
  if (!full) {
    return {
      status: 1,
      stdout: "",
      stderr: "Forgejo mode requires owner/repo — none provided and origin remote not parseable",
    }
  }
  if (args[0] === "pr" && args[1] === "merge") {
    const n = args[2]
    return callForgejo(
      "POST",
      `/repos/${full}/pulls/${n}/merge`,
      { Do: "squash", delete_branch_after_merge: true },
      { okStatus: 200, cwd: opts?.cwd },
    )
  }
  if (args[0] === "pr" && args[1] === "create") {
    const titleIdx = args.indexOf("--title")
    const bodyIdx = args.indexOf("--body")
    const title = titleIdx >= 0 ? args[titleIdx + 1] : ""
    const body = bodyIdx >= 0 ? args[bodyIdx + 1] : ""
    const headIdx = args.indexOf("--head")
    const baseIdx = args.indexOf("--base")
    const head = headIdx >= 0 ? args[headIdx + 1] : undefined
    const base = baseIdx >= 0 ? args[baseIdx + 1] : undefined
    const r = await callForgejo(
      "POST",
      `/repos/${full}/pulls`,
      { title, body, ...(head ? { head } : {}), ...(base ? { base } : {}) },
      { cwd: opts?.cwd },
    )
    if (r.status !== 0) return r
    const pr = JSON.parse(r.stdout)
    return { status: 0, stdout: pr.html_url + "\n", stderr: "" }
  }
  if (args[0] === "pr" && args[1] === "comment") {
    const n = args[2]
    const bodyIdx = args.indexOf("--body")
    const body = bodyIdx >= 0 ? args[bodyIdx + 1] : ""
    return callForgejo(
      "POST",
      `/repos/${full}/issues/${n}/comments`,
      { body },
      { okStatus: 201, cwd: opts?.cwd },
    )
  }
  if (args[0] === "issue" && args[1] === "create") {
    const titleIdx = args.indexOf("--title")
    const bodyIdx = args.indexOf("--body")
    const labelIdx = args.indexOf("--label")
    const title = titleIdx >= 0 ? args[titleIdx + 1] : ""
    const body = bodyIdx >= 0 ? args[bodyIdx + 1] : ""
    const labels = labelIdx >= 0 ? args[labelIdx + 1].split(",") : []
    const r = await callForgejo(
      "POST",
      `/repos/${full}/issues`,
      { title, body, labels },
      { okStatus: 201, cwd: opts?.cwd },
    )
    if (r.status !== 0) return r
    const issue = JSON.parse(r.stdout)
    return { status: 0, stdout: issue.html_url + "\n", stderr: "" }
  }
  return {
    status: 1,
    stdout: "",
    stderr: `gh argv ${JSON.stringify(args)} not supported in Forgejo mode`,
  }
}

/**
 * Standard success/error formatter for GitHub tools.
 *
 * On success (exit 0) returns `stdout.trim()`. On failure returns the
 * canonical error string `⚠️ <toolName> failed (exit <code>): <stderr or
 * stdout>`, matching the format previously duplicated across 5 tools.
 */
export function formatResult(r: { status: number | null; stdout: string; stderr: string }, toolName: string): string {
  if (r.status === 0) return r.stdout.trim()
  return `⚠️ ${toolName} failed (exit ${r.status}): ${r.stderr || r.stdout}`
}