import { NextResponse } from "next/server";
import { promisify } from "util";
import { execFile } from "child_process";
import fs from "fs";
import path from "path";
import { isValidBranchName } from "@/lib/gitSafe";
import { auditLog } from "@/lib/audit";

const execFilePromise = promisify(execFile);

// Run git without a shell. Arguments are passed as an array, so values like file
// names, branch names and commit messages cannot break out into shell commands.
async function git(args: string[], cwd: string): Promise<{ stdout: string; stderr: string }> {
  const { stdout, stderr } = await execFilePromise("git", args, {
    cwd,
    maxBuffer: 1024 * 1024 * 20,
    windowsHide: true,
  });
  return { stdout: stdout.toString(), stderr: stderr.toString() };
}

export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url);
    const action = searchParams.get("action");
    const rootPath = searchParams.get("root");
    const file = searchParams.get("file");

    const targetRoot = ((rootPath && fs.existsSync(rootPath)) ? rootPath : process.env.ORIGINAL_CWD || process.cwd()).replace(/\\/g, "/");

    // Check if it's a git repo
    try {
      await git(["rev-parse", "--is-inside-work-tree"], targetRoot);
    } catch {
      return NextResponse.json({ success: true, isGit: false, files: [] });
    }

    if (action === "status") {
      const { stdout } = await git(["status", "--porcelain"], targetRoot);
      const lines = stdout.split(/\r?\n/).filter(Boolean);
      const files = lines.map(line => {
        // porcelain format: XY path or XY "path"
        const indexStatus = line.substring(0, 1);
        const worktreeStatus = line.substring(1, 2);
        const status = line.substring(0, 2);
        let relativePath = line.substring(3).trim();
        // Remove quotes if git quoted the path
        if (relativePath.startsWith('"') && relativePath.endsWith('"')) {
          relativePath = relativePath.slice(1, -1);
        }
        const absolutePath = path.join(targetRoot, relativePath).replace(/\\/g, "/");
        return {
          status: status.trim(),
          indexStatus,
          worktreeStatus,
          relativePath,
          absolutePath,
          name: path.basename(relativePath)
        };
      });

      // Get branch name
      let branch = "";
      try {
        const { stdout: branchOut } = await git(["rev-parse", "--abbrev-ref", "HEAD"], targetRoot);
        branch = branchOut.trim();
      } catch {}

      // Get ahead/behind sync count
      let ahead = 0;
      let behind = 0;
      try {
        const { stdout: syncOut } = await git(["rev-list", "--left-right", "--count", "HEAD...@{u}"], targetRoot);
        const parts = syncOut.trim().split(/\s+/);
        if (parts.length === 2) {
          ahead = parseInt(parts[0], 10) || 0;
          behind = parseInt(parts[1], 10) || 0;
        }
      } catch {}

      return NextResponse.json({ success: true, isGit: true, files, branch, ahead, behind });
    }

    if (action === "diff") {
      if (!file) {
        return NextResponse.json({ error: "Missing file parameter" }, { status: 400 });
      }

      const absolutePath = path.resolve(targetRoot, file).replace(/\\/g, "/");
      if (!fs.existsSync(absolutePath)) {
        return NextResponse.json({ error: "File not found" }, { status: 404 });
      }

      // Check status to see if it is untracked
      const { stdout: statusOut } = await git(["status", "--porcelain", "--", file], targetRoot);
      const isUntracked = statusOut.startsWith("??");

      if (isUntracked) {
        const fileContent = fs.readFileSync(absolutePath, "utf8");
        const diffLines = fileContent.split(/\r?\n/).map(l => "+" + l).join("\n");
        return NextResponse.json({ success: true, diff: diffLines });
      } else {
        try {
          const { stdout: diffOut } = await git(["diff", "--no-color", "--", file], targetRoot);
          return NextResponse.json({ success: true, diff: diffOut });
        } catch {
          return NextResponse.json({ success: true, diff: `Binary file or diff unavailable.` });
        }
      }
    }

    if (action === "branches") {
      const { stdout } = await git(["branch", "--format=%(refname:short)|%(HEAD)"], targetRoot);
      const branches = stdout.split(/\r?\n/).filter(Boolean).map(line => {
        const [name, active] = line.split("|");
        return {
          name: name.trim(),
          isActive: active === "*"
        };
      });
      return NextResponse.json({ success: true, branches });
    }

    if (action === "log") {
      try {
        const { stdout } = await git(
          ["log", "--graph", "--abbrev-commit", "--decorate", "--format=format:%h||%p||%s||%an||%ar", "--all", "-n", "100"],
          targetRoot
        );
        const lines = stdout.split(/\r?\n/);
        const logItems = lines.map(line => {
          const parts = line.split("||");
          if (parts.length >= 5) {
            const firstPart = parts[0];
            const hashMatch = firstPart.match(/([0-9a-f]{7,40})$/);
            const hash = hashMatch ? hashMatch[1] : "";
            const graph = firstPart.substring(0, firstPart.length - hash.length);

            return {
              isCommit: true,
              graph,
              hash,
              parents: parts[1].trim().split(/\s+/).filter(Boolean),
              subject: parts[2],
              author: parts[3],
              date: parts[4]
            };
          } else {
            return {
              isCommit: false,
              graph: line,
              hash: "",
              parents: [],
              subject: "",
              author: "",
              date: ""
            };
          }
        });
        return NextResponse.json({ success: true, log: logItems });
      } catch {
        return NextResponse.json({ success: true, log: [] });
      }
    }

    return NextResponse.json({ error: "Invalid action" }, { status: 400 });
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : "Unknown error";
    return NextResponse.json({ error: errorMessage }, { status: 500 });
  }
}

export async function POST(request: Request) {
  try {
    const body = await request.json();
    const { action, rootPath, file, message } = body;

    const targetRoot = ((rootPath && fs.existsSync(rootPath)) ? rootPath : process.env.ORIGINAL_CWD || process.cwd()).replace(/\\/g, "/");

    // Check if it's a git repo
    try {
      await git(["rev-parse", "--is-inside-work-tree"], targetRoot);
    } catch {
      return NextResponse.json({ error: "Not a git repository" }, { status: 400 });
    }

    if (action === "stage") {
      if (file) {
        await git(["add", "--", file], targetRoot);
      } else {
        await git(["add", "-A"], targetRoot);
      }
      return NextResponse.json({ success: true });
    }

    if (action === "unstage") {
      if (file) {
        await git(["reset", "HEAD", "--", file], targetRoot);
      } else {
        await git(["reset", "HEAD", "--", "."], targetRoot);
      }
      return NextResponse.json({ success: true });
    }

    if (action === "discard") {
      if (!file) {
        return NextResponse.json({ error: "Missing file parameter" }, { status: 400 });
      }
      const absolutePath = path.resolve(targetRoot, file).replace(/\\/g, "/");
      // Check status to see if it is untracked
      const { stdout: statusOut } = await git(["status", "--porcelain", "--", file], targetRoot);
      const isUntracked = statusOut.startsWith("??");

      if (isUntracked) {
        if (fs.existsSync(absolutePath)) {
          fs.unlinkSync(absolutePath);
        }
      } else {
        await git(["checkout", "--", file], targetRoot);
      }
      auditLog({ op: "git.discard", target: absolutePath, detail: { untracked: isUntracked, root: targetRoot } });
      return NextResponse.json({ success: true });
    }

    if (action === "commit") {
      if (!message || !message.trim()) {
        return NextResponse.json({ error: "Commit message cannot be empty" }, { status: 400 });
      }
      const { stdout } = await git(["commit", "-m", message.trim()], targetRoot);
      return NextResponse.json({ success: true, output: stdout });
    }

    if (action === "ignore") {
      if (!file) {
        return NextResponse.json({ error: "Missing file parameter" }, { status: 400 });
      }
      const gitignorePath = path.join(targetRoot, ".gitignore");
      let content = "";
      if (fs.existsSync(gitignorePath)) {
        content = fs.readFileSync(gitignorePath, "utf8");
      }
      const lines = content.split(/\r?\n/);
      if (!lines.includes(file)) {
        const separator = (content.endsWith("\n") || content === "") ? "" : "\n";
        fs.appendFileSync(gitignorePath, `${separator}${file}\n`);
      }
      return NextResponse.json({ success: true });
    }

    if (action === "pull") {
      const { stdout, stderr } = await git(["pull"], targetRoot);
      return NextResponse.json({ success: true, output: stdout + stderr });
    }

    if (action === "push") {
      const { stdout, stderr } = await git(["push"], targetRoot);
      return NextResponse.json({ success: true, output: stdout + stderr });
    }

    if (action === "checkout") {
      const { branchName } = body;
      if (!isValidBranchName(branchName)) {
        return NextResponse.json({ error: "Missing or invalid branchName parameter" }, { status: 400 });
      }
      const { stdout, stderr } = await git(["checkout", branchName], targetRoot);
      return NextResponse.json({ success: true, output: stdout + stderr });
    }

    if (action === "create_branch") {
      const { branchName } = body;
      if (!isValidBranchName(branchName)) {
        return NextResponse.json({ error: "Missing or invalid branchName parameter" }, { status: 400 });
      }
      const { stdout, stderr } = await git(["checkout", "-b", branchName], targetRoot);
      return NextResponse.json({ success: true, output: stdout + stderr });
    }

    if (action === "delete_branch") {
      const { branchName, force } = body;
      if (!isValidBranchName(branchName)) {
        return NextResponse.json({ error: "Missing or invalid branchName parameter" }, { status: 400 });
      }
      const flag = force ? "-D" : "-d";
      const { stdout, stderr } = await git(["branch", flag, branchName], targetRoot);
      return NextResponse.json({ success: true, output: stdout + stderr });
    }

    return NextResponse.json({ error: "Invalid action" }, { status: 400 });
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : "Unknown error";
    return NextResponse.json({ error: errorMessage }, { status: 500 });
  }
}
