import { describe, it, expect, vi, beforeEach } from "vitest";
import {
  TEST_USER,
  TEST_USER_2,
  TEST_RESTAURANT,
  TEST_RESTAURANT_2,
  TEST_ROOM_DATA,
} from "@/__tests__/helpers/fixtures";

vi.mock("@/lib/roomRepository", () => ({
  getRoomData: vi.fn(),
}));

vi.mock("@/lib/prisma", () => ({
  prisma: {
    user: {
      findMany: vi.fn(),
    },
  },
}));

import { buildRoomStatus } from "@/lib/buildRoomStatus";
import { getRoomData } from "@/lib/roomRepository";
import { prisma } from "@/lib/prisma";

const mockGetRoomData = vi.mocked(getRoomData);
const mockFindMany = vi.mocked(prisma.user.findMany);

beforeEach(() => {
  vi.clearAllMocks();
  mockFindMany.mockResolvedValue([
    { id: TEST_USER.id, username: TEST_USER.username, avatar: TEST_USER.avatar } as never,
    { id: TEST_USER_2.id, username: TEST_USER_2.username, avatar: TEST_USER_2.avatar } as never,
  ]);
});

describe("buildRoomStatus", () => {
  it("returns null when room not found", async () => {
    mockGetRoomData.mockResolvedValue(null);
    const result = await buildRoomStatus("nonexistent");
    expect(result).toBeNull();
  });

  it("returns base status for room with no match", async () => {
    mockGetRoomData.mockResolvedValue({ ...TEST_ROOM_DATA });
    const result = await buildRoomStatus("room-1");

    expect(result).not.toBeNull();
    expect(result!.roomId).toBe("room-1");
    expect(result!.userCount).toBe(2);
    expect(result!.match).toBeNull();
    expect(result!.matchType).toBeNull();
    expect(result!.restaurants).toHaveLength(3);
    expect(result!.creatorId).toBe(TEST_USER.id);
  });

  it("returns unanimous match when data has match", async () => {
    mockGetRoomData.mockResolvedValue({
      ...TEST_ROOM_DATA,
      match: TEST_RESTAURANT.id,
    });
    const result = await buildRoomStatus("room-1");

    expect(result!.match).toBe(TEST_RESTAURANT.id);
    expect(result!.matchType).toBe("unanimous");
    expect(result!.matchLikes).toBe(2);
  });

  it("returns best match when all finished and top has likes", async () => {
    const data = {
      ...TEST_ROOM_DATA,
      swipeCounts: {
        [TEST_USER.id]: 3,
        [TEST_USER_2.id]: 3,
      },
      likes: {
        [TEST_RESTAURANT.id]: [TEST_USER.id],
        [TEST_RESTAURANT_2.id]: [TEST_USER.id, TEST_USER_2.id],
      },
    };
    mockGetRoomData.mockResolvedValue(data);
    const result = await buildRoomStatus("room-1");

    expect(result!.matchType).toBe("best");
    expect(result!.match).toBe(TEST_RESTAURANT_2.id);
    expect(result!.matchLikes).toBe(2);
    expect(result!.runnerUps).toEqual([{ id: TEST_RESTAURANT.id, likes: 1 }]);
  });

  it("returns no_match when all finished but no likes", async () => {
    const data = {
      ...TEST_ROOM_DATA,
      swipeCounts: {
        [TEST_USER.id]: 3,
        [TEST_USER_2.id]: 3,
      },
      likes: {},
    };
    mockGetRoomData.mockResolvedValue(data);
    const result = await buildRoomStatus("room-1");

    expect(result!.matchType).toBe("no_match");
    expect(result!.matchLikes).toBe(0);
  });

  it("ranks by likes descending then by rating", async () => {
    const data = {
      ...TEST_ROOM_DATA,
      swipeCounts: {
        [TEST_USER.id]: 3,
        [TEST_USER_2.id]: 3,
      },
      likes: {
        [TEST_RESTAURANT.id]: [TEST_USER.id],
        [TEST_RESTAURANT_2.id]: [TEST_USER.id],
      },
    };
    mockGetRoomData.mockResolvedValue(data);
    const result = await buildRoomStatus("room-1");

    // Both have 1 like, TEST_RESTAURANT has higher rating (4.5 vs 4.0)
    expect(result!.match).toBe(TEST_RESTAURANT.id);
    expect(result!.runnerUps[0].id).toBe(TEST_RESTAURANT_2.id);
  });

  it("does not consider allFinished when no users", async () => {
    const data = {
      ...TEST_ROOM_DATA,
      users: [],
      swipeCounts: {},
    };
    mockGetRoomData.mockResolvedValue(data);
    const result = await buildRoomStatus("room-1");

    expect(result!.matchType).toBeNull();
  });

  it("populates user profiles", async () => {
    mockGetRoomData.mockResolvedValue({ ...TEST_ROOM_DATA });
    const result = await buildRoomStatus("room-1");

    expect(result!.userProfiles[TEST_USER.id]).toEqual({
      id: TEST_USER.id,
      username: TEST_USER.username,
      avatar: TEST_USER.avatar,
    });
  });

  it("includes likeCounts only for restaurants with likes", async () => {
    const data = {
      ...TEST_ROOM_DATA,
      likes: {
        [TEST_RESTAURANT.id]: [TEST_USER.id, TEST_USER_2.id],
        [TEST_RESTAURANT_2.id]: [],
      },
    };
    mockGetRoomData.mockResolvedValue(data);
    const result = await buildRoomStatus("room-1");

    expect(result!.likeCounts[TEST_RESTAURANT.id]).toBe(2);
    expect(result!.likeCounts[TEST_RESTAURANT_2.id]).toBeUndefined();
  });
});
