import { describe, it, expect, vi, beforeEach } from "vitest";
import { TaxonomyService } from "../taxonomy-service";
import type { HaloClient } from "../client";
import type { Category, Tag } from "../types";

describe("TaxonomyService", () => {
  let service: TaxonomyService;
  let mockClient: HaloClient;

  beforeEach(() => {
    mockClient = {
      get: vi.fn(),
      post: vi.fn(),
      put: vi.fn(),
      delete: vi.fn(),
    } as unknown as HaloClient;
    service = new TaxonomyService(mockClient);
  });

  describe("getCategories", () => {
    it("should return categories from API", async () => {
      const mockCategories: Category[] = [
        {
          metadata: { name: "cat-1" },
          spec: { displayName: "技术", slug: "tech", description: "", cover: "", template: "", priority: 0, children: [] },
        },
        {
          metadata: { name: "cat-2" },
          spec: { displayName: "生活", slug: "life", description: "", cover: "", template: "", priority: 1, children: [] },
        },
      ];

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockCategories });

      const result = await service.getCategories();

      expect(result).toEqual(mockCategories);
      expect(mockClient.get).toHaveBeenCalledWith("/apis/content.halo.run/v1alpha1/categories");
    });
  });

  describe("getTags", () => {
    it("should return tags from API", async () => {
      const mockTags: Tag[] = [
        {
          metadata: { name: "tag-1" },
          spec: { displayName: "Python", slug: "python", color: "#3776AB", cover: "" },
        },
      ];

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockTags });

      const result = await service.getTags();

      expect(result).toEqual(mockTags);
      expect(mockClient.get).toHaveBeenCalledWith("/apis/content.halo.run/v1alpha1/tags");
    });
  });

  describe("getCategoryNames", () => {
    it("should return existing category names", async () => {
      const mockCategories: Category[] = [
        {
          metadata: { name: "cat-1" },
          spec: { displayName: "技术", slug: "tech", description: "", cover: "", template: "", priority: 0, children: [] },
        },
      ];

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockCategories });

      const result = await service.getCategoryNames(["技术"]);

      expect(result).toContain("cat-1");
    });

    it("should create new categories if not exist", async () => {
      const mockCategories: Category[] = [];
      const newCategory: Category = {
        metadata: { name: "new-cat" },
        spec: { displayName: "新分类", slug: "xin-fen-lei", description: "", cover: "", template: "", priority: 0, children: [] },
      };

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockCategories });
      vi.mocked(mockClient.post).mockResolvedValue(newCategory);

      const result = await service.getCategoryNames(["新分类"]);

      expect(result).toContain("new-cat");
      expect(mockClient.post).toHaveBeenCalled();
    });
  });

  describe("getTagNames", () => {
    it("should return existing tag names", async () => {
      const mockTags: Tag[] = [
        {
          metadata: { name: "tag-1" },
          spec: { displayName: "Python", slug: "python", color: "#3776AB", cover: "" },
        },
      ];

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockTags });

      const result = await service.getTagNames(["Python"]);

      expect(result).toContain("tag-1");
    });

    it("should create new tags if not exist", async () => {
      const mockTags: Tag[] = [];
      const newTag: Tag = {
        metadata: { name: "new-tag" },
        spec: { displayName: "新标签", slug: "xin-biao-qian", color: "#ffffff", cover: "" },
      };

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockTags });
      vi.mocked(mockClient.post).mockResolvedValue(newTag);

      const result = await service.getTagNames(["新标签"]);

      expect(result).toContain("new-tag");
      expect(mockClient.post).toHaveBeenCalled();
    });
  });

  describe("getCategoryDisplayNames", () => {
    it("should convert category names to display names", async () => {
      const mockCategories: Category[] = [
        {
          metadata: { name: "cat-1" },
          spec: { displayName: "技术", slug: "tech", description: "", cover: "", template: "", priority: 0, children: [] },
        },
      ];

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockCategories });

      const result = await service.getCategoryDisplayNames(["cat-1"]);

      expect(result).toEqual(["技术"]);
    });

    it("should return empty array for empty input", async () => {
      const result = await service.getCategoryDisplayNames([]);

      expect(result).toEqual([]);
    });

    it("should filter out unknown category names", async () => {
      const mockCategories: Category[] = [
        {
          metadata: { name: "cat-1" },
          spec: { displayName: "技术", slug: "tech", description: "", cover: "", template: "", priority: 0, children: [] },
        },
      ];

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockCategories });

      const result = await service.getCategoryDisplayNames(["cat-1", "unknown-cat"]);

      expect(result).toEqual(["技术"]);
    });
  });

  describe("getTagDisplayNames", () => {
    it("should convert tag names to display names", async () => {
      const mockTags: Tag[] = [
        {
          metadata: { name: "tag-1" },
          spec: { displayName: "Python", slug: "python", color: "#3776AB", cover: "" },
        },
      ];

      vi.mocked(mockClient.get).mockResolvedValue({ items: mockTags });

      const result = await service.getTagDisplayNames(["tag-1"]);

      expect(result).toEqual(["Python"]);
    });
  });

  describe("createCategory", () => {
    it("should create a new category", async () => {
      const newCategory: Category = {
        metadata: { name: "cat-created" },
        spec: { displayName: "新分类", slug: "xin-fen-lei", description: "", cover: "", template: "", priority: 0, children: [] },
      };

      vi.mocked(mockClient.post).mockResolvedValue(newCategory);

      const result = await service.createCategory("新分类", "xin-fen-lei", 0);

      expect(result).toEqual(newCategory);
      expect(mockClient.post).toHaveBeenCalledWith(
        "/apis/content.halo.run/v1alpha1/categories",
        expect.objectContaining({
          spec: expect.objectContaining({ displayName: "新分类", slug: "xin-fen-lei" }),
        }),
      );
    });
  });

  describe("createTag", () => {
    it("should create a new tag", async () => {
      const newTag: Tag = {
        metadata: { name: "tag-created" },
        spec: { displayName: "新标签", slug: "xin-biao-qian", color: "#ffffff", cover: "" },
      };

      vi.mocked(mockClient.post).mockResolvedValue(newTag);

      const result = await service.createTag("新标签", "xin-biao-qian", "#ffffff");

      expect(result).toEqual(newTag);
      expect(mockClient.post).toHaveBeenCalledWith(
        "/apis/content.halo.run/v1alpha1/tags",
        expect.objectContaining({
          spec: expect.objectContaining({ displayName: "新标签", slug: "xin-biao-qian", color: "#ffffff" }),
        }),
      );
    });
  });

  describe("deleteCategory", () => {
    it("should delete a category", async () => {
      vi.mocked(mockClient.delete).mockResolvedValue();

      await service.deleteCategory("cat-1");

      expect(mockClient.delete).toHaveBeenCalledWith("/apis/content.halo.run/v1alpha1/categories/cat-1");
    });
  });

  describe("deleteTag", () => {
    it("should delete a tag", async () => {
      vi.mocked(mockClient.delete).mockResolvedValue();

      await service.deleteTag("tag-1");

      expect(mockClient.delete).toHaveBeenCalledWith("/apis/content.halo.run/v1alpha1/tags/tag-1");
    });
  });
});