import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { BadRequestException } from "@nestjs/common";
import { ImportExportService } from "./import-export.service";
import { KnowledgeService } from "../knowledge.service";
import { PrismaService } from "../../prisma/prisma.service";
import { ExportFormat } from "../dto";
import { EntryStatus, Visibility } from "@prisma/client";

describe("ImportExportService", () => {
  let service: ImportExportService;
  let knowledgeService: KnowledgeService;
  let prisma: PrismaService;

  const workspaceId = "workspace-123";
  const userId = "user-123";

  const mockEntry = {
    id: "entry-123",
    workspaceId,
    slug: "test-entry",
    title: "Test Entry",
    content: "Test content",
    summary: "Test summary",
    status: EntryStatus.PUBLISHED,
    visibility: Visibility.WORKSPACE,
    createdAt: new Date(),
    updatedAt: new Date(),
    tags: [
      {
        tag: {
          id: "tag-1",
          name: "TypeScript",
          slug: "typescript",
          color: "#3178c6",
        },
      },
    ],
  };

  const mockKnowledgeService = {
    create: vi.fn(),
    findAll: vi.fn(),
  };

  const mockPrismaService = {
    knowledgeEntry: {
      findMany: vi.fn(),
    },
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ImportExportService,
        {
          provide: KnowledgeService,
          useValue: mockKnowledgeService,
        },
        {
          provide: PrismaService,
          useValue: mockPrismaService,
        },
      ],
    }).compile();

    service = module.get<ImportExportService>(ImportExportService);
    knowledgeService = module.get<KnowledgeService>(KnowledgeService);
    prisma = module.get<PrismaService>(PrismaService);

    vi.clearAllMocks();
  });

  describe("importEntries", () => {
    it("should import a single markdown file successfully", async () => {
      const markdown = `---
title: Test Entry
status: PUBLISHED
tags:
  - TypeScript
  - Testing
---

This is the content of the entry.`;

      const file: Express.Multer.File = {
        fieldname: "file",
        originalname: "test.md",
        encoding: "utf-8",
        mimetype: "text/markdown",
        size: markdown.length,
        buffer: Buffer.from(markdown),
        stream: null as any,
        destination: "",
        filename: "",
        path: "",
      };

      mockKnowledgeService.create.mockResolvedValue({
        id: "entry-123",
        slug: "test-entry",
        title: "Test Entry",
      });

      const result = await service.importEntries(workspaceId, userId, file);

      expect(result.totalFiles).toBe(1);
      expect(result.imported).toBe(1);
      expect(result.failed).toBe(0);
      expect(result.results[0].success).toBe(true);
      expect(result.results[0].title).toBe("Test Entry");
      expect(mockKnowledgeService.create).toHaveBeenCalledWith(
        workspaceId,
        userId,
        expect.objectContaining({
          title: "Test Entry",
          content: "This is the content of the entry.",
          status: EntryStatus.PUBLISHED,
          tags: ["TypeScript", "Testing"],
        })
      );
    });

    it("should use filename as title if frontmatter title is missing", async () => {
      const markdown = `This is content without frontmatter.`;

      const file: Express.Multer.File = {
        fieldname: "file",
        originalname: "my-entry.md",
        encoding: "utf-8",
        mimetype: "text/markdown",
        size: markdown.length,
        buffer: Buffer.from(markdown),
        stream: null as any,
        destination: "",
        filename: "",
        path: "",
      };

      mockKnowledgeService.create.mockResolvedValue({
        id: "entry-123",
        slug: "my-entry",
        title: "my-entry",
      });

      const result = await service.importEntries(workspaceId, userId, file);

      expect(result.imported).toBe(1);
      expect(mockKnowledgeService.create).toHaveBeenCalledWith(
        workspaceId,
        userId,
        expect.objectContaining({
          title: "my-entry",
          content: "This is content without frontmatter.",
        })
      );
    });

    it("should reject invalid file types", async () => {
      const file: Express.Multer.File = {
        fieldname: "file",
        originalname: "test.txt",
        encoding: "utf-8",
        mimetype: "text/plain",
        size: 100,
        buffer: Buffer.from("test"),
        stream: null as any,
        destination: "",
        filename: "",
        path: "",
      };

      await expect(service.importEntries(workspaceId, userId, file)).rejects.toThrow(
        BadRequestException
      );
    });

    it("should handle import errors gracefully", async () => {
      const markdown = `---
title: Test Entry
---

Content`;

      const file: Express.Multer.File = {
        fieldname: "file",
        originalname: "test.md",
        encoding: "utf-8",
        mimetype: "text/markdown",
        size: markdown.length,
        buffer: Buffer.from(markdown),
        stream: null as any,
        destination: "",
        filename: "",
        path: "",
      };

      mockKnowledgeService.create.mockRejectedValue(new Error("Database error"));

      const result = await service.importEntries(workspaceId, userId, file);

      expect(result.totalFiles).toBe(1);
      expect(result.imported).toBe(0);
      expect(result.failed).toBe(1);
      expect(result.results[0].success).toBe(false);
      expect(result.results[0].error).toBe("Database error");
    });

    it("should reject empty markdown content", async () => {
      const markdown = `---
title: Empty Entry
---

`;

      const file: Express.Multer.File = {
        fieldname: "file",
        originalname: "empty.md",
        encoding: "utf-8",
        mimetype: "text/markdown",
        size: markdown.length,
        buffer: Buffer.from(markdown),
        stream: null as any,
        destination: "",
        filename: "",
        path: "",
      };

      const result = await service.importEntries(workspaceId, userId, file);

      expect(result.imported).toBe(0);
      expect(result.failed).toBe(1);
      expect(result.results[0].error).toBe("Empty content");
    });
  });

  describe("exportEntries", () => {
    it("should export entries as markdown format", async () => {
      mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([mockEntry]);

      const result = await service.exportEntries(workspaceId, ExportFormat.MARKDOWN);

      expect(result.filename).toMatch(/knowledge-export-\d{4}-\d{2}-\d{2}\.zip/);
      expect(result.stream).toBeDefined();
      expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith({
        where: { workspaceId },
        include: {
          tags: {
            include: {
              tag: true,
            },
          },
        },
        orderBy: {
          title: "asc",
        },
      });
    });

    it("should export only specified entries", async () => {
      const entryIds = ["entry-123", "entry-456"];
      mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([mockEntry]);

      await service.exportEntries(workspaceId, ExportFormat.JSON, entryIds);

      expect(mockPrismaService.knowledgeEntry.findMany).toHaveBeenCalledWith({
        where: {
          workspaceId,
          id: { in: entryIds },
        },
        include: {
          tags: {
            include: {
              tag: true,
            },
          },
        },
        orderBy: {
          title: "asc",
        },
      });
    });

    it("should throw error when no entries found", async () => {
      mockPrismaService.knowledgeEntry.findMany.mockResolvedValue([]);

      await expect(service.exportEntries(workspaceId, ExportFormat.MARKDOWN)).rejects.toThrow(
        BadRequestException
      );
    });
  });
});
