/**
 * Bulk Pokémon catalog sync from pokemon-tcg-data GitHub JSON.
 *
 * Fetches all sets from sets/en.json, then upserts every card in each set
 * (keyed on scryfall_id = external card id). Populates legalities from set metadata.
 */

import { sql } from '../sql.js';

import {
  fetchPokemonSetCards,
  fetchPokemonSets,
  mapGithubCardForInsert,
} from './pokemon-github.js';

const DEFAULT_BATCH_SIZE = 100;
const DEFAULT_SET_DELAY_MS = 100;

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

function getSetLegalities(setMeta) {
  if (setMeta?.legalities && typeof setMeta.legalities === 'object') {
    return setMeta.legalities;
  }
  return {};
}

export function mapPokemonBulkRow(mapped, setLegalities) {
  return {
    scryfallId: mapped.externalId,
    name: mapped.name,
    setName: mapped.setName,
    setCode: mapped.setCode,
    cardNumber: mapped.cardNumber,
    rarity: mapped.rarity,
    cardType: mapped.cardType,
    colors: JSON.stringify(mapped.types || []),
    oracleText: mapped.flavorText,
    power: mapped.hp,
    imageUrl: mapped.imageSmall,
    stockImageUrl: mapped.imageLarge,
    legalities: JSON.stringify(setLegalities),
  };
}

function buildUpsertQuery(batch) {
  const columns = [
    'scryfall_id', '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', 'legalities', 'verified',
  ];

  const placeholders = [];
  const values = [];
  let paramIdx = 1;

  for (const row of batch) {
    const rowPlaceholders = [];
    const rowValues = [
      row.scryfallId,
      row.name,
      row.setName,
      row.setCode,
      row.cardNumber,
      row.rarity,
      'Pokemon',
      null,
      null,
      row.cardType,
      row.colors,
      row.oracleText,
      row.power,
      null,
      row.imageUrl,
      row.stockImageUrl,
      null,
      null,
      row.legalities,
      true,
    ];

    for (let i = 0; i < rowValues.length; i += 1) {
      rowPlaceholders.push(`$${paramIdx}`);
      paramIdx += 1;
    }
    placeholders.push(`(${rowPlaceholders.join(', ')})`);
    values.push(...rowValues);
  }

  const updateCols = columns
    .filter((c) => c !== 'scryfall_id')
    .map((c) => `${c} = EXCLUDED.${c}`)
    .join(', ');

  const query = `
    INSERT INTO cards (${columns.join(', ')})
    VALUES ${placeholders.join(',\n')}
    ON CONFLICT (scryfall_id) DO UPDATE SET
      ${updateCols},
      updated_at = CURRENT_TIMESTAMP
  `;

  return { query, values };
}

async function upsertBatch(batch, queryFn, stats) {
  if (batch.length === 0) return;

  if (stats.dryRun) {
    stats.skipped += batch.length;
    return;
  }

  const { query, values } = buildUpsertQuery(batch);
  try {
    await queryFn(query, values);
    stats.upserted += batch.length;
  } catch (error) {
    console.error(`[pokemon-bulk] Batch failed (${batch.length} rows):`, error.message);
    for (const row of batch) {
      try {
        const { query: singleQ, values: singleV } = buildUpsertQuery([row]);
        await queryFn(singleQ, singleV);
        stats.upserted += 1;
      } catch (singleErr) {
        console.error(`[pokemon-bulk] Failed: ${row.name} (${row.scryfallId}):`, singleErr.message);
        stats.errors += 1;
      }
    }
  }
}

async function upsertSetCards(setMeta, cards, options, stats) {
  const setLegalities = getSetLegalities(setMeta);
  let batch = [];

  for (const card of cards) {
    const mapped = mapGithubCardForInsert(card, setMeta);
    batch.push(mapPokemonBulkRow(mapped, setLegalities));

    if (batch.length >= options.batchSize) {
      await upsertBatch(batch, options.queryFn, stats);
      batch = [];
    }
  }

  if (batch.length > 0) {
    await upsertBatch(batch, options.queryFn, stats);
  }
}

/**
 * Run a full Pokémon bulk sync from pokemon-tcg-data GitHub.
 * Returns a summary compatible with multi-game catalog sync orchestration.
 */
export async function runBulkPokemonSync(options = {}) {
  const batchSize = options.batchSize ?? parseInt(process.env.BATCH_SIZE || String(DEFAULT_BATCH_SIZE), 10);
  const dryRun = options.dryRun ?? process.env.DRY_RUN === 'true';
  const setDelayMs = options.setDelayMs ?? DEFAULT_SET_DELAY_MS;
  const queryFn = options.query ?? ((query, values) => sql.query(query, values));

  const sets = await fetchPokemonSets();
  console.log(`[pokemon-bulk] Found ${sets.length} sets in pokemon-tcg-data`);

  const stats = {
    dryRun,
    upserted: 0,
    skipped: 0,
    errors: 0,
    setsProcessed: 0,
  };

  for (let index = 0; index < sets.length; index += 1) {
    const setMeta = sets[index];
    const setCode = setMeta.id;

    if (!setCode) {
      console.warn('[pokemon-bulk] Skipping set with no id');
      continue;
    }

    try {
      const cards = await fetchPokemonSetCards(setCode);
      stats.setsProcessed += 1;

      if (cards.length === 0) {
        console.log(`[pokemon-bulk] Set ${setCode}: no cards (empty or missing file)`);
      } else {
        await upsertSetCards(setMeta, cards, { batchSize, queryFn }, stats);
        console.log(`[pokemon-bulk] Set ${setCode}: processed ${cards.length} cards`);
      }
    } catch (error) {
      console.error(`[pokemon-bulk] Set ${setCode} failed:`, error.message);
      stats.errors += 1;
    }

    if (index < sets.length - 1) {
      await delay(setDelayMs);
    }
  }

  const summary = {
    mode: 'bulk',
    game: 'pokemon',
    setsProcessed: stats.setsProcessed,
    totalSets: sets.length,
    upserted: stats.upserted,
    skipped: stats.skipped,
    errors: stats.errors,
    dryRun,
  };

  console.log('[pokemon-bulk]', JSON.stringify(summary));
  return summary;
}
