import { describe, it, expect, beforeEach, vi } from "vitest";
import { UnauthorizedException } from "@nestjs/common";
import { PreferencesController } from "./preferences.controller";
import { PreferencesService } from "./preferences.service";
import type { UpdatePreferencesDto, PreferencesResponseDto } from "./dto";
import type { AuthenticatedRequest } from "../common/types/user.types";

describe("PreferencesController", () => {
  let controller: PreferencesController;
  let service: PreferencesService;

  const mockPreferencesService = {
    getPreferences: vi.fn(),
    updatePreferences: vi.fn(),
  };

  const mockUserId = "user-uuid-123";

  const mockPreferencesResponse: PreferencesResponseDto = {
    id: "pref-uuid-456",
    userId: mockUserId,
    theme: "system",
    locale: "en",
    timezone: null,
    settings: {},
    updatedAt: new Date("2026-01-01T00:00:00Z"),
  };

  function makeRequest(userId?: string): AuthenticatedRequest {
    return {
      user: userId ? { id: userId } : undefined,
    } as unknown as AuthenticatedRequest;
  }

  beforeEach(() => {
    service = mockPreferencesService as unknown as PreferencesService;
    controller = new PreferencesController(service);
    vi.clearAllMocks();
  });

  describe("GET /api/users/me/preferences", () => {
    it("should return preferences for authenticated user", async () => {
      mockPreferencesService.getPreferences.mockResolvedValue(mockPreferencesResponse);

      const result = await controller.getPreferences(makeRequest(mockUserId));

      expect(result).toEqual(mockPreferencesResponse);
      expect(mockPreferencesService.getPreferences).toHaveBeenCalledWith(mockUserId);
    });

    it("should throw UnauthorizedException when user is not authenticated", async () => {
      await expect(controller.getPreferences(makeRequest())).rejects.toThrow(UnauthorizedException);
      expect(mockPreferencesService.getPreferences).not.toHaveBeenCalled();
    });
  });

  describe("PUT /api/users/me/preferences", () => {
    const updateDto: UpdatePreferencesDto = {
      theme: "dark",
      locale: "fr",
      timezone: "Europe/Paris",
    };

    it("should update and return preferences for authenticated user", async () => {
      const updatedResponse: PreferencesResponseDto = {
        ...mockPreferencesResponse,
        theme: "dark",
        locale: "fr",
        timezone: "Europe/Paris",
      };
      mockPreferencesService.updatePreferences.mockResolvedValue(updatedResponse);

      const result = await controller.updatePreferences(updateDto, makeRequest(mockUserId));

      expect(result).toEqual(updatedResponse);
      expect(mockPreferencesService.updatePreferences).toHaveBeenCalledWith(mockUserId, updateDto);
    });

    it("should throw UnauthorizedException when user is not authenticated", async () => {
      await expect(controller.updatePreferences(updateDto, makeRequest())).rejects.toThrow(
        UnauthorizedException
      );
      expect(mockPreferencesService.updatePreferences).not.toHaveBeenCalled();
    });
  });

  describe("PATCH /api/users/me/preferences", () => {
    const patchDto: UpdatePreferencesDto = {
      theme: "light",
    };

    it("should partially update and return preferences for authenticated user", async () => {
      const patchedResponse: PreferencesResponseDto = {
        ...mockPreferencesResponse,
        theme: "light",
      };
      mockPreferencesService.updatePreferences.mockResolvedValue(patchedResponse);

      const result = await controller.patchPreferences(patchDto, makeRequest(mockUserId));

      expect(result).toEqual(patchedResponse);
      expect(mockPreferencesService.updatePreferences).toHaveBeenCalledWith(mockUserId, patchDto);
    });

    it("should throw UnauthorizedException when user is not authenticated", async () => {
      await expect(controller.patchPreferences(patchDto, makeRequest())).rejects.toThrow(
        UnauthorizedException
      );
      expect(mockPreferencesService.updatePreferences).not.toHaveBeenCalled();
    });
  });
});
