import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { QualityGateConfigService } from "./quality-gate-config.service";
import { PrismaService } from "../prisma/prisma.service";
import { NotFoundException, ConflictException } from "@nestjs/common";

describe("QualityGateConfigService", () => {
  let service: QualityGateConfigService;
  let prisma: PrismaService;

  const mockPrismaService = {
    qualityGate: {
      create: vi.fn(),
      findMany: vi.fn(),
      findUnique: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
      count: vi.fn(),
    },
    $transaction: vi.fn(),
  };

  const mockWorkspaceId = "550e8400-e29b-41d4-a716-446655440001";
  const mockGateId = "550e8400-e29b-41d4-a716-446655440002";

  const mockQualityGate = {
    id: mockGateId,
    workspaceId: mockWorkspaceId,
    name: "Build Check",
    description: "Verify code compiles without errors",
    type: "build",
    command: "pnpm build",
    expectedOutput: null,
    isRegex: false,
    required: true,
    order: 1,
    isEnabled: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

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

    service = module.get<QualityGateConfigService>(QualityGateConfigService);
    prisma = module.get<PrismaService>(PrismaService);

    vi.clearAllMocks();
  });

  it("should be defined", () => {
    expect(service).toBeDefined();
  });

  describe("create", () => {
    it("should create a quality gate successfully", async () => {
      const createDto = {
        name: "Build Check",
        description: "Verify code compiles without errors",
        type: "build" as const,
        command: "pnpm build",
        required: true,
        order: 1,
      };

      mockPrismaService.qualityGate.create.mockResolvedValue(mockQualityGate);

      const result = await service.create(mockWorkspaceId, createDto);

      expect(result).toEqual(mockQualityGate);
      expect(prisma.qualityGate.create).toHaveBeenCalledWith({
        data: {
          workspaceId: mockWorkspaceId,
          name: createDto.name,
          description: createDto.description,
          type: createDto.type,
          command: createDto.command,
          expectedOutput: null,
          isRegex: false,
          required: true,
          order: 1,
          isEnabled: true,
        },
      });
    });

    it("should use default values when optional fields are not provided", async () => {
      const createDto = {
        name: "Test Gate",
        type: "test" as const,
      };

      mockPrismaService.qualityGate.create.mockResolvedValue({
        ...mockQualityGate,
        name: createDto.name,
        type: createDto.type,
      });

      await service.create(mockWorkspaceId, createDto);

      expect(prisma.qualityGate.create).toHaveBeenCalledWith({
        data: {
          workspaceId: mockWorkspaceId,
          name: createDto.name,
          description: null,
          type: createDto.type,
          command: null,
          expectedOutput: null,
          isRegex: false,
          required: true,
          order: 0,
          isEnabled: true,
        },
      });
    });
  });

  describe("findAll", () => {
    it("should return all quality gates for a workspace", async () => {
      const mockGates = [mockQualityGate];
      mockPrismaService.qualityGate.findMany.mockResolvedValue(mockGates);

      const result = await service.findAll(mockWorkspaceId);

      expect(result).toEqual(mockGates);
      expect(prisma.qualityGate.findMany).toHaveBeenCalledWith({
        where: { workspaceId: mockWorkspaceId },
        orderBy: { order: "asc" },
      });
    });

    it("should return empty array when no gates exist", async () => {
      mockPrismaService.qualityGate.findMany.mockResolvedValue([]);

      const result = await service.findAll(mockWorkspaceId);

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

  describe("findEnabled", () => {
    it("should return only enabled quality gates ordered by priority", async () => {
      const mockGates = [mockQualityGate];
      mockPrismaService.qualityGate.findMany.mockResolvedValue(mockGates);

      const result = await service.findEnabled(mockWorkspaceId);

      expect(result).toEqual(mockGates);
      expect(prisma.qualityGate.findMany).toHaveBeenCalledWith({
        where: {
          workspaceId: mockWorkspaceId,
          isEnabled: true,
        },
        orderBy: { order: "asc" },
      });
    });
  });

  describe("findOne", () => {
    it("should return a quality gate by id", async () => {
      mockPrismaService.qualityGate.findUnique.mockResolvedValue(mockQualityGate);

      const result = await service.findOne(mockWorkspaceId, mockGateId);

      expect(result).toEqual(mockQualityGate);
      expect(prisma.qualityGate.findUnique).toHaveBeenCalledWith({
        where: {
          id: mockGateId,
          workspaceId: mockWorkspaceId,
        },
      });
    });

    it("should throw NotFoundException when gate does not exist", async () => {
      mockPrismaService.qualityGate.findUnique.mockResolvedValue(null);

      await expect(service.findOne(mockWorkspaceId, mockGateId)).rejects.toThrow(NotFoundException);
    });
  });

  describe("update", () => {
    it("should update a quality gate successfully", async () => {
      const updateDto = {
        name: "Updated Build Check",
        required: false,
      };

      const updatedGate = {
        ...mockQualityGate,
        ...updateDto,
      };

      mockPrismaService.qualityGate.findUnique.mockResolvedValue(mockQualityGate);
      mockPrismaService.qualityGate.update.mockResolvedValue(updatedGate);

      const result = await service.update(mockWorkspaceId, mockGateId, updateDto);

      expect(result).toEqual(updatedGate);
      expect(prisma.qualityGate.update).toHaveBeenCalledWith({
        where: { id: mockGateId },
        data: updateDto,
      });
    });

    it("should throw NotFoundException when gate does not exist", async () => {
      const updateDto = { name: "Updated" };
      mockPrismaService.qualityGate.findUnique.mockResolvedValue(null);

      await expect(service.update(mockWorkspaceId, mockGateId, updateDto)).rejects.toThrow(
        NotFoundException
      );
    });
  });

  describe("delete", () => {
    it("should delete a quality gate successfully", async () => {
      mockPrismaService.qualityGate.findUnique.mockResolvedValue(mockQualityGate);
      mockPrismaService.qualityGate.delete.mockResolvedValue(mockQualityGate);

      await service.delete(mockWorkspaceId, mockGateId);

      expect(prisma.qualityGate.delete).toHaveBeenCalledWith({
        where: { id: mockGateId },
      });
    });

    it("should throw NotFoundException when gate does not exist", async () => {
      mockPrismaService.qualityGate.findUnique.mockResolvedValue(null);

      await expect(service.delete(mockWorkspaceId, mockGateId)).rejects.toThrow(NotFoundException);
    });
  });

  describe("reorder", () => {
    it("should reorder gates successfully", async () => {
      const gateIds = ["gate1", "gate2", "gate3"];
      const mockGates = gateIds.map((id, index) => ({
        ...mockQualityGate,
        id,
        order: index,
      }));

      mockPrismaService.$transaction.mockImplementation((callback) => {
        return callback(mockPrismaService);
      });

      mockPrismaService.qualityGate.update.mockResolvedValue(mockGates[0]);
      mockPrismaService.qualityGate.findMany.mockResolvedValue(mockGates);

      const result = await service.reorder(mockWorkspaceId, gateIds);

      expect(result).toEqual(mockGates);
      expect(prisma.$transaction).toHaveBeenCalled();
    });
  });

  describe("seedDefaults", () => {
    it("should seed default quality gates for a workspace", async () => {
      const mockDefaultGates = [
        {
          id: "1",
          workspaceId: mockWorkspaceId,
          name: "Build Check",
          description: null,
          type: "build",
          command: "pnpm build",
          expectedOutput: null,
          isRegex: false,
          required: true,
          order: 1,
          isEnabled: true,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
        {
          id: "2",
          workspaceId: mockWorkspaceId,
          name: "Lint Check",
          description: null,
          type: "lint",
          command: "pnpm lint",
          expectedOutput: null,
          isRegex: false,
          required: true,
          order: 2,
          isEnabled: true,
          createdAt: new Date(),
          updatedAt: new Date(),
        },
      ];

      mockPrismaService.$transaction.mockImplementation((callback) => {
        return callback(mockPrismaService);
      });

      mockPrismaService.qualityGate.create.mockImplementation((args) =>
        Promise.resolve({
          ...mockDefaultGates[0],
          ...args.data,
        })
      );

      mockPrismaService.qualityGate.findMany.mockResolvedValue(mockDefaultGates);

      const result = await service.seedDefaults(mockWorkspaceId);

      expect(result.length).toBeGreaterThan(0);
      expect(prisma.$transaction).toHaveBeenCalled();
    });
  });

  describe("toOrchestratorFormat", () => {
    it("should convert database gates to orchestrator format", () => {
      const gates = [mockQualityGate];

      const result = service.toOrchestratorFormat(gates);

      expect(result).toHaveLength(1);
      expect(result[0]).toMatchObject({
        id: mockQualityGate.id,
        name: mockQualityGate.name,
        description: mockQualityGate.description,
        type: mockQualityGate.type,
        command: mockQualityGate.command,
        required: mockQualityGate.required,
        order: mockQualityGate.order,
      });
    });

    it("should handle regex patterns correctly", () => {
      const gateWithRegex = {
        ...mockQualityGate,
        expectedOutput: "Coverage: (\\d+)%",
        isRegex: true,
      };

      const result = service.toOrchestratorFormat([gateWithRegex]);

      expect(result[0]?.expectedOutput).toBeInstanceOf(RegExp);
    });

    it("should handle string patterns correctly", () => {
      const gateWithString = {
        ...mockQualityGate,
        expectedOutput: "All tests passed",
        isRegex: false,
      };

      const result = service.toOrchestratorFormat([gateWithString]);

      expect(typeof result[0]?.expectedOutput).toBe("string");
    });
  });
});
