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 { tracked } from "@trpc/server";
import type { EventArgs } from "@/server/sse/events";
import {
  decreaseLobbyMemberCount,
  increaseLobbyMemberCount,
  handleLobbyAfterLeave,
} from "@/server/api/mutation-utils/lobby-utils";
import { createGameConfig } from "../mutation-utils/game-config";
import { ee } from "@/server/sse";

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: {
            gameConfig: true,
            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: {
            gameConfig: true,
            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");
      const [member] = await ctx.db
        .insert(lobbyMembers)
        .values({
          lobbyId: lobby.id,
          playerId: ctx.session.user.id,
          isReady: false,
          role: "admin",
        })
        .returning({
          id: lobbyMembers.playerId,
        });
      if (member && lobby) {
        increaseLobbyMemberCount(lobby.id);
        createGameConfig(lobby.id, ctx.db);
      }
      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) ee.emit("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) {
            ee.emit("lobby:member:membership", input.lobbyId, true, {
              ...member,
              player,
            });
            increaseLobbyMemberCount(input.lobbyId);
            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) {
          ee.emit(
            "lobby:member:membership",
            input.lobbyId,
            false,
            member.playerId,
          );
          decreaseLobbyMemberCount(input.lobbyId);
          handleLobbyAfterLeave(input.lobbyId);
          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) ee.emit("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)
        ee.emit(
          "lobby:member:membership",
          input.lobbyId,
          false,
          member.playerId,
          true,
        );
      decreaseLobbyMemberCount(input.lobbyId);
      return member;
    }),

  // subscriptions
  onUpdate: publicProcedure
    .input(z.object({ lobbyId: z.string() }))
    .subscription(async function* (opts) {
      const iterable = ee.toIterable("lobby:update", {
        signal: opts.signal,
      });

      function* maybeYield([lobby, deleted]: EventArgs["lobby:update"]) {
        if (lobby.id !== opts.input.lobbyId) {
          return;
        }
        yield tracked(lobby.id, { lobby, deleted });
      }

      for await (const args of iterable) {
        yield* maybeYield(args);
      }
    }),

  onMemberUpdate: publicProcedure
    .input(z.object({ lobbyId: z.string() }))
    .subscription(async function* (opts) {
      const iterable = ee.toIterable("lobby:member:membership", {
        signal: opts.signal,
      });

      function* maybeYield([
        lobbyId,
        joined,
        member,
        kicked,
      ]: EventArgs["lobby:member:membership"]) {
        if (lobbyId !== opts.input.lobbyId) {
          return;
        }
        yield tracked(lobbyId, { member, joined, kicked });
      }

      for await (const args of iterable) {
        yield* maybeYield(args);
      }
    }),
});
