import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { copySkills, copyWorkflows, copyActions, setupSymlink, SKILLS_SRC } from "./distribute";

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

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "distribute-test-"));
    skillsDest = path.join(tmpDir, ".agents", "skills");
  });

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

  it("copies erp-kit-* skills to destination", () => {
    const result = copySkills(skillsDest);
    const skillPath = path.join(skillsDest, "erp-kit-module-1-requirements", "SKILL.md");
    expect(fs.existsSync(skillPath)).toBe(true);
    expect(result.copied).toBeGreaterThan(0);
  });

  it("removes stale erp-kit-* skills before copying", () => {
    const staleDir = path.join(skillsDest, "erp-kit-old-skill");
    fs.mkdirSync(staleDir, { recursive: true });
    fs.writeFileSync(path.join(staleDir, "SKILL.md"), "# Stale");
    const result = copySkills(skillsDest);
    expect(fs.existsSync(staleDir)).toBe(false);
    expect(result.removed).toBe(1);
  });

  it("preserves non-erp-kit skills", () => {
    const customDir = path.join(skillsDest, "my-custom-skill");
    fs.mkdirSync(customDir, { recursive: true });
    fs.writeFileSync(path.join(customDir, "SKILL.md"), "# Custom");
    copySkills(skillsDest);
    expect(fs.readFileSync(path.join(customDir, "SKILL.md"), "utf-8")).toBe("# Custom");
  });
});

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

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "distribute-test-"));
  });

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

  it("copies workflow templates to .github/workflows/", () => {
    const result = copyWorkflows(tmpDir);
    const wfPath = path.join(tmpDir, ".github", "workflows", "erp-kit-check.yml");
    expect(fs.existsSync(wfPath)).toBe(true);
    expect(result.copied).toBeGreaterThan(0);
  });

  it("overwrites existing workflows", () => {
    const wfDir = path.join(tmpDir, ".github", "workflows");
    fs.mkdirSync(wfDir, { recursive: true });
    fs.writeFileSync(path.join(wfDir, "erp-kit-check.yml"), "# Old");
    copyWorkflows(tmpDir);
    const content = fs.readFileSync(path.join(wfDir, "erp-kit-check.yml"), "utf-8");
    expect(content).not.toBe("# Old");
  });

  it("removes stale erp-kit-* workflows not in source", () => {
    const wfDir = path.join(tmpDir, ".github", "workflows");
    fs.mkdirSync(wfDir, { recursive: true });
    fs.writeFileSync(path.join(wfDir, "erp-kit-old-removed.yml"), "# Stale");
    const result = copyWorkflows(tmpDir);
    expect(fs.existsSync(path.join(wfDir, "erp-kit-old-removed.yml"))).toBe(false);
    expect(result.removed).toBe(1);
  });

  it("preserves non-erp-kit workflows", () => {
    const wfDir = path.join(tmpDir, ".github", "workflows");
    fs.mkdirSync(wfDir, { recursive: true });
    fs.writeFileSync(path.join(wfDir, "custom-ci.yml"), "# Custom");
    copyWorkflows(tmpDir);
    expect(fs.existsSync(path.join(wfDir, "custom-ci.yml"))).toBe(true);
  });
});

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

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "distribute-test-"));
  });

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

  it("copies action directories to .github/actions/", () => {
    const result = copyActions(tmpDir);
    const actionPath = path.join(
      tmpDir,
      ".github",
      "actions",
      "erp-kit-fetch-tailor-token",
      "action.yml",
    );
    expect(fs.existsSync(actionPath)).toBe(true);
    expect(result.copied).toBeGreaterThan(0);
  });

  it("overwrites existing actions", () => {
    const actionDir = path.join(tmpDir, ".github", "actions", "erp-kit-fetch-tailor-token");
    fs.mkdirSync(actionDir, { recursive: true });
    fs.writeFileSync(path.join(actionDir, "action.yml"), "# Old");
    copyActions(tmpDir);
    const content = fs.readFileSync(path.join(actionDir, "action.yml"), "utf-8");
    expect(content).not.toBe("# Old");
  });

  it("removes stale erp-kit-* action directories not in source", () => {
    const staleDir = path.join(tmpDir, ".github", "actions", "erp-kit-old-removed");
    fs.mkdirSync(staleDir, { recursive: true });
    fs.writeFileSync(path.join(staleDir, "action.yml"), "# Stale");
    const result = copyActions(tmpDir);
    expect(fs.existsSync(staleDir)).toBe(false);
    expect(result.removed).toBe(1);
  });

  it("preserves non-erp-kit actions", () => {
    const customDir = path.join(tmpDir, ".github", "actions", "custom-action");
    fs.mkdirSync(customDir, { recursive: true });
    fs.writeFileSync(path.join(customDir, "action.yml"), "# Custom");
    copyActions(tmpDir);
    expect(fs.existsSync(customDir)).toBe(true);
  });
});

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

  beforeEach(() => {
    tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "distribute-test-"));
  });

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

  it("creates .claude/skills symlink", () => {
    setupSymlink(tmpDir);
    const claudeSkills = path.join(tmpDir, ".claude", "skills");
    expect(fs.lstatSync(claudeSkills).isSymbolicLink()).toBe(true);
    expect(fs.readlinkSync(claudeSkills)).toBe(path.join("..", ".agents", "skills"));
  });

  it("skips when correct symlink already exists", () => {
    setupSymlink(tmpDir);
    const result = setupSymlink(tmpDir);
    expect(result).toBe("already_linked");
  });

  it("skips when .claude/skills is a real directory", () => {
    const claudeSkills = path.join(tmpDir, ".claude", "skills");
    fs.mkdirSync(claudeSkills, { recursive: true });
    const result = setupSymlink(tmpDir);
    expect(result).toBe("skipped_directory");
  });

  it("falls back to copy when symlink creation fails", () => {
    // Simulate Windows EPERM — symlink creation fails
    vi.spyOn(fs, "symlinkSync").mockImplementation(() => {
      const err = new Error("EPERM: operation not permitted") as NodeJS.ErrnoException;
      err.code = "EPERM";
      throw err;
    });

    try {
      const result = setupSymlink(tmpDir);
      expect(result).toBe("copied");

      const claudeSkills = path.join(tmpDir, ".claude", "skills");
      expect(fs.existsSync(claudeSkills)).toBe(true);
      expect(fs.statSync(claudeSkills).isDirectory()).toBe(true);
      // Not a symlink — it's a real directory copy
      expect(fs.lstatSync(claudeSkills).isSymbolicLink()).toBe(false);
      // Framework skills were copied in
      expect(
        fs.existsSync(path.join(claudeSkills, "erp-kit-module-1-requirements", "SKILL.md")),
      ).toBe(true);
    } finally {
      vi.restoreAllMocks();
    }
  });
});

describe("framework skills source", () => {
  // Updates only remove erp-kit-* skills, so anything else here would be copied
  // to users and never cleaned up.
  it("contains only erp-kit-* skill directories", () => {
    const invalid = fs
      .readdirSync(SKILLS_SRC, { withFileTypes: true })
      .filter((entry) => !entry.isDirectory() || !entry.name.startsWith("erp-kit-"))
      .map((entry) => entry.name);
    expect(invalid).toEqual([]);
  });
});
