import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { db, recipes, ratings, eq, and } from "@epicure/db";
import { requireSessionOrApiKey } from "@/lib/api-auth";
import { createNotification } from "@/lib/notifications";
import { isOwnedReviewPhotoKey } from "@/lib/storage";
import { checkTierLimitInTransaction, TierLimitError } from "@/lib/tiers";

const Schema = z.object({
  score: z.number().int().min(1).max(5),
  reviewText: z.string().max(2000).optional(),
  photoKey: z.string().max(500).optional(),
  photoSizeMb: z.number().int().min(0).max(50).default(0),
});

type Params = { params: Promise<{ id: string }> };

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

  const recipe = await db.query.recipes.findFirst({ where: eq(recipes.id, id) });
  if (!recipe || (recipe.visibility === "private" && recipe.authorId !== session!.user.id)) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }
  if (recipe.authorId === session!.user.id) {
    return NextResponse.json({ error: "Cannot rate your own recipe" }, { status: 400 });
  }

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

  if (parsed.data.photoKey && !isOwnedReviewPhotoKey(parsed.data.photoKey, id, session!.user.id)) {
    return NextResponse.json({ error: "Validation error", issues: [{ path: ["photoKey"], message: "Photo key not issued for this review" }] }, { status: 400 });
  }

  const existing = await db.query.ratings.findFirst({
    where: and(eq(ratings.recipeId, id), eq(ratings.userId, session!.user.id)),
  });

  const newPhotoSizeMb = parsed.data.photoKey ? parsed.data.photoSizeMb : 0;
  const photoSizeDeltaMb = newPhotoSizeMb - (existing?.photoSizeMb ?? 0);

  try {
    if (existing) {
      await db.transaction(async (tx) => {
        if (photoSizeDeltaMb > 0) {
          await checkTierLimitInTransaction(tx, session!.user.id, session!.user.tier as "free" | "pro" | "family", "storage", photoSizeDeltaMb);
        }
        await tx.update(ratings)
          .set({
            score: parsed.data.score,
            reviewText: parsed.data.reviewText,
            photoKey: parsed.data.photoKey,
            photoSizeMb: newPhotoSizeMb,
            updatedAt: new Date(),
          })
          .where(eq(ratings.id, existing.id));
      });
      return NextResponse.json({ updated: true });
    }

    await db.transaction(async (tx) => {
      if (photoSizeDeltaMb > 0) {
        await checkTierLimitInTransaction(tx, session!.user.id, session!.user.tier as "free" | "pro" | "family", "storage", photoSizeDeltaMb);
      }
      await tx.insert(ratings).values({
        id: crypto.randomUUID(),
        recipeId: id,
        userId: session!.user.id,
        score: parsed.data.score,
        reviewText: parsed.data.reviewText,
        photoKey: parsed.data.photoKey,
        photoSizeMb: newPhotoSizeMb,
      });
    });
  } catch (err) {
    if (err instanceof TierLimitError) {
      return NextResponse.json({ error: "Storage limit reached for your tier" }, { status: 403 });
    }
    throw err;
  }

  void createNotification({ userId: recipe.authorId, type: "rating", actorId: session!.user.id, recipeId: id, score: parsed.data.score });
  return NextResponse.json({ created: true }, { status: 201 });
}
