import { randomUUID } from "node:crypto";
import { getDb } from "@/lib/db";
import { embedFingerprint } from "@/lib/db/migrate";
import { slugify } from "@/lib/slug";
import { sanitizeGameText, translateFields, isAutoTranslateEnabled } from "@/lib/translate";
import { syncGameToAllSites } from "@/lib/sites";
import { ALL_SOURCES, ALL_MAX } from "./sources";
import type { ImportResult, NormalizedFeedGame } from "./types";

function compositeSourceId(source: string, id: string) {
  return `${source}:${id}`;
}

export async function importGamesFromFeeds(options: {
  maxPerSource?: number;
  sources?: string[];
  translateDuringImport?: boolean;
  mirrorThumbs?: boolean;
} = {}): Promise<ImportResult> {
  const db = getDb();
  const maxPerSource = options.maxPerSource ?? ALL_MAX;
  const sourceIds = options.sources ?? ALL_SOURCES.map((s) => s.id);
  const translate = options.translateDuringImport ?? false;
  const mirrorThumbs = options.mirrorThumbs ?? true;

  const currentInDb = (db.prepare("SELECT COUNT(*) AS c FROM games").get() as { c: number }).c;

  const result: ImportResult = {
    ok: true,
    inserted: 0,
    updated: 0,
    skipped: 0,
    total: 0,
    totalInDb: currentInDb,
    targetTotal: 0,
    bySource: {},
  };

  const knownFp = new Set(
    (
      db.prepare("SELECT embed_fingerprint FROM games WHERE embed_fingerprint IS NOT NULL").all() as {
        embed_fingerprint: string;
      }[]
    ).map((r) => r.embed_fingerprint),
  );

  const insertGame = db.prepare(`
    INSERT INTO games (
      id, slug, title, description, instructions, thumb_url, embed_url,
      width, height, category_id, source, source_id, embed_fingerprint
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  `);

  const cats = db.prepare("SELECT id, slug FROM categories").all() as { id: string; slug: string }[];
  const catBySlug = new Map(cats.map((c) => [c.slug, c.id]));
  const usedSlugs = new Set(
    (db.prepare("SELECT slug FROM games").all() as { slug: string }[]).map((r) => r.slug),
  );

  const pendingSync: Array<{
    gameId: string;
    slug: string;
    title: string;
    description: string | null;
    instructions: string | null;
  }> = [];

  for (const sourceDef of ALL_SOURCES) {
    if (!sourceIds.includes(sourceDef.id)) continue;

    const sourceStats = { fetched: 0, inserted: 0, skipped: 0 };
    let feed: NormalizedFeedGame[] = [];
    try {
      console.log(`[import] ${sourceDef.id}...`);
      feed = await sourceDef.fetch(maxPerSource);
    } catch (err) {
      console.warn(`[import] ${sourceDef.id} failed:`, err);
      result.bySource[sourceDef.id] = sourceStats;
      continue;
    }

    sourceStats.fetched = feed.length;
    result.total += feed.length;
    console.log(`[import] ${sourceDef.id}: fetched ${feed.length}`);

    for (const g of feed) {
      const fp = embedFingerprint(g.embedUrl);
      if (knownFp.has(fp)) {
        sourceStats.skipped++;
        result.skipped++;
        continue;
      }

      const existing = db
        .prepare("SELECT id FROM games WHERE embed_fingerprint = ? OR source_id = ?")
        .get(fp, compositeSourceId(g.source, g.sourceId));
      if (existing) {
        knownFp.add(fp);
        sourceStats.skipped++;
        result.skipped++;
        continue;
      }

      const categoryId = catBySlug.get(g.category) ?? catBySlug.get("arcade") ?? null;
      const title = sanitizeGameText(g.title) ?? g.title;
      const description = sanitizeGameText(g.description);
      const instructions = sanitizeGameText(g.instructions);

      const baseSlug = slugify(title);
      let slug = baseSlug || `game-${g.sourceId}`;
      let n = 2;
      while (usedSlugs.has(slug)) slug = `${baseSlug}-${n++}`;
      usedSlugs.add(slug);

      const gameId = randomUUID();
      const compositeId = compositeSourceId(g.source, g.sourceId);

      try {
        insertGame.run(
          gameId,
          slug,
          title,
          description,
          instructions,
          g.thumbUrl,
          g.embedUrl,
          g.width,
          g.height,
          categoryId,
          g.source,
          compositeId,
          fp,
        );
        knownFp.add(fp);
        sourceStats.inserted++;
        result.inserted++;
        pendingSync.push({ gameId, slug, title, description, instructions });
      } catch {
        sourceStats.skipped++;
        result.skipped++;
      }
    }

    result.bySource[sourceDef.id] = sourceStats;
    console.log(`[import] ${sourceDef.id}: +${sourceStats.inserted}, skip ${sourceStats.skipped}`);
  }

  for (const item of pendingSync) {
    syncGameToAllSites(item.gameId, item);
  }

  result.totalInDb = (db.prepare("SELECT COUNT(*) AS c FROM games").get() as { c: number }).c;
  result.targetTotal = result.totalInDb;

  if (result.inserted > 0 && translate) {
    for (const item of pendingSync) {
      const translated = await translateFields(
        { title: item.title, description: item.description, instructions: item.instructions },
        { force: isAutoTranslateEnabled() },
      );
      if (
        translated.title !== item.title ||
        translated.description !== item.description ||
        translated.instructions !== item.instructions
      ) {
        db.prepare("UPDATE games SET title = ?, description = ?, instructions = ? WHERE id = ?").run(
          translated.title,
          translated.description,
          translated.instructions,
          item.gameId,
        );
        db.prepare(
          `UPDATE site_games SET title = ?, description = ?, instructions = ? WHERE game_id = ?`,
        ).run(translated.title, translated.description, translated.instructions, item.gameId);
      }
    }
  }

  if (result.inserted > 0 && mirrorThumbs) {
    const { mirrorThumbsBatch } = await import("@/lib/media/thumbs");
    let mirrored = 0;
    while (true) {
      const batch = await mirrorThumbsBatch(300, 0);
      mirrored += batch.done;
      if (batch.done > 0) console.log(`[import] thumbs mirrored: ${mirrored}`);
      if (batch.done === 0) break;
    }
  }

  return result;
}
