import { join } from "node:path";

import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { SKILL_FILE_NAME } from "../../constants/general.js";
import { RULESYNC_SKILLS_RELATIVE_DIR_PATH } from "../../constants/rulesync-paths.js";
import { setupTestDirectory } from "../../test-utils/test-directories.js";
import { ensureDir, writeFileContent } from "../../utils/file.js";
import { CodexCliSkill } from "./codexcli-skill.js";
import { RulesyncSkill } from "./rulesync-skill.js";

describe("CodexCliSkill", () => {
  let testDir: string;
  let cleanup: () => Promise<void>;

  beforeEach(async () => {
    const testSetup = await setupTestDirectory();
    testDir = testSetup.testDir;
    cleanup = testSetup.cleanup;
    vi.spyOn(process, "cwd").mockReturnValue(testDir);
  });

  afterEach(async () => {
    await cleanup();
    vi.restoreAllMocks();
  });

  describe("getSettablePaths", () => {
    it("should return .codex/skills as relativeDirPath in project mode", () => {
      const paths = CodexCliSkill.getSettablePaths();
      expect(paths.relativeDirPath).toBe(join(".codex", "skills"));
    });

    it("should return .codex/skills as relativeDirPath in global mode", () => {
      const paths = CodexCliSkill.getSettablePaths({ global: true });
      expect(paths.relativeDirPath).toBe(join(".codex", "skills"));
    });
  });

  describe("constructor", () => {
    it("should create instance with valid content in global mode", () => {
      const skill = new CodexCliSkill({
        outputRoot: testDir,
        relativeDirPath: join(".codex", "skills"),
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "Test skill description",
        },
        body: "This is the body of the codex cli skill.",
        validate: true,
        global: true,
      });

      expect(skill).toBeInstanceOf(CodexCliSkill);
      expect(skill.getBody()).toBe("This is the body of the codex cli skill.");
      expect(skill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "Test skill description",
      });
    });

    it("should create instance with valid content in project mode", () => {
      const skill = new CodexCliSkill({
        outputRoot: testDir,
        relativeDirPath: join(".codex", "skills"),
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "Test skill description",
        },
        body: "This is the body of the codex cli skill.",
        validate: true,
        global: false,
      });

      expect(skill).toBeInstanceOf(CodexCliSkill);
      expect(skill.getBody()).toBe("This is the body of the codex cli skill.");
      expect(skill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "Test skill description",
      });
    });

    it("should create instance with metadata.short-description", () => {
      const skill = new CodexCliSkill({
        outputRoot: testDir,
        relativeDirPath: join(".codex", "skills"),
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "AI-facing description",
          metadata: {
            "short-description": "User-facing description",
          },
        },
        body: "This is the body of the codex cli skill.",
        validate: true,
        global: false,
      });

      expect(skill).toBeInstanceOf(CodexCliSkill);
      expect(skill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "AI-facing description",
        metadata: {
          "short-description": "User-facing description",
        },
      });
    });
  });

  describe("fromDir", () => {
    it("should create instance from valid skill directory in global mode", async () => {
      const skillDir = join(testDir, ".codex", "skills", "test-skill");
      await ensureDir(skillDir);
      const skillContent = `---
name: Test Skill
description: Test skill description
---

This is the body of the codex cli skill.`;
      await writeFileContent(join(skillDir, SKILL_FILE_NAME), skillContent);

      const skill = await CodexCliSkill.fromDir({
        outputRoot: testDir,
        dirName: "test-skill",
        global: true,
      });

      expect(skill).toBeInstanceOf(CodexCliSkill);
      expect(skill.getBody()).toBe("This is the body of the codex cli skill.");
      expect(skill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "Test skill description",
      });
    });

    it("should create instance from valid skill directory in project mode", async () => {
      const skillDir = join(testDir, ".codex", "skills", "test-skill");
      await ensureDir(skillDir);
      const skillContent = `---
name: Test Skill
description: Test skill description
---

This is the body of the codex cli skill.`;
      await writeFileContent(join(skillDir, SKILL_FILE_NAME), skillContent);

      const skill = await CodexCliSkill.fromDir({
        outputRoot: testDir,
        dirName: "test-skill",
        global: false,
      });

      expect(skill).toBeInstanceOf(CodexCliSkill);
      expect(skill.getBody()).toBe("This is the body of the codex cli skill.");
      expect(skill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "Test skill description",
      });
    });

    it("should create instance with metadata.short-description from directory", async () => {
      const skillDir = join(testDir, ".codex", "skills", "test-skill");
      await ensureDir(skillDir);
      const skillContent = `---
name: Test Skill
description: AI-facing description
metadata:
  short-description: User-facing description
---

This is the body of the codex cli skill.`;
      await writeFileContent(join(skillDir, SKILL_FILE_NAME), skillContent);

      const skill = await CodexCliSkill.fromDir({
        outputRoot: testDir,
        dirName: "test-skill",
        global: false,
      });

      expect(skill).toBeInstanceOf(CodexCliSkill);
      expect(skill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "AI-facing description",
        metadata: {
          "short-description": "User-facing description",
        },
      });
    });

    it("should throw error when SKILL.md not found", async () => {
      const skillDir = join(testDir, ".codex", "skills", "empty-skill");
      await ensureDir(skillDir);

      await expect(
        CodexCliSkill.fromDir({
          outputRoot: testDir,
          dirName: "empty-skill",
          global: true,
        }),
      ).rejects.toThrow(/SKILL\.md not found/);
    });
  });

  describe("fromRulesyncSkill", () => {
    it("should create instance from RulesyncSkill in global mode", () => {
      const rulesyncSkill = new RulesyncSkill({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "Test skill description",
        },
        body: "Test body content",
        validate: true,
      });

      const codexCliSkill = CodexCliSkill.fromRulesyncSkill({
        rulesyncSkill,
        validate: true,
        global: true,
      });

      expect(codexCliSkill).toBeInstanceOf(CodexCliSkill);
      expect(codexCliSkill.getBody()).toBe("Test body content");
      expect(codexCliSkill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "Test skill description",
      });
    });

    it("should create instance from RulesyncSkill in project mode", () => {
      const rulesyncSkill = new RulesyncSkill({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "Test skill description",
        },
        body: "Test body content",
        validate: true,
      });

      const codexCliSkill = CodexCliSkill.fromRulesyncSkill({
        rulesyncSkill,
        validate: true,
        global: false,
      });

      expect(codexCliSkill).toBeInstanceOf(CodexCliSkill);
      expect(codexCliSkill.getBody()).toBe("Test body content");
      expect(codexCliSkill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "Test skill description",
      });
    });

    it("should convert codexcli.short-description to metadata.short-description", () => {
      const rulesyncSkill = new RulesyncSkill({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "AI-facing description",
          codexcli: {
            "short-description": "User-facing description",
          },
        },
        body: "Test body content",
        validate: true,
      });

      const codexCliSkill = CodexCliSkill.fromRulesyncSkill({
        rulesyncSkill,
        validate: true,
        global: false,
      });

      expect(codexCliSkill).toBeInstanceOf(CodexCliSkill);
      expect(codexCliSkill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "AI-facing description",
        metadata: {
          "short-description": "User-facing description",
        },
      });
    });
  });

  describe("isTargetedByRulesyncSkill", () => {
    it("should return true when targets includes '*'", () => {
      const rulesyncSkill = new RulesyncSkill({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
        dirName: "all-targets-skill",
        frontmatter: {
          name: "All Targets Skill",
          description: "Skill for all targets",
          targets: ["*"],
        },
        body: "Test body",
        validate: true,
      });

      expect(CodexCliSkill.isTargetedByRulesyncSkill(rulesyncSkill)).toBe(true);
    });

    it("should return true when targets includes 'codexcli'", () => {
      const rulesyncSkill = new RulesyncSkill({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
        dirName: "codexcli-skill",
        frontmatter: {
          name: "CodexCli Skill",
          description: "Skill for codexcli",
          targets: ["copilot", "codexcli"],
        },
        body: "Test body",
        validate: true,
      });

      expect(CodexCliSkill.isTargetedByRulesyncSkill(rulesyncSkill)).toBe(true);
    });

    it("should return false when targets does not include 'codexcli'", () => {
      const rulesyncSkill = new RulesyncSkill({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_SKILLS_RELATIVE_DIR_PATH,
        dirName: "claudecode-only-skill",
        frontmatter: {
          name: "ClaudeCode Only Skill",
          description: "Skill for claudecode only",
          targets: ["claudecode"],
        },
        body: "Test body",
        validate: true,
      });

      expect(CodexCliSkill.isTargetedByRulesyncSkill(rulesyncSkill)).toBe(false);
    });
  });

  describe("toRulesyncSkill", () => {
    it("should convert to a RulesyncSkill", () => {
      const skill = new CodexCliSkill({
        outputRoot: testDir,
        relativeDirPath: join(".codex", "skills"),
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "Test description",
        },
        body: "Test body",
        validate: true,
        global: true,
      });

      const rulesyncSkill = skill.toRulesyncSkill();

      expect(rulesyncSkill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "Test description",
        targets: ["*"],
      });
      expect(rulesyncSkill.getBody()).toBe("Test body");
    });

    it("should convert metadata.short-description to codexcli.short-description", () => {
      const skill = new CodexCliSkill({
        outputRoot: testDir,
        relativeDirPath: join(".codex", "skills"),
        dirName: "test-skill",
        frontmatter: {
          name: "Test Skill",
          description: "AI-facing description",
          metadata: {
            "short-description": "User-facing description",
          },
        },
        body: "Test body",
        validate: true,
        global: false,
      });

      const rulesyncSkill = skill.toRulesyncSkill();

      expect(rulesyncSkill.getFrontmatter()).toEqual({
        name: "Test Skill",
        description: "AI-facing description",
        targets: ["*"],
        codexcli: {
          "short-description": "User-facing description",
        },
      });
      expect(rulesyncSkill.getBody()).toBe("Test body");
    });
  });
});
