import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { db, conversations, conversationReads, messages, eq, and, desc, lt } from "@epicure/db";
import { requireSession } from "@/lib/api-auth";
import { applyRateLimit } from "@/lib/rate-limit";
import { isParticipant, otherParticipantId } from "@/lib/messaging";
import { isBlockedEitherWay } from "@/lib/blocks";
import { randomUUID } from "crypto";

interface RouteContext {
  params: Promise<{ id: string }>;
}

const Schema = z.object({ content: z.string().min(1).max(4000) });

const PAGE_SIZE = 50;

export async function GET(req: NextRequest, { params }: RouteContext) {
  const { session, response } = await requireSession();
  if (response) return response;
  const { id } = await params;

  const conversation = await db.query.conversations.findFirst({ where: eq(conversations.id, id) });
  if (!conversation || !isParticipant(conversation, session!.user.id)) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  // Cursor-based pagination: fetch the page ending just before `before`
  // (an ISO createdAt timestamp), newest-first, then reverse so the client
  // still receives oldest-first order for the page. This avoids the old
  // `asc + limit(200)` bug, which permanently hid every message past the
  // 200th once a conversation grew beyond that.
  const before = req.nextUrl.searchParams.get("before");
  const beforeDate = before ? new Date(before) : null;
  const validBefore = beforeDate && !isNaN(beforeDate.getTime()) ? beforeDate : null;

  const rows = await db
    .select()
    .from(messages)
    .where(
      validBefore
        ? and(eq(messages.conversationId, id), lt(messages.createdAt, validBefore))
        : eq(messages.conversationId, id)
    )
    .orderBy(desc(messages.createdAt))
    .limit(PAGE_SIZE);

  const ordered = rows.slice().reverse();
  const nextCursor = rows.length === PAGE_SIZE ? rows[rows.length - 1]!.createdAt.toISOString() : null;

  // Only mark the conversation read when loading the latest page, not when
  // paging through history.
  if (!validBefore) {
    await db
      .insert(conversationReads)
      .values({ conversationId: id, userId: session!.user.id, lastReadAt: new Date() })
      .onConflictDoUpdate({
        target: [conversationReads.conversationId, conversationReads.userId],
        set: { lastReadAt: new Date() },
      });
  }

  return NextResponse.json({ messages: ordered, nextCursor });
}

export async function POST(req: NextRequest, { params }: RouteContext) {
  const { session, response } = await requireSession();
  if (response) return response;
  const { id } = await params;

  const limited = await applyRateLimit(`rl:message:${session!.user.id}`, 30, 60);
  if (limited) return limited;

  const conversation = await db.query.conversations.findFirst({ where: eq(conversations.id, id) });
  if (!conversation || !isParticipant(conversation, session!.user.id)) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  const otherId = otherParticipantId(conversation, session!.user.id);
  if (await isBlockedEitherWay(session!.user.id, otherId)) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  const body = await req.json() as unknown;
  const parsed = Schema.safeParse(body);
  if (!parsed.success) return NextResponse.json({ error: "Validation error" }, { status: 400 });

  const messageId = randomUUID();
  await db.insert(messages).values({
    id: messageId,
    conversationId: id,
    senderId: session!.user.id,
    content: parsed.data.content,
  });
  await db.update(conversations).set({ lastMessageAt: new Date() }).where(eq(conversations.id, id));

  return NextResponse.json({ id: messageId }, { status: 201 });
}
