import { sql } from '@vercel/postgres'; import { getUserFromRequest } from '../../../lib/permission-middleware'; export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } try { const user = await getUserFromRequest(req); if (!user) { return res.status(401).json({ error: 'Authentication required' }); } const { name, set, setCode, cardNumber, game, cardType, rarity, hp, manaCost, ocrData } = req.body; if (!name) { return res.status(400).json({ error: 'Card name is required' }); } console.log(`🔍 Looking for card: "${name}" | Set: "${set || setCode}" | Number: "${cardNumber}" | Game: "${game}"`); // First, try exact match by name, set, and card number (most specific) let existingCard = null; if ((set || setCode) && cardNumber) { console.log('🎯 Trying exact match with card number...'); const exactQuery = sql` SELECT * FROM cards WHERE LOWER(name) = LOWER(${name}) AND (LOWER(set_name) = LOWER(${set || setCode}) OR LOWER(set_code) = LOWER(${setCode || set})) AND LOWER(card_number) = LOWER(${cardNumber}) LIMIT 1 `; const exactResult = await exactQuery; if (exactResult.rows.length > 0) { existingCard = exactResult.rows[0]; console.log('✅ Found exact match with card number:', existingCard.name); } } // Second, try exact match by name and set (without card number) if (!existingCard && (set || setCode)) { console.log('🎯 Trying exact match by name and set...'); const setQuery = set ? sql`SELECT * FROM cards WHERE LOWER(name) = LOWER(${name}) AND (LOWER(set_name) = LOWER(${set}) OR LOWER(set_code) = LOWER(${setCode || set})) LIMIT 1` : sql`SELECT * FROM cards WHERE LOWER(name) = LOWER(${name}) AND LOWER(set_code) = LOWER(${setCode}) LIMIT 1`; const setResult = await setQuery; if (setResult.rows.length > 0) { existingCard = setResult.rows[0]; console.log('✅ Found exact match by name and set:', existingCard.name); } } // Third, try exact name match (any set) if (!existingCard) { console.log('🎯 Trying exact name match (any set)...'); const nameQuery = sql` SELECT * FROM cards WHERE LOWER(name) = LOWER(${name}) ORDER BY CASE WHEN game = ${game || 'UNKNOWN'} THEN 1 ELSE 2 END, created_at DESC LIMIT 1 `; const nameResult = await nameQuery; if (nameResult.rows.length > 0) { existingCard = nameResult.rows[0]; console.log('✅ Found exact name match:', existingCard.name); } } // Fourth, try fuzzy name matching with game preference if (!existingCard) { console.log('🎯 Trying fuzzy name matching...'); const fuzzyResult = await sql` SELECT * FROM cards WHERE LOWER(name) ILIKE LOWER(${`%${name}%`}) ORDER BY CASE WHEN LOWER(name) = LOWER(${name}) THEN 1 WHEN LOWER(name) LIKE LOWER(${name + '%'}) THEN 2 WHEN LOWER(name) LIKE LOWER(${'%' + name + '%'}) THEN 3 ELSE 4 END, CASE WHEN game = ${game || 'UNKNOWN'} THEN 1 ELSE 2 END, LENGTH(name) LIMIT 5 `; if (fuzzyResult.rows.length > 0) { console.log(`🔍 Found ${fuzzyResult.rows.length} fuzzy matches`); // If we have high confidence and an exact match, use it const exactFuzzyMatch = fuzzyResult.rows.find(row => row.name.toLowerCase() === name.toLowerCase() ); if (exactFuzzyMatch && ocrData?.confidence >= 80) { existingCard = exactFuzzyMatch; console.log('✅ Using high-confidence fuzzy exact match:', existingCard.name); } else if (ocrData?.confidence < 80 && fuzzyResult.rows.length > 1) { // Low confidence with multiple matches - let user choose console.log('⚠️ Multiple matches with low confidence - requiring user selection'); return res.status(200).json({ card: null, matches: fuzzyResult.rows.map(card => ({ id: card.id, name: card.name, set_name: card.set_name, set_code: card.set_code, card_number: card.card_number, game: card.game, rarity: card.rarity, image_url: card.image_url })), needsUserSelection: true, message: `Found ${fuzzyResult.rows.length} possible matches for "${name}". Please select the correct card.` }); } else { // Use the best match existingCard = fuzzyResult.rows[0]; console.log('✅ Using best fuzzy match:', existingCard.name); } } } // If we found an existing card, return it if (existingCard) { console.log('🎉 Returning existing card:', existingCard.name); return res.status(200).json({ card: existingCard, isExisting: true, message: `Found existing card: "${existingCard.name}"` }); } // If no existing card found, decide whether to create a new one const confidenceThreshold = 75; // Increased threshold for better accuracy if (!ocrData || ocrData.confidence < confidenceThreshold) { console.log(`❌ No match found and confidence too low (${ocrData?.confidence || 0}% < ${confidenceThreshold}%)`); return res.status(200).json({ card: null, matches: [], needsUserInput: true, message: `Could not find card "${name}" in database and confidence is low (${ocrData?.confidence || 0}%). Please verify the card name and try again.` }); } // Create new card entry with enhanced data console.log('🆕 Creating new card from OCR data...'); const newCardResult = await sql` INSERT INTO cards ( name, set_name, set_code, card_number, rarity, game, mana_cost, cmc, card_type, colors, oracle_text, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified ) VALUES ( ${name.trim()}, ${set || null}, ${setCode || null}, ${cardNumber || null}, ${rarity || null}, ${game || 'UNKNOWN'}, ${manaCost || null}, ${null}, -- cmc (calculated from mana cost) ${cardType || null}, ${null}, -- colors (unknown from OCR) ${ocrData?.rawText || null}, -- Store OCR text in oracle_text temporarily ${hp || null}, -- power (HP for Pokemon) ${null}, -- toughness ${null}, -- image_url (to be fetched later) ${null}, -- stock_image_url ${null}, -- current_price ${null}, -- market_price ${null}, -- scryfall_id (to be populated later) ${false} -- not verified since it's from OCR ) RETURNING * `; const newCard = newCardResult.rows[0]; // Log the OCR creation for potential review console.log(`✅ Created new card from OCR: ${name} (${game}) - Confidence: ${ocrData?.confidence}%`); return res.status(201).json({ card: newCard, isExisting: false, message: `Created new card "${name}" from scan data. This card may need verification.` }); } catch (error) { console.error('Error in find-or-create card:', error); return res.status(500).json({ error: 'Internal server error' }); } }