import { $ } from "bun"
import * as Log from "@saeeol/core/util/log"
import { Instance } from "@/project/instance"
import type { DiffFile, DiffHunk, DiffResult } from "./types"

const log = Log.create({ service: "review" })

export function parseDiff(raw: string): DiffResult {
  const files: DiffFile[] = []

  if (!raw.trim()) {
    return { files: [], raw }
  }

  const fileDiffs = raw.split(/^diff --git /m).filter(Boolean)

  for (const fileDiff of fileDiffs) {
    const file = parseFileDiff("diff --git " + fileDiff)
    if (file) files.push(file)
  }

  return { files, raw }
}

function parseFileDiff(content: string): DiffFile | null {
  const lines = content.split("\n")

  const headerMatch = lines[0]?.match(/^diff --git a\/(.+) b\/(.+)$/)
  if (!headerMatch) return null

  const oldPath = headerMatch[1]
  const newPath = headerMatch[2]

  let status: DiffFile["status"] = "modified"
  const isNew = lines.some((l) => l.startsWith("new file mode"))
  const isDeleted = lines.some((l) => l.startsWith("deleted file mode"))
  const isRenamed = lines.some((l) => l.startsWith("rename from"))

  if (isNew) status = "added"
  else if (isDeleted) status = "deleted"
  else if (isRenamed) status = "renamed"

  const hunks: DiffHunk[] = []
  let currentHunk: DiffHunk | null = null
  let hunkContent: string[] = []

  for (const line of lines) {
    const hunkMatch = line.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/)
    if (hunkMatch) {
      if (currentHunk) {
        currentHunk.content = hunkContent.join("\n")
        hunks.push(currentHunk)
      }
      currentHunk = {
        oldStart: parseInt(hunkMatch[1], 10),
        oldLines: parseInt(hunkMatch[2] || "1", 10),
        newStart: parseInt(hunkMatch[3], 10),
        newLines: parseInt(hunkMatch[4] || "1", 10),
        content: "",
      }
      hunkContent = [line]
    } else if (currentHunk && (line.startsWith("+") || line.startsWith("-") || line.startsWith(" "))) {
      hunkContent.push(line)
    }
  }

  if (currentHunk) {
    currentHunk.content = hunkContent.join("\n")
    hunks.push(currentHunk)
  }

  return {
    path: newPath,
    status,
    hunks,
    ...(isRenamed && oldPath !== newPath ? { oldPath } : {}),
  }
}

export async function getCurrentBranch(): Promise<string> {
  const result = await $`git rev-parse --abbrev-ref HEAD`.cwd(Instance.directory).quiet().nothrow()
  return result.stdout.toString().trim()
}

export async function getBaseBranch(): Promise<string> {
  const candidates = ["main", "master", "dev", "develop"]

  for (const branch of candidates) {
    const remoteCheck = await $`git show-ref --verify --quiet refs/remotes/origin/${branch}`
      .cwd(Instance.directory)
      .quiet()
      .nothrow()

    if (remoteCheck.exitCode === 0) {
      log.info("detected remote base branch", { branch: `origin/${branch}` })
      return `origin/${branch}`
    }
  }

  for (const branch of candidates) {
    const check = await $`git show-ref --verify --quiet refs/heads/${branch}`
      .cwd(Instance.directory)
      .quiet()
      .nothrow()

    if (check.exitCode === 0) {
      log.info("detected local base branch", { branch })
      return branch
    }
  }

  log.warn("no base branch found, defaulting to main")
  return "main"
}

export async function getBranchCommits(base: string, current: string): Promise<string> {
  const result = await $`git log ${base}..${current} --oneline`.cwd(Instance.directory).quiet().nothrow()

  if (result.exitCode !== 0) {
    log.warn("git log for branch commits failed", {
      exitCode: result.exitCode,
      stderr: result.stderr.toString(),
    })
    return ""
  }

  return result.stdout.toString().trim()
}
