import { join } from "node:path";

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

import { RULESYNC_RELATIVE_DIR_PATH } from "../../constants/rulesync-paths.js";
import { setupTestDirectory } from "../../test-utils/test-directories.js";
import { writeFileContent } from "../../utils/file.js";
import { RulesyncRule } from "./rulesync-rule.js";
import { WindsurfRule } from "./windsurf-rule.js";

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

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

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

  describe("constructor", () => {
    it("should create instance with default parameters", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "test-rule.md",
        fileContent: "# Test Rule\n\nThis is a test rule.",
      });

      expect(windsurfRule).toBeInstanceOf(WindsurfRule);
      expect(windsurfRule.getRelativeDirPath()).toBe(".windsurf/rules");
      expect(windsurfRule.getRelativeFilePath()).toBe("test-rule.md");
      expect(windsurfRule.getFileContent()).toBe("# Test Rule\n\nThis is a test rule.");
    });

    it("should create instance with custom outputRoot", () => {
      const windsurfRule = new WindsurfRule({
        outputRoot: "/custom/path",
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "test-rule.md",
        fileContent: "# Custom Rule",
      });

      expect(windsurfRule.getFilePath()).toBe("/custom/path/.windsurf/rules/test-rule.md");
    });

    it("should validate content by default", () => {
      expect(() => {
        const _instance = new WindsurfRule({
          relativeDirPath: ".windsurf/rules",
          relativeFilePath: "test-rule.md",
          fileContent: "", // empty content should be valid since validate always returns success
        });
      }).not.toThrow();
    });

    it("should skip validation when validate is false", () => {
      expect(() => {
        const _instance = new WindsurfRule({
          relativeDirPath: ".windsurf/rules",
          relativeFilePath: "test-rule.md",
          fileContent: "invalid content",
          validate: false,
        });
      }).not.toThrow();
    });

    it("should create instance with root property", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "test-rule.md",
        fileContent: "# Test Rule",
        root: true,
      });

      expect(windsurfRule.isRoot()).toBe(true);
    });
  });

  describe("fromFile", () => {
    it("should create instance from file", async () => {
      const ruleContent = "# Test Rule\n\nThis is a test rule from file.";
      const filePath = join(testDir, ".windsurf", "rules", "test-rule.md");
      await writeFileContent(filePath, ruleContent);

      const windsurfRule = await WindsurfRule.fromFile({
        outputRoot: testDir,
        relativeFilePath: "test-rule.md",
      });

      expect(windsurfRule).toBeInstanceOf(WindsurfRule);
      expect(windsurfRule.getRelativeDirPath()).toBe(".windsurf/rules");
      expect(windsurfRule.getRelativeFilePath()).toBe("test-rule.md");
      expect(windsurfRule.getFileContent()).toBe(ruleContent);
      expect(windsurfRule.getFilePath()).toBe(filePath);
    });

    it("should create instance from file with validate false", async () => {
      const ruleContent = "# Another Rule";
      const filePath = join(testDir, ".windsurf", "rules", "another-rule.md");
      await writeFileContent(filePath, ruleContent);

      const windsurfRule = await WindsurfRule.fromFile({
        outputRoot: testDir,
        relativeFilePath: "another-rule.md",
        validate: false,
      });

      expect(windsurfRule.getFileContent()).toBe(ruleContent);
    });

    it("should create instance from file with custom outputRoot", async () => {
      const customDir = join(testDir, "custom");
      const ruleContent = "# Custom Base Dir Rule";
      await writeFileContent(join(customDir, ".windsurf", "rules", "custom-rule.md"), ruleContent);

      const windsurfRule = await WindsurfRule.fromFile({
        outputRoot: customDir,
        relativeFilePath: "custom-rule.md",
      });

      expect(windsurfRule.getFileContent()).toBe(ruleContent);
      expect(windsurfRule.getFilePath()).toBe(
        join(customDir, ".windsurf", "rules", "custom-rule.md"),
      );
    });

    it("should throw error when file does not exist", async () => {
      await expect(
        WindsurfRule.fromFile({
          outputRoot: testDir,
          relativeFilePath: "non-existent.md",
        }),
      ).rejects.toThrow();
    });

    it("should handle nested file paths", async () => {
      const ruleContent = "# Nested Rule";
      await writeFileContent(
        join(testDir, ".windsurf", "rules", "nested", "deep", "nested-rule.md"),
        ruleContent,
      );

      const windsurfRule = await WindsurfRule.fromFile({
        outputRoot: testDir,
        relativeFilePath: "nested/deep/nested-rule.md",
      });

      expect(windsurfRule.getFileContent()).toBe(ruleContent);
      expect(windsurfRule.getRelativeFilePath()).toBe("nested/deep/nested-rule.md");
    });
  });

  describe("fromRulesyncRule", () => {
    it("should create instance from RulesyncRule with default parameters", () => {
      const rulesyncRule = new RulesyncRule({
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test-rule.md",
        frontmatter: { description: "Test Rule" },
        body: "# Test Rule Content",
      });

      const windsurfRule = WindsurfRule.fromRulesyncRule({
        rulesyncRule,
      }) as WindsurfRule;

      expect(windsurfRule).toBeInstanceOf(WindsurfRule);
      expect(windsurfRule.getRelativeDirPath()).toBe(".windsurf/rules");
      expect(windsurfRule.getRelativeFilePath()).toBe("test-rule.md");
      expect(windsurfRule.getFileContent()).toBe(
        "---\ntitle: Test Rule\ntrigger: always_on\n---\n# Test Rule Content\n",
      );
    });

    it("should create instance from RulesyncRule with custom outputRoot", () => {
      const rulesyncRule = new RulesyncRule({
        outputRoot: "/existing/path",
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "custom-rule.md",
        frontmatter: { description: "Custom Rule" },
        body: "# Custom Content",
      });

      const windsurfRule = WindsurfRule.fromRulesyncRule({
        outputRoot: "/custom/path",
        rulesyncRule,
      }) as WindsurfRule;

      expect(windsurfRule.getFilePath()).toBe("/custom/path/.windsurf/rules/custom-rule.md");
      expect(windsurfRule.getFileContent()).toBe(
        "---\ntitle: Custom Rule\ntrigger: always_on\n---\n# Custom Content\n",
      );
    });

    it("should create instance from RulesyncRule with validate false", () => {
      const rulesyncRule = new RulesyncRule({
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test-rule.md",
        frontmatter: { description: "Test Rule" },
        body: "# Content",
      });

      const windsurfRule = WindsurfRule.fromRulesyncRule({
        rulesyncRule,
        validate: false,
      }) as WindsurfRule;

      expect(windsurfRule).toBeInstanceOf(WindsurfRule);
      expect(windsurfRule.getFileContent()).toBe(
        "---\ntitle: Test Rule\ntrigger: always_on\n---\n# Content\n",
      );
    });

    it("should handle RulesyncRule with complex frontmatter", () => {
      const rulesyncRule = new RulesyncRule({
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "complex-rule.md",
        frontmatter: {
          description: "A complex rule with metadata",
          targets: ["windsurf", "cursor"],
        },
        body: "# Complex Rule\n\nThis is a complex rule with multiple targets.",
      });

      const windsurfRule = WindsurfRule.fromRulesyncRule({
        rulesyncRule,
      }) as WindsurfRule;

      expect(windsurfRule.getFileContent()).toBe(
        "---\ntitle: A complex rule with metadata\ntrigger: always_on\n---\n# Complex Rule\n\nThis is a complex rule with multiple targets.\n",
      );
    });

    it("should handle RulesyncRule with empty content after frontmatter", () => {
      const rulesyncRule = new RulesyncRule({
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "empty-content.md",
        frontmatter: { description: "Empty Content" },
        body: "",
      });

      const windsurfRule = WindsurfRule.fromRulesyncRule({
        rulesyncRule,
      }) as WindsurfRule;

      expect(windsurfRule.getFileContent()).toBe(
        "---\ntitle: Empty Content\ntrigger: always_on\n---\n\n",
      );
    });

    it("should include glob trigger and globs when specific globs are set", () => {
      const rulesyncRule = new RulesyncRule({
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "globbed.md",
        frontmatter: {
          description: "Globbed Rule",
          globs: ["src/**/*.ts", "test/**/*.ts"],
        },
        body: "# Globbed",
      });

      const windsurfRule = WindsurfRule.fromRulesyncRule({
        rulesyncRule,
      }) as WindsurfRule;

      expect(windsurfRule.getFileContent()).toBe(
        "---\ntitle: Globbed Rule\ntrigger: glob\nglobs:\n  - src/**/*.ts\n  - test/**/*.ts\n---\n# Globbed\n",
      );
    });
  });

  describe("toRulesyncRule", () => {
    it("should convert WindsurfRule to RulesyncRule", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "test-rule.md",
        fileContent: "# Test Rule Content",
      });

      const rulesyncRule = windsurfRule.toRulesyncRule();

      expect(rulesyncRule).toBeInstanceOf(RulesyncRule);
      expect(rulesyncRule.getRelativeFilePath()).toBe("test-rule.md");
      expect(rulesyncRule.getFileContent()).toContain("# Test Rule Content");
      expect(rulesyncRule.getFrontmatter()).toEqual({
        root: false,
        targets: ["*"],
        description: undefined,
        globs: [],
      });
    });

    it("should convert WindsurfRule with nested path to RulesyncRule", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "category/subcategory/nested-rule.md",
        fileContent: "# Nested Rule Content",
      });

      const rulesyncRule = windsurfRule.toRulesyncRule();

      expect(rulesyncRule.getRelativeFilePath()).toBe("category/subcategory/nested-rule.md");
      expect(rulesyncRule.getFrontmatter().root).toBe(false);
    });

    it("should preserve file content during conversion", () => {
      const content =
        "# Multi-line Rule\n\nThis rule has:\n- Multiple lines\n- Various content\n- Special characters: !@#$%";
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "multi-line.md",
        fileContent: content,
      });

      const rulesyncRule = windsurfRule.toRulesyncRule();

      expect(rulesyncRule.getFileContent()).toContain(content);
    });

    it("should handle empty content during conversion", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "empty.md",
        fileContent: "",
      });

      const rulesyncRule = windsurfRule.toRulesyncRule();

      expect(rulesyncRule.getFileContent()).toContain(
        "---\nroot: false\ntargets:\n  - '*'\nglobs: []\n---\n\n",
      );
    });
  });

  describe("validate", () => {
    it("should always return success", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "test-rule.md",
        fileContent: "# Test Content",
      });

      const result = windsurfRule.validate();

      expect(result.success).toBe(true);
      expect(result.error).toBeNull();
    });

    it("should return success even for empty content", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "empty.md",
        fileContent: "",
      });

      const result = windsurfRule.validate();

      expect(result.success).toBe(true);
      expect(result.error).toBeNull();
    });

    it("should return success for any content", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "any-content.md",
        fileContent: "Any kind of content\nWith special chars: !@#$%^&*()\nAnd unicode: 🚀🎉",
      });

      const result = windsurfRule.validate();

      expect(result.success).toBe(true);
      expect(result.error).toBeNull();
    });
  });

  describe("inheritance from ToolRule", () => {
    it("should inherit base functionality from ToolRule", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "inheritance-test.md",
        fileContent: "# Inheritance Test",
      });

      // Test inherited methods
      expect(windsurfRule.getRelativeDirPath()).toBe(".windsurf/rules");
      expect(windsurfRule.getRelativeFilePath()).toBe("inheritance-test.md");
      expect(windsurfRule.getFileContent()).toBe("# Inheritance Test");
      expect(windsurfRule.isRoot()).toBe(false);
    });

    it("should support root property", () => {
      const windsurfRule = new WindsurfRule({
        relativeDirPath: ".windsurf/rules",
        relativeFilePath: "root-test.md",
        fileContent: "# Root Test",
        root: true, // true means it's a root rule
      });

      expect(windsurfRule.isRoot()).toBe(true);
    });
  });

  describe("error handling", () => {
    it("should handle file read errors in fromFile", async () => {
      await expect(
        WindsurfRule.fromFile({
          outputRoot: "/non-existent-directory",
          relativeFilePath: "non-existent.md",
        }),
      ).rejects.toThrow();
    });

    it("should handle permission errors gracefully", async () => {
      // Try to read from a non-existent file in a valid directory
      await expect(
        WindsurfRule.fromFile({
          outputRoot: testDir,
          relativeFilePath: "restricted/non-existent.md",
        }),
      ).rejects.toThrow();
    });
  });

  describe("getSettablePaths", () => {
    it("should return correct paths for nonRoot", () => {
      const paths = WindsurfRule.getSettablePaths();

      expect(paths.nonRoot).toEqual({
        relativeDirPath: ".windsurf/rules",
      });
    });

    it("should have consistent paths structure", () => {
      const paths = WindsurfRule.getSettablePaths();

      expect(paths).toHaveProperty("nonRoot");
      expect(paths.nonRoot).toHaveProperty("relativeDirPath");
    });
  });

  describe("isTargetedByRulesyncRule", () => {
    it("should return true for rules targeting windsurf", () => {
      const rulesyncRule = new RulesyncRule({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test.md",
        frontmatter: {
          targets: ["windsurf"],
        },
        body: "Test content",
      });

      expect(WindsurfRule.isTargetedByRulesyncRule(rulesyncRule)).toBe(true);
    });

    it("should return true for rules targeting all tools (*)", () => {
      const rulesyncRule = new RulesyncRule({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test.md",
        frontmatter: {
          targets: ["*"],
        },
        body: "Test content",
      });

      expect(WindsurfRule.isTargetedByRulesyncRule(rulesyncRule)).toBe(true);
    });

    it("should return false for rules not targeting windsurf", () => {
      const rulesyncRule = new RulesyncRule({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test.md",
        frontmatter: {
          targets: ["cursor", "copilot"],
        },
        body: "Test content",
      });

      expect(WindsurfRule.isTargetedByRulesyncRule(rulesyncRule)).toBe(false);
    });

    it("should return false for empty targets", () => {
      const rulesyncRule = new RulesyncRule({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test.md",
        frontmatter: {
          targets: [],
        },
        body: "Test content",
      });

      expect(WindsurfRule.isTargetedByRulesyncRule(rulesyncRule)).toBe(false);
    });

    it("should handle mixed targets including windsurf", () => {
      const rulesyncRule = new RulesyncRule({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test.md",
        frontmatter: {
          targets: ["cursor", "windsurf", "copilot"],
        },
        body: "Test content",
      });

      expect(WindsurfRule.isTargetedByRulesyncRule(rulesyncRule)).toBe(true);
    });

    it("should handle undefined targets in frontmatter", () => {
      const rulesyncRule = new RulesyncRule({
        outputRoot: testDir,
        relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
        relativeFilePath: "test.md",
        frontmatter: {},
        body: "Test content",
      });

      expect(WindsurfRule.isTargetedByRulesyncRule(rulesyncRule)).toBe(true);
    });
  });

  describe("integration with file system", () => {
    it("should work with various file extensions", async () => {
      const extensions = [".md", ".txt", ".rule"];

      for (const ext of extensions) {
        const fileName = `test-rule${ext}`;
        const content = `# Test Rule for ${ext}`;
        await writeFileContent(join(testDir, ".windsurf", "rules", fileName), content);

        const windsurfRule = await WindsurfRule.fromFile({
          outputRoot: testDir,
          relativeFilePath: fileName,
        });

        expect(windsurfRule.getFileContent()).toBe(content);
        expect(windsurfRule.getRelativeFilePath()).toBe(fileName);
      }
    });

    it("should handle unicode content correctly", async () => {
      const unicodeContent = "# Unicode Rule 🚀\n\n日本語のルール\nEmoji: 🎉🔥💯";
      await writeFileContent(join(testDir, ".windsurf", "rules", "unicode.md"), unicodeContent);

      const windsurfRule = await WindsurfRule.fromFile({
        outputRoot: testDir,
        relativeFilePath: "unicode.md",
      });

      expect(windsurfRule.getFileContent()).toBe(unicodeContent);
    });

    it("should handle very large files", async () => {
      // Create a large content string
      const largeContent = "# Large Rule\n\n" + "A".repeat(10000) + "\n\nEnd of large rule.";
      await writeFileContent(join(testDir, ".windsurf", "rules", "large.md"), largeContent);

      const windsurfRule = await WindsurfRule.fromFile({
        outputRoot: testDir,
        relativeFilePath: "large.md",
      });

      expect(windsurfRule.getFileContent()).toBe(largeContent);
      expect(windsurfRule.getFileContent().length).toBe(largeContent.length);
    });
  });
});
