import { sql } from '@vercel/postgres';

import {
  cardNumbersEquivalent,
  findPrintingByCollectorNumber,
} from './card-number-utils.js';

function mapCardRow(card) {
  return {
    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,
    card_type: card.card_type,
    mana_cost: card.mana_cost,
    hp: card.power,
  };
}

function buildOcrPayload(fields) {
  return {
    name: fields.name?.trim() || null,
    set: fields.set || null,
    setCode: fields.setCode || null,
    cardNumber: fields.cardNumber || null,
    game: fields.game || null,
    cardType: fields.cardType || null,
    rarity: fields.rarity || null,
    hp: fields.hp || null,
    manaCost: fields.manaCost || null,
    rawText: fields.ocrData?.rawText || null,
    abilities: fields.ocrData?.abilities || [],
    flavorText: fields.ocrData?.flavorText || null,
    artist: fields.ocrData?.artist || null,
  };
}

export async function submitScanForReview(userId, fields, candidateIds = []) {
  const submissionId = await createCardSubmission(userId, fields, candidateIds);
  const label = [fields.name, fields.set || fields.setCode, fields.cardNumber]
    .filter(Boolean)
    .join(' · ');
  return {
    type: 'submitted',
    card: null,
    submissionId,
    needsReview: true,
    message: label
      ? `"${label}" is not in our catalog yet. Your scan was saved for admin review (submission #${submissionId}).`
      : `Your scan was saved for admin review (submission #${submissionId}).`,
  };
}

async function createCardSubmission(userId, fields, candidateIds = []) {
  const ocrPayload = buildOcrPayload(fields);
  const result = await sql`
    INSERT INTO card_submissions (
      user_id, ocr_text, ocr_confidence, scan_image_url,
      candidate_card_ids, ocr_payload, status
    ) VALUES (
      ${userId},
      ${fields.ocrData?.rawText || fields.name || null},
      ${fields.ocrData?.confidence ?? null},
      ${fields.scanImageUrl || null},
      ${JSON.stringify(candidateIds)},
      ${JSON.stringify(ocrPayload)},
      'pending'
    )
    RETURNING id
  `;
  return result.rows[0].id;
}

/**
 * Match OCR fields against the global cards catalog.
 * Never INSERTs into cards — unknowns become card_submissions.
 */
export async function matchCardInCatalog({
  userId,
  name,
  set,
  setCode,
  cardNumber,
  game,
  cardType,
  rarity,
  hp,
  manaCost,
  ocrData,
  scanImageUrl = null,
}) {
  if (!name || typeof name !== 'string' || !name.trim()) {
    return {
      type: 'needs_input',
      card: null,
      matches: [],
      needsUserInput: true,
      message: 'Card name is required',
    };
  }

  const trimmedName = name.trim();
  let existingCard = null;

  if ((set || setCode) && cardNumber) {
    const exactResult = await sql`
      SELECT * FROM cards
      WHERE LOWER(name) = LOWER(${trimmedName})
        AND (LOWER(set_name) = LOWER(${set || setCode}) OR LOWER(set_code) = LOWER(${setCode || set}))
        AND LOWER(card_number) = LOWER(${cardNumber})
      LIMIT 1
    `;
    if (exactResult.rows.length > 0) {
      existingCard = exactResult.rows[0];
    } else {
      const setCandidates = await sql`
        SELECT * FROM cards
        WHERE LOWER(name) = LOWER(${trimmedName})
          AND (
            LOWER(set_name) = LOWER(${set || setCode})
            OR LOWER(set_code) = LOWER(${setCode || set})
          )
      `;

      const normalizedMatch = findPrintingByCollectorNumber(setCandidates.rows, cardNumber);
      if (normalizedMatch) {
        existingCard = normalizedMatch;
      } else {
        const ambiguousMatches = setCandidates.rows.filter((row) =>
          cardNumbersEquivalent(row.card_number, cardNumber)
        );
        if (ambiguousMatches.length > 1) {
          return {
            type: 'disambiguation',
            card: null,
            matches: ambiguousMatches.map(mapCardRow),
            needsUserSelection: true,
            message: `Found ${ambiguousMatches.length} matches for "${trimmedName}" in that set. Select the correct printing.`,
          };
        }

        return submitScanForReview(
          userId,
          {
            name: trimmedName,
            set,
            setCode,
            cardNumber,
            game,
            cardType,
            rarity,
            hp,
            manaCost,
            ocrData,
            scanImageUrl,
          },
          []
        );
      }
    }
  }

  if (!existingCard && (set || setCode)) {
    const setResult = set
      ? await sql`
          SELECT * FROM cards
          WHERE LOWER(name) = LOWER(${trimmedName})
            AND (LOWER(set_name) = LOWER(${set}) OR LOWER(set_code) = LOWER(${setCode || set}))
          LIMIT 5
        `
      : await sql`
          SELECT * FROM cards
          WHERE LOWER(name) = LOWER(${trimmedName})
            AND LOWER(set_code) = LOWER(${setCode})
          LIMIT 5
        `;
    if (setResult.rows.length === 1) {
      existingCard = setResult.rows[0];
    } else if (setResult.rows.length > 1) {
      return {
        type: 'disambiguation',
        card: null,
        matches: setResult.rows.map(mapCardRow),
        needsUserSelection: true,
        message: `Found ${setResult.rows.length} matches for "${trimmedName}" in that set. Select the correct printing.`,
      };
    }
  }

  if (!existingCard) {
    const nameResult = game
      ? await sql`
          SELECT * FROM cards
          WHERE LOWER(name) = LOWER(${trimmedName})
          ORDER BY CASE WHEN game = ${game} THEN 0 ELSE 1 END, set_name, card_number
        `
      : await sql`
          SELECT * FROM cards
          WHERE LOWER(name) = LOWER(${trimmedName})
          ORDER BY set_name, card_number
        `;
    if (nameResult.rows.length === 1) {
      existingCard = nameResult.rows[0];
    } else if (nameResult.rows.length > 1) {
      return {
        type: 'disambiguation',
        card: null,
        matches: nameResult.rows.slice(0, 8).map(mapCardRow),
        needsUserSelection: true,
        message: `Found ${nameResult.rows.length} printings of "${trimmedName}". Select the correct card.`,
      };
    }
  }

  if (!existingCard) {
    const fuzzyResult = await sql`
      SELECT * FROM cards
      WHERE LOWER(name) ILIKE LOWER(${`%${trimmedName}%`})
      ORDER BY
        CASE
          WHEN LOWER(name) = LOWER(${trimmedName}) THEN 1
          WHEN LOWER(name) LIKE LOWER(${trimmedName + '%'}) THEN 2
          WHEN LOWER(name) LIKE LOWER(${'%' + trimmedName + '%'}) THEN 3
          ELSE 4
        END,
        CASE WHEN game = ${game || 'UNKNOWN'} THEN 1 ELSE 2 END,
        LENGTH(name)
      LIMIT 5
    `;

    if (fuzzyResult.rows.length > 0) {
      const exactFuzzyMatch = fuzzyResult.rows.find(
        (row) => row.name.toLowerCase() === trimmedName.toLowerCase()
      );

      if (exactFuzzyMatch && ocrData?.confidence >= 80) {
        existingCard = exactFuzzyMatch;
      } else if (ocrData?.confidence < 80 && fuzzyResult.rows.length > 1) {
        return {
          type: 'disambiguation',
          card: null,
          matches: fuzzyResult.rows.map(mapCardRow),
          needsUserSelection: true,
          message: `Found ${fuzzyResult.rows.length} possible matches for "${trimmedName}". Please select the correct card.`,
        };
      } else {
        existingCard = fuzzyResult.rows[0];
      }
    }
  }

  if (existingCard) {
    const hasSpecificPrinting = Boolean((set || setCode) && cardNumber);

    if (hasSpecificPrinting) {
      if (!cardNumbersEquivalent(existingCard.card_number, cardNumber)) {
        const siblingIds = await sql`
          SELECT id FROM cards WHERE LOWER(name) = LOWER(${trimmedName})
        `;
        return submitScanForReview(
          userId,
          {
            name: trimmedName,
            set,
            setCode,
            cardNumber,
            game,
            cardType,
            rarity,
            hp,
            manaCost,
            ocrData,
            scanImageUrl,
          },
          siblingIds.rows.map((row) => row.id)
        );
      }
    } else {
      const siblingsResult = await sql`
        SELECT * FROM cards
        WHERE LOWER(name) = LOWER(${trimmedName})
        ORDER BY set_name, card_number
      `;

      if (siblingsResult.rows.length > 1) {
        const ordered = [
          existingCard,
          ...siblingsResult.rows.filter((row) => row.id !== existingCard.id),
        ];
        return {
          type: 'disambiguation',
          card: null,
          matches: ordered.slice(0, 8).map(mapCardRow),
          needsUserSelection: true,
          message: `Found ${siblingsResult.rows.length} printings of "${trimmedName}". Confirm the correct one.`,
        };
      }
    }

    return {
      type: 'matched',
      card: existingCard,
      isExisting: true,
      message: `Found existing card: "${existingCard.name}"`,
    };
  }

  const confidenceThreshold = 75;
  if (!ocrData || ocrData.confidence < confidenceThreshold) {
    return {
      type: 'needs_input',
      card: null,
      matches: [],
      needsUserInput: true,
      message: `Could not find card "${trimmedName}" in database and confidence is low (${ocrData?.confidence || 0}%). Please verify the card name and try again.`,
    };
  }

  return submitScanForReview(
    userId,
    {
      name: trimmedName,
      set,
      setCode,
      cardNumber,
      game,
      cardType,
      rarity,
      hp,
      manaCost,
      ocrData,
      scanImageUrl,
    },
    []
  );
}

export async function logScanAttempt({
  userId,
  ocrText,
  ocrConfidence,
  layer = 2,
  matchedCardId = null,
  resultKind,
  latencyMs,
}) {
  try {
    await sql`
      INSERT INTO scan_attempts (
        user_id, ocr_text, ocr_confidence, layer,
        matched_card_id, result_kind, latency_ms
      ) VALUES (
        ${userId},
        ${ocrText || null},
        ${ocrConfidence ?? null},
        ${layer},
        ${matchedCardId},
        ${resultKind},
        ${latencyMs ?? null}
      )
    `;
  } catch (error) {
    // Telemetry must not block identification (e.g. migration not yet applied).
    console.error('[logScanAttempt]', error.message);
  }
}
