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

import { runBulkMtgSync } from './bulk-sync.js';
import { runBulkLorcanaSync } from './lorcana-bulk.js';
import { runBulkPokemonSync } from './pokemon-bulk.js';
import { discoverMissingMtgSets, discoverMissingPokemonSets } from './discover.js';
import { importMtgSet } from './mtg.js';
import { importPokemonSet } from './pokemon.js';
import { reconcileSubmissionsForImportedSet } from './reconcile-submissions.js';

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

const DEFAULT_MAX_SETS_PER_RUN = 3;
const DEFAULT_DELAY_MS = 1500;

/**
 * Merge missing MTG + Pokémon sets and pick the newest releases first.
 */
export function buildCatalogSyncQueue(missingMtg, missingPokemon, maxSetsPerRun = DEFAULT_MAX_SETS_PER_RUN) {
  const queue = [
    ...missingMtg.map((set) => ({
      game: 'mtg',
      setCode: set.code,
      name: set.name,
      releasedAt: set.releasedAt,
    })),
    ...missingPokemon.map((set) => ({
      game: 'pokemon',
      setCode: set.id,
      name: set.name,
      releasedAt: set.releasedAt,
    })),
  ];

  queue.sort((a, b) => {
    const dateA = a.releasedAt || '';
    const dateB = b.releasedAt || '';
    if (!dateA && !dateB) return 0;
    if (!dateA) return 1;
    if (!dateB) return -1;
    return dateB.localeCompare(dateA);
  });

  return queue.slice(0, maxSetsPerRun);
}

function countGameErrors(result) {
  if (!result) return 0;
  if (result.error) return 1;
  if (typeof result.errors === 'number') return result.errors;
  if (Array.isArray(result.errors)) return result.errors.length;
  return 0;
}

/**
 * Persist one game's sync result to catalog_sync_log.
 */
export async function logCatalogSyncRun(game, result) {
  if (!result) return;

  const upserted = result.upserted ?? result.imported ?? 0;
  const errorCount = countGameErrors(result);
  const mode = result.mode ?? 'bulk';
  const sourceUpdatedAt = result.updatedAt ?? null;

  try {
    await sql`
      INSERT INTO catalog_sync_log (game, mode, upserted, errors, source_updated_at, ran_at, details)
      VALUES (
        ${game},
        ${mode},
        ${upserted},
        ${errorCount},
        ${sourceUpdatedAt},
        CURRENT_TIMESTAMP,
        ${JSON.stringify(result)}
      )
    `;
  } catch (error) {
    console.error(`[logCatalogSyncRun] Failed to log ${game} sync:`, error.message);
  }
}

async function runGameSync(game, syncFn, options) {
  try {
    return await syncFn(options);
  } catch (error) {
    console.error(`[runUnifiedCatalogSync] ${game} failed:`, error);
    return { mode: 'bulk', error: error.message };
  }
}

/**
 * Run MTG, Pokémon, and Lorcana bulk syncs in sequence.
 * Per-game failures are isolated; results are logged to catalog_sync_log.
 */
export async function runUnifiedCatalogSync(options = {}) {
  const startedAt = Date.now();

  const summary = {
    mode: 'unified',
    mtg: null,
    pokemon: null,
    lorcana: null,
    errors: [],
    durationMs: 0,
  };

  summary.mtg = await runGameSync('mtg', runBulkMtgSync, options);
  if (summary.mtg.error) {
    summary.errors.push({ game: 'mtg', message: summary.mtg.error });
  }

  summary.pokemon = await runGameSync('pokemon', runBulkPokemonSync, options);
  if (summary.pokemon.error) {
    summary.errors.push({ game: 'pokemon', message: summary.pokemon.error });
  }

  summary.lorcana = await runGameSync('lorcana', runBulkLorcanaSync, options);
  if (summary.lorcana.error) {
    summary.errors.push({ game: 'lorcana', message: summary.lorcana.error });
  }

  summary.durationMs = Date.now() - startedAt;

  await Promise.all([
    logCatalogSyncRun('mtg', summary.mtg),
    logCatalogSyncRun('pokemon', summary.pokemon),
    logCatalogSyncRun('lorcana', summary.lorcana),
  ]);

  console.log('[runUnifiedCatalogSync]', JSON.stringify(summary));
  return summary;
}

/**
 * Discover missing MTG + Pokémon sets and import up to maxSetsPerRun, paced for upstream APIs.
 */
export async function runCatalogSync(options = {}) {
  const maxSetsPerRun = options.maxSetsPerRun ?? DEFAULT_MAX_SETS_PER_RUN;
  const delayBetweenSetsMs = options.delayBetweenSetsMs ?? DEFAULT_DELAY_MS;

  const [missingMtg, missingPokemon] = await Promise.all([
    discoverMissingMtgSets(),
    discoverMissingPokemonSets(),
  ]);

  const queue = buildCatalogSyncQueue(missingMtg, missingPokemon, maxSetsPerRun);

  const summary = {
    imported: 0,
    skipped: 0,
    errors: [],
    setsProcessed: [],
    pendingMtgSets: missingMtg.length,
    pendingPokemonSets: missingPokemon.length,
    lorcana: 'skipped — manual Lorcana set map update required',
    submissionsReconciled: 0,
    reconciliation: [],
  };

  for (let index = 0; index < queue.length; index += 1) {
    const item = queue[index];
    try {
      const result =
        item.game === 'mtg'
          ? await importMtgSet(item.setCode)
          : await importPokemonSet(item.setCode);

      summary.setsProcessed.push({
        game: item.game,
        setCode: item.setCode,
        name: item.name,
        ...result,
      });
      summary.imported += result.imported;
      summary.skipped += result.skipped;

      const reconcileResult = await reconcileSubmissionsForImportedSet({
        game: item.game,
        setCode: item.setCode,
        setName: item.name,
      });
      summary.submissionsReconciled += reconcileResult.matched;
      if (reconcileResult.matched > 0 || reconcileResult.skipped > 0) {
        summary.reconciliation.push(reconcileResult);
      }
    } catch (error) {
      console.error(`[runCatalogSync] Failed ${item.game}/${item.setCode}:`, error);
      summary.errors.push({
        game: item.game,
        setCode: item.setCode,
        message: error.message,
      });
    }

    if (index < queue.length - 1) {
      await delay(delayBetweenSetsMs);
    }
  }

  console.log('[runCatalogSync]', JSON.stringify(summary));
  return summary;
}
