import fs from "node:fs";
import fsp from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { PACKAGE_ROOT } from "../../../../util";
import { formatGhActionsSyncReport, collectGhActionsSyncResult } from "./sync-check";

const WORKFLOWS_SRC = path.join(PACKAGE_ROOT, "templates", "workflows");
const ACTIONS_SRC = path.join(PACKAGE_ROOT, "templates", "actions");

async function copyDir(src: string, dest: string): Promise<void> {
  await fsp.mkdir(dest, { recursive: true });
  for (const entry of await fsp.readdir(src, { withFileTypes: true })) {
    const srcPath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);
    if (entry.isDirectory()) {
      await copyDir(srcPath, destPath);
    } else if (entry.isFile()) {
      await fsp.copyFile(srcPath, destPath);
    }
  }
}

async function stageFreshRepo(cwd: string): Promise<void> {
  await copyDir(WORKFLOWS_SRC, path.join(cwd, ".github", "workflows"));
  await copyDir(ACTIONS_SRC, path.join(cwd, ".github", "actions"));
}

describe("collectGhActionsSyncResult", () => {
  let tmpDir: string;

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "gh-actions-sync-"));
  });

  afterEach(() => {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  });

  it("returns exit code 0 when local copies match source templates", async () => {
    await stageFreshRepo(tmpDir);
    const result = await collectGhActionsSyncResult(tmpDir);
    expect(result.exitCode).toBe(0);
    expect(result.errors).toEqual([]);
    expect(result.summary.filesChecked).toBeGreaterThan(0);
  });

  it("reports missing when a workflow file is absent", async () => {
    await stageFreshRepo(tmpDir);
    const targetWorkflow = (await fsp.readdir(path.join(tmpDir, ".github", "workflows")))[0];
    await fsp.unlink(path.join(tmpDir, ".github", "workflows", targetWorkflow));

    const result = await collectGhActionsSyncResult(tmpDir);
    const missing = result.errors.filter((e) => e.type === "missing");
    expect(missing.length).toBe(1);
    expect(missing[0].filePath).toBe(path.join(".github", "workflows", targetWorkflow));
    expect(missing[0].severity).toBe("error");
    expect(result.exitCode).toBe(1);
  });

  it("reports modified when a workflow file content differs", async () => {
    await stageFreshRepo(tmpDir);
    const targetWorkflow = (await fsp.readdir(path.join(tmpDir, ".github", "workflows")))[0];
    const targetPath = path.join(tmpDir, ".github", "workflows", targetWorkflow);
    const original = await fsp.readFile(targetPath, "utf-8");
    await fsp.writeFile(targetPath, `${original}\n# locally modified`);

    const result = await collectGhActionsSyncResult(tmpDir);
    const modified = result.errors.filter((e) => e.type === "modified");
    expect(modified.length).toBe(1);
    expect(modified[0].filePath).toBe(path.join(".github", "workflows", targetWorkflow));
    expect(modified[0].severity).toBe("error");
    expect(result.exitCode).toBe(1);
  });

  it("reports missing when an entire action directory is absent", async () => {
    await stageFreshRepo(tmpDir);
    const actionDirs = (
      await fsp.readdir(path.join(tmpDir, ".github", "actions"), { withFileTypes: true })
    ).filter((e) => e.isDirectory() && e.name.startsWith("erp-kit-"));
    expect(actionDirs.length).toBeGreaterThan(0);
    const targetAction = actionDirs[0].name;
    await fsp.rm(path.join(tmpDir, ".github", "actions", targetAction), {
      recursive: true,
      force: true,
    });

    const result = await collectGhActionsSyncResult(tmpDir);
    const missing = result.errors.filter(
      (e) => e.type === "missing" && e.filePath.includes(targetAction),
    );
    expect(missing.length).toBeGreaterThan(0);
  });

  it("ignores non-erp-kit files in .github/workflows", async () => {
    await stageFreshRepo(tmpDir);
    await fsp.writeFile(
      path.join(tmpDir, ".github", "workflows", "user-custom.yml"),
      "name: user custom\n",
    );

    const result = await collectGhActionsSyncResult(tmpDir);
    expect(result.errors).toEqual([]);
  });

  it("reports all framework templates as missing when .github does not exist", async () => {
    const result = await collectGhActionsSyncResult(tmpDir);
    expect(result.errors.length).toBeGreaterThan(0);
    expect(result.errors.every((e) => e.type === "missing")).toBe(true);
    expect(result.summary.filesChecked).toBeGreaterThan(0);
    expect(result.exitCode).toBe(1);
  });
});

describe("formatGhActionsSyncReport", () => {
  it("formats a passing result", () => {
    const report = formatGhActionsSyncReport({
      exitCode: 0,
      errors: [],
      summary: { filesChecked: 4, errorCount: 0 },
    });
    expect(report).toContain("gh-actions sync-check passed");
    expect(report).toContain("Files checked: 4");
  });

  it("formats a failing result with errors", () => {
    const report = formatGhActionsSyncReport({
      exitCode: 1,
      errors: [
        {
          type: "missing",
          severity: "error",
          filePath: ".github/workflows/erp-kit-check.yml",
          message: "Framework template not found locally.",
        },
      ],
      summary: { filesChecked: 4, errorCount: 1 },
    });
    expect(report).toContain("Errors:");
    expect(report).toContain(".github/workflows/erp-kit-check.yml");
    expect(report).toContain("gh-actions sync-check failed");
  });
});
