import { z } from "zod";

import { lobbies, lobbyMembers, players, type Lobby } from "@/server/db/schema";
import {
  createTRPCRouter,
  protectedProcedure,
  publicProcedure,
} from "@/server/api/trpc";
import {
  LobbyMemberRoleSchema,
  lobbyPatchSchema,
} from "@/lib/validations/lobby";
import { and, eq } from "drizzle-orm";
import {
  combineRedisIterators,
  redisAsyncIterator,
  redisPublish,
} from "@/server/redis/sse-redis";
import { tracked, TRPCError } from "@trpc/server";
import { trcpSubscriptionInput } from "@/lib/validations/trcp";
import type { LobbyMemberLeaveEventData } from "@/server/redis/events";

export const lobbyRouter = createTRPCRouter({
  // queries
  getCurrentLobby: protectedProcedure.query(async ({ ctx }) => {
    const reuslt = await ctx.db.query.lobbyMembers.findFirst({
      where: eq(lobbyMembers.playerId, ctx.session.user.id),
      with: {
        lobby: {
          with: {
            members: {
              with: {
                player: true,
              },
            },
          },
        },
      },
    });
    return reuslt?.lobby!;
  }),

  get: publicProcedure
    .input(
      z.object({
        id: z.string(),
      }),
    )
    .query(
      async ({ ctx, input }) =>
        await ctx.db.query.lobbies.findFirst({
          where: eq(lobbies.id, input.id),
          with: {
            members: {
              with: {
                player: true,
              },
            },
          },
        }),
    ),

  // mutations
  create: protectedProcedure
    .input(
      z.object({
        lobby: lobbyPatchSchema,
      }),
    )
    .mutation(async ({ ctx, input }) => {
      const [lobby] = await ctx.db
        .insert(lobbies)
        .values({
          ...input.lobby,
          createdById: ctx.session.user.id,
        })
        .returning({ id: lobbies.id });
      if (!lobby) throw new Error("Error creating lobby");
      await ctx.db.insert(lobbyMembers).values({
        lobbyId: lobby.id,
        playerId: ctx.session.user.id,
        isReady: false,

        role: "admin",
      });

      return lobby;
    }),
  update: protectedProcedure
    .input(
      z.object({
        lobby: lobbyPatchSchema,
        lobbyId: z.string(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      console.log("Check if user is admin");

      const [lobby] = await ctx.db
        .update(lobbies)
        .set(input.lobby)
        .where(
          and(
            eq(lobbies.id, input.lobbyId),
            eq(lobbies.createdById, ctx.session.user.id),
          ),
        )
        .returning();

      if (lobby) redisPublish("lobby:update", lobby);
      return lobby;
    }),
  delete: protectedProcedure
    .input(
      z.object({
        lobbyId: z.string(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      console.log("Check if user is admin");

      const [lobby] = await ctx.db
        .delete(lobbies)
        .where(
          and(
            eq(lobbies.id, input.lobbyId),
            eq(lobbies.createdById, ctx.session.user.id),
          ),
        )
        .returning({ id: lobbies.id });
      return lobby;
    }),

  membership: protectedProcedure
    .input(
      z.object({
        lobbyId: z.string(),
        join: z.boolean(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      if (input.join) {
        try {
          const [member] = await ctx.db
            .insert(lobbyMembers)
            .values({
              lobbyId: input.lobbyId,
              playerId: ctx.session.user.id,
              isReady: false,
              role: "player",
            })
            .returning();
          const player = member
            ? await ctx.db.query.players.findFirst({
                where: eq(players.id, member.playerId),
              })
            : undefined;
          if (member) redisPublish("lobby:member:join", { ...member, player });
          return { success: true, member };
        } catch (e: unknown) {
          if (e instanceof Error) {
            if (
              e.message.includes(
                "duplicate key value violates unique constraint",
              )
            )
              return {
                knownError: true,
                succes: false,
                error: "You can only be in one lobby at a time.",
              };
          } else {
            return {
              knownError: true,
              succes: false,
              error: "Error joining lobby",
            };
          }
        }
      } else {
        const [member] = await ctx.db
          .delete(lobbyMembers)
          .where(
            and(
              eq(lobbyMembers.lobbyId, input.lobbyId),
              eq(lobbyMembers.playerId, ctx.session.user.id),
            ),
          )
          .returning();
        if (member)
          redisPublish("lobby:member:leave", { playerId: member.playerId });
        return { success: true, member };
      }
    }),
  // admin mutaions
  changeRole: protectedProcedure
    .input(
      z.object({
        lobbyId: z.string(),
        playerId: z.string(),
        role: LobbyMemberRoleSchema,
      }),
    )
    .mutation(async ({ ctx, input }) => {
      console.log("Check if user is admin");

      const [member] = await ctx.db
        .update(lobbyMembers)
        .set(input.role === "admin" ? { role: "admin" } : { role: "player" })
        .where(
          and(
            eq(lobbyMembers.lobbyId, input.lobbyId),
            eq(lobbyMembers.playerId, input.playerId),
          ),
        )
        .returning();
      if (member) redisPublish("lobby:member:update", member);
      return member;
    }),

  kick: protectedProcedure
    .input(
      z.object({
        lobbyId: z.string(),
        playerId: z.string(),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      console.log("Check if user is admin");

      const [member] = await ctx.db
        .delete(lobbyMembers)
        .where(
          and(
            eq(lobbyMembers.lobbyId, input.lobbyId),
            eq(lobbyMembers.playerId, input.playerId),
          ),
        )
        .returning();
      if (member)
        redisPublish("lobby:member:leave", {
          playerId: member.playerId,
          kicked: true,
        });
      return member;
    }),

  // subscriptions
  onUpdate: publicProcedure
    .input(trcpSubscriptionInput)
    .subscription(async function* (opts) {
      if (opts.input?.lastEventId) {
        // fetch posts from a database that were missed.
      }
      for await (const lobby of redisAsyncIterator("lobby:update")) {
        yield tracked(lobby?.updatedAt?.toString(), {
          lobby,
        });
      }
    }),

  onMemberUpdate: publicProcedure
    .input(trcpSubscriptionInput)
    .subscription(async function* (opts) {
      if (opts.input?.lastEventId) {
        // fetch posts from a database that were missed.
      }

      for await (const { event, data: membership } of combineRedisIterators([
        "lobby:member:join",
        "lobby:member:leave",
        "lobby:member:update",
      ])) {
        switch (event) {
          case "lobby:member:join":
            yield tracked(String(membership.playerId), {
              joined: true,
              membership,
            });
            break;
          case "lobby:member:leave":
            const data = membership as LobbyMemberLeaveEventData;
            yield tracked(String(membership.playerId), {
              joined: false,
              membership: data,
            });
            break;
          // case "lobby:member:update":
          //   yield tracked(String(membership.playerId), {
          //     membership,
          //   });
          //   break;
        }
      }
    }),
});
