import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { NotFoundException, BadRequestException } from "@nestjs/common";
import { LlmProviderAdminController } from "./llm-provider-admin.controller";
import { LlmManagerService } from "./llm-manager.service";
import { PrismaService } from "../prisma/prisma.service";
import { CreateLlmProviderDto, UpdateLlmProviderDto } from "./dto";

describe("LlmProviderAdminController", () => {
  let controller: LlmProviderAdminController;
  let prisma: PrismaService;
  let llmManager: LlmManagerService;

  const mockProviderId = "provider-123";
  const mockUserId = "user-123";

  const mockOllamaProvider = {
    id: mockProviderId,
    providerType: "ollama",
    displayName: "Local Ollama",
    userId: null,
    config: {
      endpoint: "http://localhost:11434",
      timeout: 30000,
    },
    isDefault: true,
    isEnabled: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  const mockOpenAiProvider = {
    id: "provider-456",
    providerType: "openai",
    displayName: "OpenAI GPT-4",
    userId: null,
    config: {
      endpoint: "https://api.openai.com/v1",
      apiKey: "sk-test-key",
      timeout: 30000,
    },
    isDefault: false,
    isEnabled: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  const mockPrismaService = {
    llmProviderInstance: {
      findMany: vi.fn(),
      findUnique: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      delete: vi.fn(),
    },
  };

  const mockLlmManagerService = {
    registerProvider: vi.fn(),
    unregisterProvider: vi.fn(),
    getProviderById: vi.fn(),
    reloadFromDatabase: vi.fn(),
  };

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

    controller = module.get<LlmProviderAdminController>(LlmProviderAdminController);
    prisma = module.get<PrismaService>(PrismaService);
    llmManager = module.get<LlmManagerService>(LlmManagerService);

    // Reset mocks
    vi.clearAllMocks();
  });

  describe("listProviders", () => {
    it("should return all providers from database", async () => {
      const mockProviders = [mockOllamaProvider, mockOpenAiProvider];
      mockPrismaService.llmProviderInstance.findMany.mockResolvedValue(mockProviders);

      const result = await controller.listProviders();

      expect(result).toEqual(mockProviders);
      expect(prisma.llmProviderInstance.findMany).toHaveBeenCalledWith({
        orderBy: { createdAt: "asc" },
      });
    });

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

      const result = await controller.listProviders();

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

  describe("getProvider", () => {
    it("should return a provider by id", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(mockOllamaProvider);

      const result = await controller.getProvider(mockProviderId);

      expect(result).toEqual(mockOllamaProvider);
      expect(prisma.llmProviderInstance.findUnique).toHaveBeenCalledWith({
        where: { id: mockProviderId },
      });
    });

    it("should throw NotFoundException when provider not found", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(null);

      await expect(controller.getProvider("nonexistent")).rejects.toThrow(NotFoundException);
      await expect(controller.getProvider("nonexistent")).rejects.toThrow(
        "LLM provider with ID nonexistent not found"
      );
    });
  });

  describe("createProvider", () => {
    it("should create an ollama provider", async () => {
      const createDto: CreateLlmProviderDto = {
        providerType: "ollama",
        displayName: "Local Ollama",
        config: {
          endpoint: "http://localhost:11434",
          timeout: 30000,
        },
        isDefault: true,
        isEnabled: true,
      };

      mockPrismaService.llmProviderInstance.create.mockResolvedValue(mockOllamaProvider);
      mockLlmManagerService.registerProvider.mockResolvedValue(undefined);

      const result = await controller.createProvider(createDto);

      expect(result).toEqual(mockOllamaProvider);
      expect(prisma.llmProviderInstance.create).toHaveBeenCalledWith({
        data: {
          providerType: "ollama",
          displayName: "Local Ollama",
          userId: null,
          config: {
            endpoint: "http://localhost:11434",
            timeout: 30000,
          },
          isDefault: true,
          isEnabled: true,
        },
      });
      expect(llmManager.registerProvider).toHaveBeenCalledWith(mockOllamaProvider);
    });

    it("should create an openai provider", async () => {
      const createDto: CreateLlmProviderDto = {
        providerType: "openai",
        displayName: "OpenAI GPT-4",
        config: {
          endpoint: "https://api.openai.com/v1",
          apiKey: "sk-test-key",
          timeout: 30000,
        },
        isDefault: false,
        isEnabled: true,
      };

      mockPrismaService.llmProviderInstance.create.mockResolvedValue(mockOpenAiProvider);
      mockLlmManagerService.registerProvider.mockResolvedValue(undefined);

      const result = await controller.createProvider(createDto);

      expect(result).toEqual(mockOpenAiProvider);
      expect(prisma.llmProviderInstance.create).toHaveBeenCalledWith({
        data: {
          providerType: "openai",
          displayName: "OpenAI GPT-4",
          userId: null,
          config: {
            endpoint: "https://api.openai.com/v1",
            apiKey: "sk-test-key",
            timeout: 30000,
          },
          isDefault: false,
          isEnabled: true,
        },
      });
      expect(llmManager.registerProvider).toHaveBeenCalledWith(mockOpenAiProvider);
    });

    it("should create a user-specific provider", async () => {
      const createDto: CreateLlmProviderDto = {
        providerType: "ollama",
        displayName: "User Ollama",
        userId: mockUserId,
        config: {
          endpoint: "http://localhost:11434",
        },
        isDefault: false,
        isEnabled: true,
      };

      const userProvider = {
        ...mockOllamaProvider,
        userId: mockUserId,
        displayName: "User Ollama",
        isDefault: false,
      };

      mockPrismaService.llmProviderInstance.create.mockResolvedValue(userProvider);
      mockLlmManagerService.registerProvider.mockResolvedValue(undefined);

      const result = await controller.createProvider(createDto);

      expect(result).toEqual(userProvider);
      expect(prisma.llmProviderInstance.create).toHaveBeenCalledWith({
        data: {
          providerType: "ollama",
          displayName: "User Ollama",
          userId: mockUserId,
          config: {
            endpoint: "http://localhost:11434",
          },
          isDefault: false,
          isEnabled: true,
        },
      });
    });

    it("should handle registration failure gracefully", async () => {
      const createDto: CreateLlmProviderDto = {
        providerType: "ollama",
        displayName: "Local Ollama",
        config: {
          endpoint: "http://localhost:11434",
        },
      };

      mockPrismaService.llmProviderInstance.create.mockResolvedValue(mockOllamaProvider);
      mockLlmManagerService.registerProvider.mockRejectedValue(
        new Error("Provider initialization failed")
      );

      await expect(controller.createProvider(createDto)).rejects.toThrow(
        "Provider initialization failed"
      );
    });
  });

  describe("updateProvider", () => {
    it("should update provider settings", async () => {
      const updateDto: UpdateLlmProviderDto = {
        displayName: "Updated Ollama",
        isEnabled: false,
      };

      const updatedProvider = {
        ...mockOllamaProvider,
        ...updateDto,
      };

      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(mockOllamaProvider);
      mockPrismaService.llmProviderInstance.update.mockResolvedValue(updatedProvider);
      mockLlmManagerService.unregisterProvider.mockResolvedValue(undefined);

      const result = await controller.updateProvider(mockProviderId, updateDto);

      expect(result).toEqual(updatedProvider);
      expect(prisma.llmProviderInstance.update).toHaveBeenCalledWith({
        where: { id: mockProviderId },
        data: updateDto,
      });
      expect(llmManager.unregisterProvider).toHaveBeenCalledWith(mockProviderId);
    });

    it("should re-register provider when updated and still enabled", async () => {
      const updateDto: UpdateLlmProviderDto = {
        displayName: "Updated Ollama",
        config: {
          endpoint: "http://new-endpoint:11434",
        },
      };

      const updatedProvider = {
        ...mockOllamaProvider,
        ...updateDto,
      };

      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(mockOllamaProvider);
      mockPrismaService.llmProviderInstance.update.mockResolvedValue(updatedProvider);
      mockLlmManagerService.unregisterProvider.mockResolvedValue(undefined);
      mockLlmManagerService.registerProvider.mockResolvedValue(undefined);

      const result = await controller.updateProvider(mockProviderId, updateDto);

      expect(result).toEqual(updatedProvider);
      expect(llmManager.unregisterProvider).toHaveBeenCalledWith(mockProviderId);
      expect(llmManager.registerProvider).toHaveBeenCalledWith(updatedProvider);
    });

    it("should throw NotFoundException when provider not found", async () => {
      const updateDto: UpdateLlmProviderDto = {
        displayName: "Updated",
      };

      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(null);

      await expect(controller.updateProvider("nonexistent", updateDto)).rejects.toThrow(
        NotFoundException
      );
    });
  });

  describe("deleteProvider", () => {
    it("should delete a non-default provider", async () => {
      const nonDefaultProvider = {
        ...mockOllamaProvider,
        isDefault: false,
      };

      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(nonDefaultProvider);
      mockPrismaService.llmProviderInstance.delete.mockResolvedValue(nonDefaultProvider);
      mockLlmManagerService.unregisterProvider.mockResolvedValue(undefined);

      await controller.deleteProvider(mockProviderId);

      expect(prisma.llmProviderInstance.delete).toHaveBeenCalledWith({
        where: { id: mockProviderId },
      });
      expect(llmManager.unregisterProvider).toHaveBeenCalledWith(mockProviderId);
    });

    it("should throw NotFoundException when provider not found", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(null);

      await expect(controller.deleteProvider("nonexistent")).rejects.toThrow(NotFoundException);
    });

    it("should prevent deleting the default provider", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(mockOllamaProvider);

      await expect(controller.deleteProvider(mockProviderId)).rejects.toThrow(BadRequestException);
      await expect(controller.deleteProvider(mockProviderId)).rejects.toThrow(
        "Cannot delete the default provider. Set another provider as default first."
      );
    });
  });

  describe("testProvider", () => {
    it("should return healthy status when provider is healthy", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(mockOllamaProvider);
      mockLlmManagerService.getProviderById.mockResolvedValue({
        checkHealth: vi.fn().mockResolvedValue({
          healthy: true,
          provider: "ollama",
          endpoint: "http://localhost:11434",
        }),
      });

      const result = await controller.testProvider(mockProviderId);

      expect(result).toEqual({ healthy: true });
      expect(llmManager.getProviderById).toHaveBeenCalledWith(mockProviderId);
    });

    it("should return unhealthy status with error message when provider fails", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(mockOllamaProvider);
      mockLlmManagerService.getProviderById.mockResolvedValue({
        checkHealth: vi.fn().mockResolvedValue({
          healthy: false,
          provider: "ollama",
          error: "Connection refused",
        }),
      });

      const result = await controller.testProvider(mockProviderId);

      expect(result).toEqual({
        healthy: false,
        error: "Connection refused",
      });
    });

    it("should throw NotFoundException when provider not found", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(null);

      await expect(controller.testProvider("nonexistent")).rejects.toThrow(NotFoundException);
    });

    it("should handle provider not loaded in manager", async () => {
      mockPrismaService.llmProviderInstance.findUnique.mockResolvedValue(mockOllamaProvider);
      mockLlmManagerService.getProviderById.mockRejectedValue(
        new Error("Provider with ID provider-123 not found")
      );

      const result = await controller.testProvider(mockProviderId);

      expect(result).toEqual({
        healthy: false,
        error: "Provider not loaded in manager. Try reloading providers.",
      });
    });
  });

  describe("reloadProviders", () => {
    it("should reload all providers from database", async () => {
      const mockProviders = [mockOllamaProvider, mockOpenAiProvider];
      mockPrismaService.llmProviderInstance.findMany.mockResolvedValue(mockProviders);
      mockLlmManagerService.reloadFromDatabase.mockResolvedValue(undefined);

      const result = await controller.reloadProviders();

      expect(result).toEqual({
        message: "Providers reloaded successfully",
        count: 2,
      });
      expect(llmManager.reloadFromDatabase).toHaveBeenCalled();
      expect(prisma.llmProviderInstance.findMany).toHaveBeenCalledWith({
        where: { isEnabled: true },
      });
    });

    it("should handle reload with no enabled providers", async () => {
      mockPrismaService.llmProviderInstance.findMany.mockResolvedValue([]);
      mockLlmManagerService.reloadFromDatabase.mockResolvedValue(undefined);

      const result = await controller.reloadProviders();

      expect(result).toEqual({
        message: "Providers reloaded successfully",
        count: 0,
      });
    });
  });
});
