import { Router, type IRouter } from "express";
import { eq, and, desc, sql } from "drizzle-orm";
import { db } from "@workspace/db";
import {
  conversationsTable,
  conversationParticipantsTable,
  messagesTable,
  messageReadsTable,
  usersTable,
} from "@workspace/db";
import { requireAuth } from "../middlewares/auth";
import {
  CreateConversationBody,
  GetConversationParams,
  ListMessagesParams,
  SendMessageParams,
  SendMessageBody,
  MarkConversationReadParams,
} from "@workspace/api-zod";

const router: IRouter = Router();

async function isConversationMember(
  conversationId: number,
  userId: number,
): Promise<boolean> {
  const [row] = await db
    .select({ userId: conversationParticipantsTable.userId })
    .from(conversationParticipantsTable)
    .where(
      and(
        eq(conversationParticipantsTable.conversationId, conversationId),
        eq(conversationParticipantsTable.userId, userId),
      ),
    );
  return !!row;
}

async function buildConversationDetails(conversationId: number, currentUserId: number) {
  const [conv] = await db
    .select()
    .from(conversationsTable)
    .where(eq(conversationsTable.id, conversationId));

  if (!conv) return null;

  const participants = await db
    .select({
      id: usersTable.id,
      username: usersTable.username,
      displayNameAr: usersTable.displayNameAr,
      displayNameEn: usersTable.displayNameEn,
      avatarUrl: usersTable.avatarUrl,
      isAdmin: conversationParticipantsTable.isAdmin,
    })
    .from(conversationParticipantsTable)
    .innerJoin(usersTable, eq(conversationParticipantsTable.userId, usersTable.id))
    .where(eq(conversationParticipantsTable.conversationId, conversationId));

  const lastMessages = await db
    .select()
    .from(messagesTable)
    .where(eq(messagesTable.conversationId, conversationId))
    .orderBy(desc(messagesTable.createdAt))
    .limit(1);

  let lastMessage = null;
  if (lastMessages.length > 0) {
    const msg = lastMessages[0];
    const [sender] = await db
      .select({
        id: usersTable.id,
        username: usersTable.username,
        displayNameAr: usersTable.displayNameAr,
        displayNameEn: usersTable.displayNameEn,
        avatarUrl: usersTable.avatarUrl,
        isAdmin: sql<boolean>`false`,
      })
      .from(usersTable)
      .where(eq(usersTable.id, msg.senderId));

    lastMessage = { ...msg, sender };
  }

  const unreadResult = await db
    .select({ count: sql<number>`count(*)` })
    .from(messagesTable)
    .where(
      and(
        eq(messagesTable.conversationId, conversationId),
        sql`${messagesTable.id} NOT IN (
          SELECT message_id FROM message_reads WHERE user_id = ${currentUserId}
        )`,
        sql`${messagesTable.senderId} != ${currentUserId}`,
      ),
    );

  const unreadCount = Number(unreadResult[0]?.count ?? 0);

  return {
    ...conv,
    participants,
    lastMessage,
    unreadCount,
  };
}

router.get("/conversations", requireAuth, async (req, res): Promise<void> => {
  const userId = req.session.userId!;

  const userConvs = await db
    .select({ conversationId: conversationParticipantsTable.conversationId })
    .from(conversationParticipantsTable)
    .where(eq(conversationParticipantsTable.userId, userId));

  const results = await Promise.all(
    userConvs.map((c) => buildConversationDetails(c.conversationId, userId)),
  );

  res.json(results.filter(Boolean));
});

router.post("/conversations", requireAuth, async (req, res): Promise<void> => {
  const userId = req.session.userId!;
  const parsed = CreateConversationBody.safeParse(req.body);
  if (!parsed.success) {
    res.status(400).json({ error: parsed.error.message });
    return;
  }

  const { participantIds, nameAr, nameEn, isGroup } = parsed.data;

  const allParticipants = [...new Set([userId, ...participantIds])];

  const [conv] = await db
    .insert(conversationsTable)
    .values({
      nameAr: nameAr ?? null,
      nameEn: nameEn ?? null,
      isGroup: isGroup ?? false,
      createdBy: userId,
    })
    .returning();

  await db.insert(conversationParticipantsTable).values(
    allParticipants.map((pid) => ({
      conversationId: conv.id,
      userId: pid,
      isAdmin: pid === userId,
    })),
  );

  const details = await buildConversationDetails(conv.id, userId);
  res.status(201).json(details);
});

router.get("/conversations/:id", requireAuth, async (req, res): Promise<void> => {
  const params = GetConversationParams.safeParse(req.params);
  if (!params.success) {
    res.status(400).json({ error: params.error.message });
    return;
  }

  const userId = req.session.userId!;

  if (!(await isConversationMember(params.data.id, userId))) {
    res.status(403).json({ error: "Forbidden" });
    return;
  }

  const details = await buildConversationDetails(params.data.id, userId);

  if (!details) {
    res.status(404).json({ error: "Conversation not found" });
    return;
  }

  res.json(details);
});

router.get("/conversations/:id/messages", requireAuth, async (req, res): Promise<void> => {
  const params = ListMessagesParams.safeParse(req.params);
  if (!params.success) {
    res.status(400).json({ error: params.error.message });
    return;
  }

  const userId = req.session.userId!;

  if (!(await isConversationMember(params.data.id, userId))) {
    res.status(403).json({ error: "Forbidden" });
    return;
  }

  const msgs = await db
    .select()
    .from(messagesTable)
    .where(eq(messagesTable.conversationId, params.data.id))
    .orderBy(desc(messagesTable.createdAt))
    .limit(50);

  const messagesWithSenders = await Promise.all(
    msgs.reverse().map(async (msg) => {
      const [sender] = await db
        .select({
          id: usersTable.id,
          username: usersTable.username,
          displayNameAr: usersTable.displayNameAr,
          displayNameEn: usersTable.displayNameEn,
          avatarUrl: usersTable.avatarUrl,
          isAdmin: sql<boolean>`false`,
        })
        .from(usersTable)
        .where(eq(usersTable.id, msg.senderId));

      return { ...msg, sender };
    }),
  );

  res.json(messagesWithSenders);
});

router.post("/conversations/:id/messages", requireAuth, async (req, res): Promise<void> => {
  const params = SendMessageParams.safeParse(req.params);
  if (!params.success) {
    res.status(400).json({ error: params.error.message });
    return;
  }

  const parsed = SendMessageBody.safeParse(req.body);
  if (!parsed.success) {
    res.status(400).json({ error: parsed.error.message });
    return;
  }

  const userId = req.session.userId!;

  if (!(await isConversationMember(params.data.id, userId))) {
    res.status(403).json({ error: "Forbidden" });
    return;
  }

  const [msg] = await db
    .insert(messagesTable)
    .values({
      conversationId: params.data.id,
      senderId: userId,
      content: parsed.data.content,
    })
    .returning();

  const [sender] = await db
    .select({
      id: usersTable.id,
      username: usersTable.username,
      displayNameAr: usersTable.displayNameAr,
      displayNameEn: usersTable.displayNameEn,
      avatarUrl: usersTable.avatarUrl,
      isAdmin: sql<boolean>`false`,
    })
    .from(usersTable)
    .where(eq(usersTable.id, userId));

  const fullMessage = { ...msg, sender };

  const { io } = await import("../index.js");
  io.to(`conversation:${params.data.id}`).emit("new_message", fullMessage);

  res.status(201).json(fullMessage);
});

router.post("/conversations/:id/read", requireAuth, async (req, res): Promise<void> => {
  const params = MarkConversationReadParams.safeParse(req.params);
  if (!params.success) {
    res.status(400).json({ error: params.error.message });
    return;
  }

  const userId = req.session.userId!;

  if (!(await isConversationMember(params.data.id, userId))) {
    res.status(403).json({ error: "Forbidden" });
    return;
  }

  const unreadMessages = await db
    .select({ id: messagesTable.id })
    .from(messagesTable)
    .where(
      and(
        eq(messagesTable.conversationId, params.data.id),
        sql`${messagesTable.id} NOT IN (
          SELECT message_id FROM message_reads WHERE user_id = ${userId}
        )`,
      ),
    );

  if (unreadMessages.length > 0) {
    await db.insert(messageReadsTable).values(
      unreadMessages.map((m) => ({ messageId: m.id, userId })),
    ).onConflictDoNothing();

    const { io } = await import("../index.js");
    io.to(`conversation:${params.data.id}`).emit("messages_read", {
      conversationId: params.data.id,
      userId,
    });
  }

  res.json({ success: true });
});

export default router;
