import { describe, it, expect, beforeEach, vi } from "vitest";
import { Test, TestingModule } from "@nestjs/testing";
import { WorkspacesController } from "./workspaces.controller";
import { WorkspacesService } from "./workspaces.service";
import { AuthGuard } from "../auth/guards/auth.guard";
import { WorkspaceGuard, PermissionGuard } from "../common/guards";
import { WorkspaceMemberRole } from "@prisma/client";
import type { AuthUser } from "@mosaic/shared";

describe("WorkspacesController", () => {
  let controller: WorkspacesController;
  let service: WorkspacesService;

  const mockWorkspacesService = {
    getUserWorkspaces: vi.fn(),
    addMember: vi.fn(),
    updateMemberRole: vi.fn(),
    removeMember: vi.fn(),
  };

  const mockUser: AuthUser = {
    id: "user-1",
    email: "test@example.com",
    name: "Test User",
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [WorkspacesController],
      providers: [
        {
          provide: WorkspacesService,
          useValue: mockWorkspacesService,
        },
      ],
    })
      .overrideGuard(AuthGuard)
      .useValue({ canActivate: () => true })
      .overrideGuard(WorkspaceGuard)
      .useValue({ canActivate: () => true })
      .overrideGuard(PermissionGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get<WorkspacesController>(WorkspacesController);
    service = module.get<WorkspacesService>(WorkspacesService);

    vi.clearAllMocks();
  });

  describe("GET /api/workspaces", () => {
    it("should call service with authenticated user id", async () => {
      mockWorkspacesService.getUserWorkspaces.mockResolvedValueOnce([]);

      await controller.getUserWorkspaces(mockUser);

      expect(service.getUserWorkspaces).toHaveBeenCalledWith("user-1");
    });

    it("should return workspace list from service", async () => {
      const mockWorkspaces = [
        {
          id: "ws-1",
          name: "My Workspace",
          ownerId: "user-1",
          role: WorkspaceMemberRole.OWNER,
          createdAt: new Date("2026-01-01"),
        },
      ];
      mockWorkspacesService.getUserWorkspaces.mockResolvedValueOnce(mockWorkspaces);

      const result = await controller.getUserWorkspaces(mockUser);

      expect(result).toEqual(mockWorkspaces);
    });

    it("should propagate service errors", async () => {
      mockWorkspacesService.getUserWorkspaces.mockRejectedValueOnce(new Error("Database error"));

      await expect(controller.getUserWorkspaces(mockUser)).rejects.toThrow("Database error");
    });
  });

  describe("POST /api/workspaces/:id/members", () => {
    it("should call service with workspace id, actor id, and add member dto", async () => {
      const workspaceId = "ws-1";
      const addMemberDto = {
        userId: "user-2",
        role: WorkspaceMemberRole.MEMBER,
      };
      const mockMember = {
        workspaceId,
        userId: "user-2",
        role: WorkspaceMemberRole.MEMBER,
        joinedAt: new Date("2026-02-01"),
      };
      mockWorkspacesService.addMember.mockResolvedValueOnce(mockMember);

      const result = await controller.addMember(workspaceId, addMemberDto, mockUser);

      expect(result).toEqual(mockMember);
      expect(service.addMember).toHaveBeenCalledWith(workspaceId, mockUser.id, addMemberDto);
    });
  });

  describe("PATCH /api/workspaces/:id/members/:userId", () => {
    it("should call service with workspace id, actor id, target user id, and role dto", async () => {
      const workspaceId = "ws-1";
      const targetUserId = "user-2";
      const updateRoleDto = {
        role: WorkspaceMemberRole.ADMIN,
      };
      const mockMember = {
        workspaceId,
        userId: targetUserId,
        role: WorkspaceMemberRole.ADMIN,
        joinedAt: new Date("2026-02-01"),
      };
      mockWorkspacesService.updateMemberRole.mockResolvedValueOnce(mockMember);

      const result = await controller.updateMemberRole(
        workspaceId,
        targetUserId,
        updateRoleDto,
        mockUser
      );

      expect(result).toEqual(mockMember);
      expect(service.updateMemberRole).toHaveBeenCalledWith(
        workspaceId,
        mockUser.id,
        targetUserId,
        updateRoleDto
      );
    });
  });

  describe("DELETE /api/workspaces/:id/members/:userId", () => {
    it("should call service with workspace id, actor id, and target user id", async () => {
      const workspaceId = "ws-1";
      const targetUserId = "user-2";
      mockWorkspacesService.removeMember.mockResolvedValueOnce(undefined);

      await controller.removeMember(workspaceId, targetUserId, mockUser);

      expect(service.removeMember).toHaveBeenCalledWith(workspaceId, mockUser.id, targetUserId);
    });
  });
});
