import { db, eq, and, isNotNull } from "@haushaltsApp/db";
import { shoppingItems } from "@haushaltsApp/db/schema";
import type { ServerWebSocket } from "bun";
import type { ShoppingServerEvent, ShoppingClientCommand } from "@haushaltsApp/shared/schemas/shopping.schema";
import {
  getShoppingItems,
  addShoppingItem,
  checkShoppingItem,
  uncheckShoppingItem,
  deleteShoppingItem,
} from "../services/shopping.service";

type WsData = { householdId: string; userId: string };

// ── Room Management ────────────────────────────────────────────────────────────

const rooms = new Map<string, Set<ServerWebSocket<WsData>>>();

function joinRoom(householdId: string, ws: ServerWebSocket<WsData>): void {
  if (!rooms.has(householdId)) rooms.set(householdId, new Set());
  rooms.get(householdId)!.add(ws);
}

function leaveRoom(householdId: string, ws: ServerWebSocket<WsData>): void {
  const room = rooms.get(householdId);
  if (!room) return;
  room.delete(ws);
  if (room.size === 0) rooms.delete(householdId);
}

export function broadcast(
  householdId: string,
  event: ShoppingServerEvent,
  exclude?: ServerWebSocket<WsData>,
): void {
  const sockets = rooms.get(householdId);
  if (!sockets) return;
  const payload = JSON.stringify(event);
  for (const ws of sockets) {
    if (ws !== exclude && ws.readyState === 1) {
      ws.send(payload);
    }
  }
}

// ── WebSocket Handlers ─────────────────────────────────────────────────────────

export const shoppingWsHandlers = {
  async open(ws: ServerWebSocket<WsData>) {
    const { householdId } = ws.data;
    joinRoom(householdId, ws);
    const items = await getShoppingItems(householdId);
    ws.send(JSON.stringify({ type: "sync", items } satisfies ShoppingServerEvent));
  },

  async message(ws: ServerWebSocket<WsData>, rawMessage: string | Buffer) {
    const { householdId, userId } = ws.data;
    let cmd: ShoppingClientCommand;
    try {
      cmd = JSON.parse(
        typeof rawMessage === "string" ? rawMessage : rawMessage.toString(),
      ) as ShoppingClientCommand;
    } catch {
      return;
    }

    if (cmd.type === "item:add") {
      const item = await addShoppingItem(householdId, userId, cmd.label, cmd.quantity);
      broadcast(householdId, { type: "item:added", item });
    } else if (cmd.type === "item:check") {
      const item = await checkShoppingItem(cmd.itemId, householdId, userId);
      if (item) {
        broadcast(householdId, {
          type: "item:checked",
          itemId: item.id,
          checkedBy: item.checkedBy!,
          checkedAt: item.checkedAt!,
        });
      }
    } else if (cmd.type === "item:uncheck") {
      await uncheckShoppingItem(cmd.itemId, householdId);
      broadcast(householdId, { type: "item:unchecked", itemId: cmd.itemId });
    } else if (cmd.type === "item:delete") {
      await deleteShoppingItem(cmd.itemId, householdId);
      broadcast(householdId, { type: "item:deleted", itemId: cmd.itemId });
    } else if (cmd.type === "item:clear") {
      await db
        .delete(shoppingItems)
        .where(
          and(eq(shoppingItems.householdId, householdId), isNotNull(shoppingItems.checkedBy)),
        );
      broadcast(householdId, { type: "item:cleared" });
    }
  },

  close(ws: ServerWebSocket<WsData>) {
    leaveRoom(ws.data.householdId, ws);
  },
};
