import { prisma } from "./prisma";
import { Prisma } from "@prisma/client";
import { Restaurant, SceneType } from "@/types";

const ROOM_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours

export interface RoomData {
  users: string[];
  restaurants: Restaurant[];
  likes: Record<string, string[]>;
  swipeCounts: Record<string, number>;
  match: string | null;
  creatorId: string;
  locked: boolean;
  kickedUsers: string[];
  scene: SceneType;
}

interface RoomLikeRow {
  userId: string;
  restaurantId: string;
}

const ROOM_ID_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";

function normalizeLikeRows(likes: Record<string, string[]>): RoomLikeRow[] {
  const rows: RoomLikeRow[] = [];
  const seen = new Set<string>();

  for (const [restaurantId, userIds] of Object.entries(likes)) {
    for (const userId of userIds) {
      const key = `${userId}::${restaurantId}`;
      if (seen.has(key)) continue;
      seen.add(key);
      rows.push({ userId, restaurantId });
    }
  }

  return rows;
}

export function diffRoomLikes(
  currentLikes: Record<string, string[]>,
  updatedLikes: Record<string, string[]>,
): { toCreate: RoomLikeRow[]; toDelete: RoomLikeRow[] } {
  const currentRows = normalizeLikeRows(currentLikes);
  const updatedRows = normalizeLikeRows(updatedLikes);

  const currentMap = new Map(
    currentRows.map((row) => [`${row.userId}::${row.restaurantId}`, row]),
  );
  const updatedMap = new Map(
    updatedRows.map((row) => [`${row.userId}::${row.restaurantId}`, row]),
  );

  const toCreate: RoomLikeRow[] = [];
  const toDelete: RoomLikeRow[] = [];

  for (const [key, row] of updatedMap) {
    if (!currentMap.has(key)) toCreate.push(row);
  }
  for (const [key, row] of currentMap) {
    if (!updatedMap.has(key)) toDelete.push(row);
  }

  return { toCreate, toDelete };
}

function generateRoomId(): string {
  let id = "";
  for (let i = 0; i < 6; i++) {
    id += ROOM_ID_CHARS[Math.floor(Math.random() * ROOM_ID_CHARS.length)];
  }
  return id;
}

let lastCleanup = 0;
const CLEANUP_INTERVAL_MS = 60 * 60 * 1000;

async function cleanupExpiredRooms() {
  const now = Date.now();
  if (now - lastCleanup < CLEANUP_INTERVAL_MS) return;
  lastCleanup = now;

  try {
    const { count } = await prisma.room.deleteMany({
      where: { expiresAt: { lt: new Date() } },
    });
    if (count > 0) {
      console.log(`Cleaned up ${count} expired room(s)`);
    }
  } catch (e) {
    console.error("Room cleanup failed:", e);
  }
}

export async function createRoom(restaurants: Restaurant[], creatorId: string, scene: SceneType = "eat"): Promise<string> {
  await cleanupExpiredRooms();

  const expiresAt = new Date(Date.now() + ROOM_TTL_MS);

  for (let attempts = 0; attempts < 20; attempts++) {
    const roomId = generateRoomId();
    try {
      await prisma.$transaction(async (tx) => {
        await tx.room.create({
          data: { id: roomId, creatorId, scene, expiresAt },
        });

        if (restaurants.length > 0) {
          await tx.roomRestaurant.createMany({
            data: restaurants.map((r) => ({
              roomId,
              restaurantData: JSON.stringify(r),
            })),
          });
        }
      });
      return roomId;
    } catch (e) {
      if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") {
        continue;
      }
      throw e;
    }
  }

  throw new Error("无法生成唯一房间号，请稍后重试");
}

export async function getRoomData(roomId: string): Promise<RoomData | null> {
  const room = await prisma.room.findUnique({
    where: { id: roomId },
    include: {
      members: true,
      restaurants: true,
      likes: true,
      swipes: true,
    },
  });

  if (!room) return null;
  if (room.expiresAt < new Date()) {
    await prisma.room.delete({ where: { id: roomId } }).catch((e) => {
      console.error(`Failed to delete expired room ${roomId}:`, e);
    });
    return null;
  }

  const users = room.members.filter((m) => !m.kicked).map((m) => m.userId);
  const kickedUsers = room.members.filter((m) => m.kicked).map((m) => m.userId);
  const restaurants: Restaurant[] = room.restaurants.map((r) => JSON.parse(r.restaurantData));

  const likes: Record<string, string[]> = {};
  for (const like of room.likes) {
    if (!likes[like.restaurantId]) likes[like.restaurantId] = [];
    likes[like.restaurantId].push(like.userId);
  }

  const swipeCounts: Record<string, number> = {};
  for (const swipe of room.swipes) {
    swipeCounts[swipe.userId] = swipe.count;
  }

  return {
    users,
    restaurants,
    likes,
    swipeCounts,
    match: room.match,
    creatorId: room.creatorId,
    locked: room.locked,
    kickedUsers,
    scene: room.scene as SceneType,
  };
}

/**
 * Per-room mutex to serialize concurrent read-modify-write operations.
 */
const roomLocks = new Map<string, Promise<unknown>>();

function withRoomLock<T>(roomId: string, fn: () => Promise<T>): Promise<T> {
  const prev = roomLocks.get(roomId) ?? Promise.resolve();
  const next = prev.then(fn, fn);
  roomLocks.set(roomId, next);
  next.finally(() => {
    if (roomLocks.get(roomId) === next) {
      roomLocks.delete(roomId);
    }
  });
  return next;
}

/**
 * Atomic read-modify-write with per-room serialization.
 * Translates RoomData mutations into relational table operations.
 */
export async function atomicUpdateRoom(
  roomId: string,
  updater: (data: RoomData) => RoomData,
): Promise<RoomData | null> {
  return withRoomLock(roomId, async () => {
    const current = await getRoomData(roomId);
    if (!current) return null;

    const updated = updater(current);

    await prisma.$transaction(async (tx) => {
      // Update room-level fields
      await tx.room.update({
        where: { id: roomId },
        data: {
          scene: updated.scene,
          locked: updated.locked,
          match: updated.match,
        },
      });

      // Sync all members (users + kicked)
      const allMemberIds = new Set([...updated.users, ...updated.kickedUsers]);
      const kickedSet = new Set(updated.kickedUsers);

      for (const userId of allMemberIds) {
        await tx.roomMember.upsert({
          where: { roomId_userId: { roomId, userId } },
          update: { kicked: kickedSet.has(userId) },
          create: { roomId, userId, kicked: kickedSet.has(userId) },
        });
      }

      // Sync likes incrementally to avoid full delete + rebuild on every change.
      const { toCreate, toDelete } = diffRoomLikes(current.likes, updated.likes);
      if (toDelete.length > 0) {
        await tx.roomLike.deleteMany({
          where: {
            roomId,
            OR: toDelete.map((row) => ({
              userId: row.userId,
              restaurantId: row.restaurantId,
            })),
          },
        });
      }
      if (toCreate.length > 0) {
        await tx.roomLike.createMany({
          data: toCreate.map((row) => ({
            roomId,
            userId: row.userId,
            restaurantId: row.restaurantId,
          })),
        });
      }

      // Sync swipes
      if (JSON.stringify(current.swipeCounts) !== JSON.stringify(updated.swipeCounts)) {
        for (const [userId, count] of Object.entries(updated.swipeCounts)) {
          await tx.roomSwipe.upsert({
            where: { roomId_userId: { roomId, userId } },
            update: { count },
            create: { roomId, userId, count },
          });
        }
      }
    });

    return updated;
  });
}
