import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { db, collections, collectionRecipes, recipes, ratings, eq, and, or, ne, inArray, isNotNull, sql } from "@epicure/db";
import { requireSessionOrApiKey } from "@/lib/api-auth";
import { deleteObject } from "@/lib/storage";

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

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

  const col = await db.query.collections.findFirst({
    where: and(eq(collections.id, id), eq(collections.userId, session!.user.id)),
    with: { recipes: { with: { recipe: { with: { photos: true } } } } },
  });

  if (!col) return NextResponse.json({ error: "Not found" }, { status: 404 });
  return NextResponse.json(col);
}

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

  const existing = await db.query.collections.findFirst({
    where: and(eq(collections.id, id), eq(collections.userId, session!.user.id)),
  });
  if (!existing) return NextResponse.json({ error: "Not found" }, { status: 404 });

  const body = await req.json() as unknown;
  const parsed = z.object({
    name: z.string().min(1).max(100).optional(),
    description: z.string().max(500).nullable().optional(),
    notes: z.string().max(2000).nullable().optional(),
    tags: z.array(z.string().min(1).max(50)).max(20).optional(),
    visibility: z.enum(["private", "unlisted", "public", "followers"]).optional(),
    addRecipeId: z.string().optional(),
    addRecipeIds: z.array(z.string()).max(200).optional(),
    removeRecipeId: z.string().optional(),
    removeRecipeIds: z.array(z.string()).max(200).optional(),
  }).safeParse(body);
  if (!parsed.success) return NextResponse.json({ error: "Validation error" }, { status: 400 });

  const data = parsed.data;
  if (data.name || data.description !== undefined || data.notes !== undefined || data.tags !== undefined || data.visibility !== undefined) {
    await db.update(collections).set({
      ...(data.name && { name: data.name }),
      ...(data.description !== undefined && { description: data.description }),
      ...(data.notes !== undefined && { notes: data.notes }),
      ...(data.tags !== undefined && { tags: data.tags }),
      ...(data.visibility !== undefined && { visibility: data.visibility }),
      updatedAt: new Date(),
    }).where(eq(collections.id, id));
  }

  const idsToAdd = [...(data.addRecipeId ? [data.addRecipeId] : []), ...(data.addRecipeIds ?? [])];
  if (idsToAdd.length > 0) {
    const owned = await db.query.recipes.findMany({
      where: and(
        inArray(recipes.id, idsToAdd),
        or(eq(recipes.authorId, session!.user.id), ne(recipes.visibility, "private"))
      ),
      columns: { id: true },
    });
    if (owned.length > 0) {
      const [maxPositionRow] = await db
        .select({ max: sql<number | null>`max(${collectionRecipes.position})` })
        .from(collectionRecipes)
        .where(eq(collectionRecipes.collectionId, id));
      let nextPosition = (maxPositionRow?.max ?? -1) + 1;
      await db.insert(collectionRecipes)
        .values(owned.map((r) => ({ collectionId: id, recipeId: r.id, position: nextPosition++ })))
        .onConflictDoNothing();
    }
  }

  const idsToRemove = [...(data.removeRecipeId ? [data.removeRecipeId] : []), ...(data.removeRecipeIds ?? [])];
  if (idsToRemove.length > 0) {
    await db.delete(collectionRecipes).where(
      and(eq(collectionRecipes.collectionId, id), inArray(collectionRecipes.recipeId, idsToRemove))
    );
  }

  return NextResponse.json({ updated: true });
}

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

  const existing = await db.query.collections.findFirst({
    where: and(eq(collections.id, id), eq(collections.userId, session!.user.id)),
  });
  if (!existing) return NextResponse.json({ error: "Not found" }, { status: 404 });

  const deleteRecipes = req.nextUrl.searchParams.get("deleteRecipes") === "true";

  if (deleteRecipes) {
    // Only recipes this user actually owns — a collection can contain other
    // people's (non-private) recipes added via sharing/collab, and those
    // must never be deleted just because this user deletes their collection.
    const owned = await db.query.collectionRecipes.findMany({
      where: eq(collectionRecipes.collectionId, id),
      with: { recipe: { columns: { id: true, authorId: true }, with: { photos: true } } },
    });
    const ownRecipes = owned.flatMap((r) => (r.recipe && r.recipe.authorId === session!.user.id ? [r.recipe] : []));

    if (ownRecipes.length > 0) {
      const recipeIds = ownRecipes.map((r) => r.id);
      const reviewPhotos = await db
        .select({ photoKey: ratings.photoKey })
        .from(ratings)
        .where(and(inArray(ratings.recipeId, recipeIds), isNotNull(ratings.photoKey)));
      const storageKeys = [
        ...ownRecipes.flatMap((r) => r.photos.map((p) => p.storageKey)),
        ...reviewPhotos.map((r) => r.photoKey).filter((k): k is string => k !== null),
      ];

      await db.delete(recipes).where(inArray(recipes.id, recipeIds));

      for (const key of storageKeys) {
        try {
          await deleteObject(key);
        } catch (err) {
          console.error(`Failed to delete storage object ${key} while deleting collection ${id}`, err);
        }
      }
    }
  }

  await db.delete(collections).where(eq(collections.id, id));
  return new NextResponse(null, { status: 204 });
}
