import { db } from "~/server/db";
import { z } from "zod";
import {
  createTRPCRouter,
  protectedProcedure,
  publicProcedure,
} from "~/server/api/trpc";
import { add } from "~/server/services/chat.service";
import { zChatId, zUserId } from "~/server/utils/zod_types";
import { ee, type OnlineStatus } from "~/server/sse";
import { getMessagesArray } from "~/utils/kysely-helper";
import { tracked, TRPCError } from "@trpc/server";
import {
  addTyping,
  getCurrentlyTyping,
  removeStaleTyping,
  removeTyping,
} from "~/server/controllers/chat.controller";
import { getKeydbClient } from "~/utils/keydb";

export const chatSSERouter = createTRPCRouter({
  list: publicProcedure.query(async () => {
    return await db
      .selectFrom("chats")
      .select([
        "chats.chatid",
        "chats.created_at",
        "chats.user_ref",
        getMessagesArray(),
      ])
      .innerJoin("users", "users.id", "chats.user_ref")
      .select(["users.username", "users.email", "users.nome"])
      .orderBy("chats.created_at", "desc")
      .execute();
  }),

  create: protectedProcedure
    .input(z.object({ userId: zUserId }))
    .mutation(async ({ input }) => {
      const newChat = await add({
        db,
        userId: input.userId,
      });

      return newChat.chatid;
    }),

  type: protectedProcedure
    .input(
      z.object({ chatId: zChatId, typing: z.boolean(), username: z.string() }),
    )
    .mutation(async ({ ctx, input }) => {
      try {
        await removeStaleTyping(input.chatId);

        if (input.typing) {
          // Add the user to the set
          await addTyping({
            chatId: input.chatId,
            userId: ctx.session.id,
            username: input.username,
          });
        } else {
          await removeTyping({
            chatId: input.chatId,
            userId: ctx.session.id,
            username: input.username,
          });
        }

        ee.emit("chatUpdates", input.chatId, {
          eventType: "typing",
          data: await getCurrentlyTyping(input.chatId),
        });
        return true;
      } catch (e) {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message:
            "An error occurred while processing the typing event: " +
            (e as Error).message,
        });
      }
    }),

  enterChat: protectedProcedure
    .input(z.object({ chatId: zChatId }))
    .mutation(async ({ input, ctx }) => {
      const chatKey = `chat:${input.chatId}:onlineUsers`;
      const keydb = getKeydbClient();

      if (!keydb) {
        console.warn(
          "KeyDB client is not initialized. Skipping online status.",
        );
        return [];
      }
      // Add the user to the Redis set
      await keydb.sadd(
        chatKey,
        JSON.stringify({
          userId: ctx.session.id,
          isAdmin: ctx.session.isAdmin,
          nome: ctx.session.nome,
        }),
      );

      const onlineUsers = await keydb.smembers(chatKey);
      const parsedUsers = onlineUsers.map(
        (user) => JSON.parse(user) as OnlineStatus,
      );
      ee.emit("chatUpdates", input.chatId, {
        eventType: "online",
        data: parsedUsers,
      });
      return parsedUsers;
    }),

  leaveChat: protectedProcedure
    .input(z.object({ chatId: zChatId }))
    .mutation(async ({ input, ctx }) => {
      const chatKey = `chat:${input.chatId}:onlineUsers`;
      const keydb = getKeydbClient();
      if (!keydb) {
        console.warn(
          "KeyDB client is not initialized. Skipping online status.",
        );
        return [];
      }
      // Remove the user from the Redis set
      await keydb.srem(
        chatKey,
        JSON.stringify({
          userId: ctx.session.id,
          isAdmin: ctx.session.isAdmin,
          nome: ctx.session.nome,
        }),
      );

      const onlineUsers = await keydb.smembers(chatKey);
      const parsedUsers = onlineUsers.map(
        (user) => JSON.parse(user) as OnlineStatus,
      );
      ee.emit("chatUpdates", input.chatId, {
        eventType: "online",
        data: parsedUsers,
      });
      return parsedUsers;
    }),

  onChatEvent: protectedProcedure
    .input(
      z.object({
        chatId: zChatId,
      }),
    )
    .subscription(async function* ({ input, signal }) {
      try {
        const iterable = ee.toIterable("chatUpdates", {
          signal,
        });

        for await (const [chatId, data] of iterable) {
          if (chatId === input.chatId) {
            yield tracked(chatId, data);
          }
        }
      } catch (e) {
        if (e instanceof Error && e.name === "AbortError") return;
        console.error("Error in onChatEvent subscription:", e);
      }
    }),
});
