import { execFile, spawn } from "node:child_process";
import { promisify } from "node:util";
import type { ChangeSetMergeAdapter, ChangeSetMergeInput } from "@awp/application";

const execute = promisify(execFile);

async function existingChangeSetCommit(
  repositoryPath: string,
  changeSetId: string,
  env: NodeJS.ProcessEnv,
): Promise<string | undefined> {
  const { stdout } = await execute(
    "git",
    [
      "log",
      "--all",
      "--fixed-strings",
      `--grep=AWP-ChangeSet: ${changeSetId}`,
      "-1",
      "--format=%H",
    ],
    { cwd: repositoryPath, env },
  );
  return stdout.trim() || undefined;
}

function applyDiff(repositoryPath: string, diff: string, env: NodeJS.ProcessEnv): Promise<void> {
  return new Promise((resolve, reject) => {
    const child = spawn("git", ["apply", "--index", "-"], {
      cwd: repositoryPath,
      env,
      stdio: ["pipe", "ignore", "pipe"],
    });
    let stderr = "";
    child.stderr.setEncoding("utf8");
    child.stderr.on("data", (chunk: string) => {
      stderr += chunk;
    });
    child.on("error", reject);
    child.on("close", (code) => {
      if (code === 0) resolve();
      else reject(new Error(`Trusted git apply failed (exit ${code}): ${stderr.trim()}`));
    });
    child.stdin.end(diff);
  });
}

export class LocalGitTrustedMergeAdapter implements ChangeSetMergeAdapter {
  constructor(
    private readonly repositoryPath: string,
    private readonly committerName: string,
    private readonly committerEmail: string,
  ) {}

  async merge(input: ChangeSetMergeInput): Promise<string> {
    const env = {
      ...process.env,
      GIT_AUTHOR_NAME: this.committerName,
      GIT_AUTHOR_EMAIL: this.committerEmail,
      GIT_COMMITTER_NAME: this.committerName,
      GIT_COMMITTER_EMAIL: this.committerEmail,
    };
    const existing = await existingChangeSetCommit(this.repositoryPath, input.changeSet.id, env);
    if (existing) {
      await execute("git", ["push", "origin", "HEAD"], { cwd: this.repositoryPath, env });
      return existing;
    }
    await applyDiff(this.repositoryPath, input.changeSet.diff, env);
    await execute(
      "git",
      [
        "commit",
        "-m",
        `AWP ChangeSet ${input.changeSet.id}`,
        "-m",
        `AWP-FactoryRun: ${input.factoryRun.id}`,
        "-m",
        `AWP-ChangeSet: ${input.changeSet.id}`,
      ],
      { cwd: this.repositoryPath, env },
    );
    await execute("git", ["push", "origin", "HEAD"], { cwd: this.repositoryPath, env });
    const { stdout } = await execute("git", ["rev-parse", "HEAD"], {
      cwd: this.repositoryPath,
      env,
    });
    return stdout.trim();
  }
}
