import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { db, shoppingLists, shoppingListItems, mealPlans, mealPlanEntries, recipeIngredients, pantryItems, eq, and, desc, inArray } from "@epicure/db";
import { requireSessionOrApiKey } from "@/lib/api-auth";
import { applyPantryToItems, mergeIngredients } from "@/lib/pantry-shopping-match";
import { guessAisle } from "@/lib/grocery-categories";
import { loadIngredientAliasIndex } from "@/lib/ingredient-match";

const CreateSchema = z.object({
  name: z.string().min(1).max(100),
  items: z.array(z.object({
    rawName: z.string().min(1),
    quantity: z.string().optional(),
    unit: z.string().optional(),
    aisle: z.string().optional(),
  })).optional(),
  fromMealPlanWeek: z.string().optional(),
});

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

  const lists = await db.query.shoppingLists.findMany({
    where: eq(shoppingLists.userId, session!.user.id),
    orderBy: desc(shoppingLists.createdAt),
    with: { items: { orderBy: (t, { asc }) => asc(t.aisle) } },
  });

  return NextResponse.json(lists);
}

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

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

  const data = parsed.data;
  const listId = crypto.randomUUID();

  let items: Array<{ rawName: string; quantity?: string; unit?: string; aisle?: string; inPantry?: boolean }> = data.items ?? [];

  if (data.fromMealPlanWeek) {
    const plan = await db.query.mealPlans.findFirst({
      where: and(eq(mealPlans.userId, session!.user.id), eq(mealPlans.weekStart, data.fromMealPlanWeek)),
      with: { entries: true },
    });

    if (plan) {
      const recipeIds = plan.entries.map((e) => e.recipeId).filter(Boolean) as string[];
      if (recipeIds.length > 0) {
        const ings = await db.query.recipeIngredients.findMany({
          where: inArray(recipeIngredients.recipeId, recipeIds),
        });

        // Merge same ingredient across all of the week's recipes, summing quantities
        // (grouped by ingredientId when available, else normalized name+unit) instead
        // of listing it once per recipe or dropping every occurrence but the first.
        const mergedItems = mergeIngredients(ings);

        // Reduce/flag quantities already covered by the user's pantry. Conservative: never silently
        // drops an item — fully-covered items are still inserted, flagged `inPantry`, so nothing
        // disappears from view without the user seeing it.
        const [pantry, aliasIndex] = await Promise.all([
          db.query.pantryItems.findMany({ where: eq(pantryItems.userId, session!.user.id) }),
          loadIngredientAliasIndex(),
        ]);
        items = applyPantryToItems(mergedItems, pantry, aliasIndex);
      }
    }
  }

  await db.insert(shoppingLists).values({
    id: listId,
    userId: session!.user.id,
    name: data.name,
    generatedAt: data.fromMealPlanWeek ? new Date() : undefined,
  });

  if (items.length > 0) {
    await db.insert(shoppingListItems).values(
      items.map((item, index) => ({
        id: crypto.randomUUID(),
        listId,
        rawName: item.rawName,
        quantity: item.quantity,
        unit: item.unit,
        // Never override an explicit aisle — only guess one when the item doesn't have one
        // (which today is every item coming from the meal-plan generation path).
        aisle: item.aisle ?? guessAisle(item.rawName) ?? undefined,
        checked: item.inPantry === true && !item.quantity,
        inPantry: item.inPantry ?? false,
        sortOrder: index,
      }))
    );
  }

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