import { execFile as execFileCb } from "node:child_process";
import { promisify } from "node:util";
import { existsSync } from "node:fs";
import path from "node:path";

const execFile = promisify(execFileCb);

export interface GitFileService {
  ensureRepo(storageDir: string): Promise<void>;
  commitFile(storageDir: string, objectKey: string, message: string): Promise<string | null>;
  getLog(
    storageDir: string,
    objectKey: string,
    limit?: number,
  ): Promise<Array<{ hash: string; date: string; message: string; author: string }>>;
}

export function gitFileService(): GitFileService {
  async function git(
    cwd: string,
    args: string[],
  ): Promise<{ stdout: string; stderr: string }> {
    return execFile("git", args, { cwd, timeout: 10000 });
  }

  return {
    async ensureRepo(storageDir: string) {
      const gitDir = path.join(storageDir, ".git");
      if (existsSync(gitDir)) return;
      await git(storageDir, ["init"]);
      await git(storageDir, ["config", "user.email", "nexus@local"]);
      await git(storageDir, ["config", "user.name", "Nexus File System"]);
    },

    async commitFile(
      storageDir: string,
      objectKey: string,
      message: string,
    ): Promise<string | null> {
      const filePath = path.join(storageDir, objectKey);
      if (!existsSync(filePath)) return null;

      await this.ensureRepo(storageDir);

      try {
        await git(storageDir, ["add", "--", objectKey]);
        const { stdout } = await git(storageDir, [
          "commit",
          "-m",
          message,
          "--",
          objectKey,
        ]);
        const match = /\[[\w\s]+\s([a-f0-9]+)\]/.exec(stdout);
        return match?.[1] ?? null;
      } catch (err) {
        const errMsg = String(
          (err as { stderr?: string }).stderr ??
            (err as Error).message ??
            "",
        );
        if (
          errMsg.includes("nothing to commit") ||
          errMsg.includes("no changes added")
        ) {
          return null;
        }
        throw err;
      }
    },

    async getLog(storageDir: string, objectKey: string, limit = 50) {
      try {
        await this.ensureRepo(storageDir);
        const { stdout } = await git(storageDir, [
          "log",
          `--max-count=${limit}`,
          "--format=%H|%aI|%s|%an",
          "--",
          objectKey,
        ]);

        return stdout
          .trim()
          .split("\n")
          .filter(Boolean)
          .map((line) => {
            const [hash, date, message, author] = line.split("|");
            return {
              hash: hash!,
              date: date!,
              message: message!,
              author: author!,
            };
          });
      } catch {
        return [];
      }
    },
  };
}
