import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { ExecutionContext } from "@nestjs/common";
import { WorkspaceSettingsController } from "./workspace-settings.controller";
import { WorkspaceSettingsService } from "./workspace-settings.service";
import { AuthGuard } from "../auth/guards/auth.guard";
import type { WorkspaceLlmSettings, LlmProviderInstance, Personality } from "@prisma/client";
import type { AuthenticatedRequest } from "../common/types/user.types";

describe("WorkspaceSettingsController", () => {
  let controller: WorkspaceSettingsController;
  let service: WorkspaceSettingsService;

  const mockWorkspaceId = "workspace-123";
  const mockUserId = "user-123";

  const mockSettings: WorkspaceLlmSettings = {
    id: "settings-123",
    workspaceId: mockWorkspaceId,
    defaultLlmProviderId: "provider-123",
    defaultPersonalityId: "personality-123",
    settings: {},
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  const mockProvider: LlmProviderInstance = {
    id: "provider-123",
    providerType: "ollama",
    displayName: "Test Provider",
    userId: null,
    config: { endpoint: "http://localhost:11434" },
    isDefault: true,
    isEnabled: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  const mockPersonality: Personality = {
    id: "personality-123",
    workspaceId: mockWorkspaceId,
    name: "default",
    displayName: "Default",
    description: "Default personality",
    systemPrompt: "You are a helpful assistant",
    temperature: null,
    maxTokens: null,
    llmProviderInstanceId: null,
    isDefault: true,
    isEnabled: true,
    createdAt: new Date(),
    updatedAt: new Date(),
  };

  const mockAuthGuard = {
    canActivate: vi.fn((context: ExecutionContext) => {
      const request = context.switchToHttp().getRequest();
      request.user = {
        id: mockUserId,
        email: "test@example.com",
        name: "Test User",
        emailVerified: true,
        image: null,
        authProviderId: null,
        preferences: {},
        createdAt: new Date(),
        updatedAt: new Date(),
      };
      return true;
    }),
  };

  const mockAuthRequest: AuthenticatedRequest = {
    user: {
      id: mockUserId,
      email: "test@example.com",
      name: "Test User",
      emailVerified: true,
      image: null,
      authProviderId: null,
      preferences: {},
      createdAt: new Date(),
      updatedAt: new Date(),
    },
  } as AuthenticatedRequest;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [WorkspaceSettingsController],
      providers: [
        {
          provide: WorkspaceSettingsService,
          useValue: {
            getSettings: vi.fn(),
            updateSettings: vi.fn(),
            getEffectiveLlmProvider: vi.fn(),
            getEffectivePersonality: vi.fn(),
          },
        },
      ],
    })
      .overrideGuard(AuthGuard)
      .useValue(mockAuthGuard)
      .compile();

    controller = module.get<WorkspaceSettingsController>(WorkspaceSettingsController);
    service = module.get<WorkspaceSettingsService>(WorkspaceSettingsService);
  });

  afterEach(() => {
    vi.clearAllMocks();
  });

  describe("getSettings", () => {
    it("should return workspace settings", async () => {
      vi.spyOn(service, "getSettings").mockResolvedValue(mockSettings);

      const result = await controller.getSettings(mockWorkspaceId);

      expect(result).toEqual(mockSettings);
      expect(service.getSettings).toHaveBeenCalledWith(mockWorkspaceId);
    });

    it("should handle service errors", async () => {
      vi.spyOn(service, "getSettings").mockRejectedValue(new Error("Service error"));

      await expect(controller.getSettings(mockWorkspaceId)).rejects.toThrow("Service error");
    });

    it("should work with valid workspace ID", async () => {
      vi.spyOn(service, "getSettings").mockResolvedValue(mockSettings);

      const result = await controller.getSettings(mockWorkspaceId);

      expect(result.workspaceId).toBe(mockWorkspaceId);
    });
  });

  describe("updateSettings", () => {
    it("should update workspace settings", async () => {
      const updateDto = {
        defaultLlmProviderId: "new-provider-123",
        defaultPersonalityId: "new-personality-123",
      };

      const updatedSettings = { ...mockSettings, ...updateDto };
      vi.spyOn(service, "updateSettings").mockResolvedValue(updatedSettings);

      const result = await controller.updateSettings(mockWorkspaceId, updateDto);

      expect(result).toEqual(updatedSettings);
      expect(service.updateSettings).toHaveBeenCalledWith(mockWorkspaceId, updateDto);
    });

    it("should allow partial updates", async () => {
      const updateDto = {
        defaultLlmProviderId: "new-provider-123",
      };

      const updatedSettings = {
        ...mockSettings,
        defaultLlmProviderId: updateDto.defaultLlmProviderId,
      };
      vi.spyOn(service, "updateSettings").mockResolvedValue(updatedSettings);

      const result = await controller.updateSettings(mockWorkspaceId, updateDto);

      expect(result.defaultLlmProviderId).toBe(updateDto.defaultLlmProviderId);
    });

    it("should handle null values", async () => {
      const updateDto = {
        defaultLlmProviderId: null,
      };

      const updatedSettings = { ...mockSettings, defaultLlmProviderId: null };
      vi.spyOn(service, "updateSettings").mockResolvedValue(updatedSettings);

      const result = await controller.updateSettings(mockWorkspaceId, updateDto);

      expect(result.defaultLlmProviderId).toBeNull();
    });

    it("should handle service errors", async () => {
      const updateDto = { defaultLlmProviderId: "invalid-id" };
      vi.spyOn(service, "updateSettings").mockRejectedValue(new Error("Provider not found"));

      await expect(controller.updateSettings(mockWorkspaceId, updateDto)).rejects.toThrow(
        "Provider not found"
      );
    });
  });

  describe("getEffectiveProvider", () => {
    it("should return effective provider with authenticated user", async () => {
      vi.spyOn(service, "getEffectiveLlmProvider").mockResolvedValue(mockProvider);

      const result = await controller.getEffectiveProvider(mockWorkspaceId, mockAuthRequest);

      expect(result).toEqual(mockProvider);
      expect(service.getEffectiveLlmProvider).toHaveBeenCalledWith(mockWorkspaceId, mockUserId);
    });

    it("should return effective provider without user ID when not authenticated", async () => {
      const unauthRequest = { user: undefined } as AuthenticatedRequest;
      vi.spyOn(service, "getEffectiveLlmProvider").mockResolvedValue(mockProvider);

      const result = await controller.getEffectiveProvider(mockWorkspaceId, unauthRequest);

      expect(result).toEqual(mockProvider);
      expect(service.getEffectiveLlmProvider).toHaveBeenCalledWith(mockWorkspaceId, undefined);
    });

    it("should handle no provider available error", async () => {
      vi.spyOn(service, "getEffectiveLlmProvider").mockRejectedValue(
        new Error("No LLM provider available")
      );

      await expect(
        controller.getEffectiveProvider(mockWorkspaceId, mockAuthRequest)
      ).rejects.toThrow("No LLM provider available");
    });

    it("should pass user ID to service when available", async () => {
      vi.spyOn(service, "getEffectiveLlmProvider").mockResolvedValue(mockProvider);

      await controller.getEffectiveProvider(mockWorkspaceId, mockAuthRequest);

      expect(service.getEffectiveLlmProvider).toHaveBeenCalledWith(mockWorkspaceId, mockUserId);
    });
  });

  describe("getEffectivePersonality", () => {
    it("should return effective personality", async () => {
      vi.spyOn(service, "getEffectivePersonality").mockResolvedValue(mockPersonality);

      const result = await controller.getEffectivePersonality(mockWorkspaceId);

      expect(result).toEqual(mockPersonality);
      expect(service.getEffectivePersonality).toHaveBeenCalledWith(mockWorkspaceId);
    });

    it("should handle no personality available error", async () => {
      vi.spyOn(service, "getEffectivePersonality").mockRejectedValue(
        new Error("No personality available")
      );

      await expect(controller.getEffectivePersonality(mockWorkspaceId)).rejects.toThrow(
        "No personality available"
      );
    });

    it("should work with valid workspace ID", async () => {
      vi.spyOn(service, "getEffectivePersonality").mockResolvedValue(mockPersonality);

      const result = await controller.getEffectivePersonality(mockWorkspaceId);

      expect(result.workspaceId).toBe(mockWorkspaceId);
    });
  });

  describe("endpoint paths", () => {
    it("should be accessible at /workspaces/:workspaceId/settings/llm", () => {
      const metadata = Reflect.getMetadata("path", WorkspaceSettingsController);
      expect(metadata).toBe("workspaces/:workspaceId/settings/llm");
    });
  });
});
