import { BadRequestException, ConflictException, NotFoundException } from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import { TeamMemberRole } from "@prisma/client";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { PrismaService } from "../prisma/prisma.service";
import { TeamsService } from "./teams.service";

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

  const mockPrismaService = {
    team: {
      create: vi.fn(),
      findMany: vi.fn(),
      findFirst: vi.fn(),
      deleteMany: vi.fn(),
    },
    workspaceMember: {
      findUnique: vi.fn(),
    },
    teamMember: {
      findUnique: vi.fn(),
      create: vi.fn(),
      deleteMany: vi.fn(),
    },
  };

  const mockWorkspaceId = "550e8400-e29b-41d4-a716-446655440001";
  const mockTeamId = "550e8400-e29b-41d4-a716-446655440002";
  const mockUserId = "550e8400-e29b-41d4-a716-446655440003";

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

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

    vi.clearAllMocks();
  });

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

  describe("create", () => {
    it("should create a team", async () => {
      const createDto = {
        name: "Platform Team",
        description: "Owns platform services",
      };

      const createdTeam = {
        id: mockTeamId,
        workspaceId: mockWorkspaceId,
        name: createDto.name,
        description: createDto.description,
        metadata: {},
        createdAt: new Date(),
        updatedAt: new Date(),
      };

      mockPrismaService.team.create.mockResolvedValue(createdTeam);

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

      expect(result).toEqual(createdTeam);
      expect(prisma.team.create).toHaveBeenCalledWith({
        data: {
          workspaceId: mockWorkspaceId,
          name: createDto.name,
          description: createDto.description,
        },
      });
    });
  });

  describe("findAll", () => {
    it("should list teams for a workspace", async () => {
      const teams = [
        {
          id: mockTeamId,
          workspaceId: mockWorkspaceId,
          name: "Platform Team",
          description: "Owns platform services",
          metadata: {},
          createdAt: new Date(),
          updatedAt: new Date(),
          _count: { members: 1 },
        },
      ];

      mockPrismaService.team.findMany.mockResolvedValue(teams);

      const result = await service.findAll(mockWorkspaceId);

      expect(result).toEqual(teams);
      expect(prisma.team.findMany).toHaveBeenCalledWith({
        where: { workspaceId: mockWorkspaceId },
        include: {
          _count: {
            select: { members: true },
          },
        },
        orderBy: { createdAt: "asc" },
      });
    });
  });

  describe("addMember", () => {
    it("should add a workspace member to a team", async () => {
      const dto = {
        userId: mockUserId,
        role: TeamMemberRole.ADMIN,
      };

      const createdTeamMember = {
        teamId: mockTeamId,
        userId: mockUserId,
        role: TeamMemberRole.ADMIN,
        joinedAt: new Date(),
        user: {
          id: mockUserId,
          name: "Test User",
          email: "test@example.com",
        },
      };

      mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId });
      mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ userId: mockUserId });
      mockPrismaService.teamMember.findUnique.mockResolvedValue(null);
      mockPrismaService.teamMember.create.mockResolvedValue(createdTeamMember);

      const result = await service.addMember(mockWorkspaceId, mockTeamId, dto);

      expect(result).toEqual(createdTeamMember);
      expect(prisma.team.findFirst).toHaveBeenCalledWith({
        where: {
          id: mockTeamId,
          workspaceId: mockWorkspaceId,
        },
        select: { id: true },
      });
      expect(prisma.workspaceMember.findUnique).toHaveBeenCalledWith({
        where: {
          workspaceId_userId: {
            workspaceId: mockWorkspaceId,
            userId: mockUserId,
          },
        },
        select: { userId: true },
      });
      expect(prisma.teamMember.create).toHaveBeenCalledWith({
        data: {
          teamId: mockTeamId,
          userId: mockUserId,
          role: TeamMemberRole.ADMIN,
        },
        include: {
          user: {
            select: {
              id: true,
              name: true,
              email: true,
            },
          },
        },
      });
    });

    it("should use MEMBER role when role is omitted", async () => {
      const dto = { userId: mockUserId };

      mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId });
      mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ userId: mockUserId });
      mockPrismaService.teamMember.findUnique.mockResolvedValue(null);
      mockPrismaService.teamMember.create.mockResolvedValue({
        teamId: mockTeamId,
        userId: mockUserId,
        role: TeamMemberRole.MEMBER,
        joinedAt: new Date(),
      });

      await service.addMember(mockWorkspaceId, mockTeamId, dto);

      expect(prisma.teamMember.create).toHaveBeenCalledWith(
        expect.objectContaining({
          data: expect.objectContaining({
            role: TeamMemberRole.MEMBER,
          }),
        })
      );
    });

    it("should throw when team does not belong to workspace", async () => {
      mockPrismaService.team.findFirst.mockResolvedValue(null);

      await expect(
        service.addMember(mockWorkspaceId, mockTeamId, { userId: mockUserId })
      ).rejects.toThrow(NotFoundException);
      expect(prisma.workspaceMember.findUnique).not.toHaveBeenCalled();
    });

    it("should throw when user is not a workspace member", async () => {
      mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId });
      mockPrismaService.workspaceMember.findUnique.mockResolvedValue(null);

      await expect(
        service.addMember(mockWorkspaceId, mockTeamId, { userId: mockUserId })
      ).rejects.toThrow(BadRequestException);
    });

    it("should throw when user is already in the team", async () => {
      mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId });
      mockPrismaService.workspaceMember.findUnique.mockResolvedValue({ userId: mockUserId });
      mockPrismaService.teamMember.findUnique.mockResolvedValue({ userId: mockUserId });

      await expect(
        service.addMember(mockWorkspaceId, mockTeamId, { userId: mockUserId })
      ).rejects.toThrow(ConflictException);
    });
  });

  describe("removeMember", () => {
    it("should remove a member from a team", async () => {
      mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId });
      mockPrismaService.teamMember.deleteMany.mockResolvedValue({ count: 1 });

      await service.removeMember(mockWorkspaceId, mockTeamId, mockUserId);

      expect(prisma.teamMember.deleteMany).toHaveBeenCalledWith({
        where: {
          teamId: mockTeamId,
          userId: mockUserId,
        },
      });
    });

    it("should throw when team does not belong to workspace", async () => {
      mockPrismaService.team.findFirst.mockResolvedValue(null);

      await expect(service.removeMember(mockWorkspaceId, mockTeamId, mockUserId)).rejects.toThrow(
        NotFoundException
      );
      expect(prisma.teamMember.deleteMany).not.toHaveBeenCalled();
    });

    it("should throw when user is not in the team", async () => {
      mockPrismaService.team.findFirst.mockResolvedValue({ id: mockTeamId });
      mockPrismaService.teamMember.deleteMany.mockResolvedValue({ count: 0 });

      await expect(service.removeMember(mockWorkspaceId, mockTeamId, mockUserId)).rejects.toThrow(
        NotFoundException
      );
    });
  });

  describe("remove", () => {
    it("should delete a team", async () => {
      mockPrismaService.team.deleteMany.mockResolvedValue({ count: 1 });

      await service.remove(mockWorkspaceId, mockTeamId);

      expect(prisma.team.deleteMany).toHaveBeenCalledWith({
        where: {
          id: mockTeamId,
          workspaceId: mockWorkspaceId,
        },
      });
    });

    it("should throw when team is not found", async () => {
      mockPrismaService.team.deleteMany.mockResolvedValue({ count: 0 });

      await expect(service.remove(mockWorkspaceId, mockTeamId)).rejects.toThrow(NotFoundException);
    });
  });
});
