import { NextResponse } from "next/server" import { auth } from "@/lib/auth" import { prisma } from "@/lib/prisma" import { decrypt } from "@/lib/encryption" import { createProvider } from "@/lib/ai/provider-factory" import { rateLimit } from "@/lib/rate-limit" import { COCKTAIL_RECIPE_PROMPT, buildBarInventoryString } from "@/lib/ai/prompts" import { fuzzyMatchIngredients, recalculateMissingCount } from "@/lib/ingredient-matcher" import { z } from "zod" const recreateSchema = z.object({ cocktailName: z.string().min(1).max(200), drinkId: z.string().optional(), }) export async function POST(request: Request) { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) } const { success: withinLimit } = rateLimit(`bartender-recreate:${session.user.id}`, 10, 60000) if (!withinLimit) { return NextResponse.json( { error: "Too many requests. Please wait a moment." }, { status: 429 } ) } try { const body = await request.json() const parsed = recreateSchema.safeParse(body) if (!parsed.success) { return NextResponse.json({ error: "Invalid request" }, { status: 400 }) } const apiKeyRecord = await prisma.userApiKey.findFirst({ where: { userId: session.user.id, isActive: true }, }) if (!apiKeyRecord) { return NextResponse.json( { error: "No AI provider configured. Add an API key in Settings." }, { status: 400 } ) } const barItems = await prisma.barItem.findMany({ where: { userId: session.user.id, quantity: { not: "EMPTY" }, }, select: { name: true, category: true, quantity: true }, }) const inventoryString = buildBarInventoryString(barItems) const prompt = COCKTAIL_RECIPE_PROMPT.replace("{barInventory}", inventoryString) const apiKey = decrypt(apiKeyRecord.encryptedKey, apiKeyRecord.iv) const provider = createProvider(apiKeyRecord.provider, apiKey) const rawResponse = await provider.sendTextRequest( prompt, `Generate a recipe for: ${parsed.data.cocktailName}` ) // Parse JSON from response let recipe try { recipe = JSON.parse(rawResponse) } catch { // Try to extract from markdown code blocks or find JSON object const codeBlockMatch = rawResponse.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/) if (codeBlockMatch) { recipe = JSON.parse(codeBlockMatch[1].trim()) } else { const objectMatch = rawResponse.match(/\{[\s\S]*\}/) if (objectMatch) { recipe = JSON.parse(objectMatch[0]) } else { throw new Error("Could not parse recipe from AI response") } } } // Post-process: fuzzy-match ingredients against bar inventory if (recipe.ingredients && Array.isArray(recipe.ingredients) && barItems.length > 0) { recipe.ingredients = fuzzyMatchIngredients(recipe.ingredients, barItems) recipe.missingCount = recalculateMissingCount(recipe.ingredients) } return NextResponse.json(recipe) } catch (error) { console.error("Bartender recreate error:", error) return NextResponse.json( { error: "Failed to generate recipe. Please try again." }, { status: 500 } ) } }