import { createHash } from "node:crypto";
import { translate as gtxLib } from "google-translate-api-x";
import { getDb } from "@/lib/db";

const INSTRUCTION_MAP: Record<string, string> = {
  clicker: "Кликер",
  mouse: "Мышь",
  keyboard: "Клавиатура",
  tap: "Нажимай",
  click: "Кликай",
  drag: "Перетаскивай",
  swipe: "Свайпай",
  touch: "Касание",
  arrows: "Стрелки",
  "arrow keys": "Стрелки",
  "space bar": "Пробел",
  space: "Пробел",
  play: "Играть",
  jump: "Прыжок",
  shoot: "Стреляй",
  move: "Движение",
  "use mouse": "Используй мышь",
  "use keyboard": "Используй клавиатуру",
  "mouse click or tap to play": "Щелкните мышью или коснитесь, чтобы играть",
  "click or tap to play": "Щелкните или коснитесь, чтобы играть",
  "tap to play": "Коснитесь, чтобы играть",
  "click to play": "Щелкните, чтобы играть",
  "use mouse to play": "Используйте мышь для игры",
  "use arrow keys to move": "Используйте стрелки для движения",
  "use arrow keys": "Используйте стрелки",
  "use the mouse": "Используйте мышь",
  "use the keyboard": "Используйте клавиатуру",
};

let lastTranslateAt = 0;
const MIN_GAP_MS = 40;
const POOL = 6;

function decodeEntitiesOnce(text: string): string {
  return text
    .replace(/&amp;/g, "&")
    .replace(/&lt;/g, "<")
    .replace(/&gt;/g, ">")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/&apos;/g, "'")
    .replace(/&nbsp;/g, " ")
    .replace(/&mdash;/g, "—")
    .replace(/&ndash;/g, "–")
    .replace(/&rsquo;/g, "'")
    .replace(/&lsquo;/g, "'")
    .replace(/&ldquo;/g, '"')
    .replace(/&rdquo;/g, '"')
    .replace(/&#(\d+);/g, (_, n) => String.fromCharCode(Number(n)))
    .replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCharCode(parseInt(h, 16)));
}

export function sanitizeGameText(text: string | null | undefined): string | null {
  if (!text) return null;
  let s = text.replace(/<[^>]*>/gi, " ");
  s = s.replace(/\b(?:src|href|data-[a-z-]+)\s*=\s*["'][^"']*["']/gi, " ");
  s = s.replace(/https?:\/\/[^\s<>"']+/gi, " ");
  s = s.replace(/\b(?:src|href)\s*=\s*[^\s]+/gi, " ");
  for (let i = 0; i < 4; i++) {
    const next = decodeEntitiesOnce(s);
    if (next === s) break;
    s = next;
  }
  s = s.replace(/\s+/g, " ").trim();
  return s || null;
}

export function normalizeInstructions(text: string | null | undefined): string | null {
  let s = sanitizeGameText(text);
  if (!s) return null;
  const key = s.toLowerCase();
  if (INSTRUCTION_MAP[key]) return INSTRUCTION_MAP[key];
  for (const [phrase, ru] of Object.entries(INSTRUCTION_MAP)) {
    if (phrase.length > 8 && key.includes(phrase)) return ru;
  }
  if (/^[a-z0-9\s,.-]+$/i.test(s) && /[a-z]/i.test(s)) {
    const dictHit = translateInstructionLabel(s);
    if (dictHit) return dictHit;
  }
  if (/^(src|href|http|www\.|img\.)/i.test(s)) return null;
  return s;
}

function needsTranslation(text: string): boolean {
  const cyr = (text.match(/[а-яёА-ЯЁ]/g) || []).length;
  const lat = (text.match(/[a-zA-Z]/g) || []).length;
  if (lat === 0) return false;
  return lat >= cyr;
}

function translateInstructionLabel(text: string): string | null {
  const key = text.trim().toLowerCase();
  if (INSTRUCTION_MAP[key]) return INSTRUCTION_MAP[key];
  if (key.length <= 24 && /^[a-z0-9\s,.-]+$/i.test(key)) {
    const words = key.split(/[\s,/]+/).filter(Boolean);
    if (words.length <= 4 && words.every((w) => INSTRUCTION_MAP[w])) {
      return words.map((w) => INSTRUCTION_MAP[w]).join(", ");
    }
  }
  return null;
}

function cacheKey(text: string, lang: string) {
  return createHash("sha256").update(`${lang}:${text}`).digest("hex");
}

async function throttle() {
  const wait = MIN_GAP_MS - (Date.now() - lastTranslateAt);
  if (wait > 0) await new Promise((r) => setTimeout(r, wait));
  lastTranslateAt = Date.now();
}

async function remoteTranslate(text: string, targetLang: string): Promise<string> {
  const clean = sanitizeGameText(text) ?? text;
  if (!clean || clean.length < 2 || !needsTranslation(clean)) return clean;

  if (clean.length > 4500) {
    const chunks = clean.match(/.{1,450}(?:\s|$)/g) ?? [clean];
    const parts: string[] = [];
    for (const chunk of chunks.slice(0, 10)) {
      parts.push(await remoteTranslate(chunk.trim(), targetLang));
    }
    return parts.join(" ");
  }

  await throttle();
  try {
    const res = await gtxLib(clean, { from: "en", to: targetLang, client: "gtx" });
    const out = res.text?.trim();
    if (out && out.toUpperCase() !== clean.toUpperCase()) return out;
  } catch {
    /* fallback below */
  }

  const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(clean)}`;
  const res = await fetch(url, { headers: { "user-agent": "Mozilla/5.0" } });
  if (res.ok) {
    const data = (await res.json()) as unknown;
    if (Array.isArray(data) && Array.isArray(data[0])) {
      const parts = (data[0] as [string][]).map((c) => c[0]).filter(Boolean).join("").trim();
      if (parts) return parts;
    }
  }
  return clean;
}

export function getTranslateTargetLang(): string {
  const db = getDb();
  const row = db.prepare("SELECT value FROM app_settings WHERE key = 'translate_lang'").get() as
    | { value: string }
    | undefined;
  return row?.value || process.env.TRANSLATE_LANG || "ru";
}

export function isAutoTranslateEnabled(): boolean {
  const db = getDb();
  const row = db.prepare("SELECT value FROM app_settings WHERE key = 'auto_translate'").get() as
    | { value: string }
    | undefined;
  if (row) return row.value === "1";
  return process.env.AUTO_TRANSLATE !== "0";
}

export async function translateText(text: string | null | undefined, targetLang?: string): Promise<string | null> {
  if (!text) return null;
  const sanitized = sanitizeGameText(text);
  if (!sanitized) return null;
  const dictHit = translateInstructionLabel(sanitized);
  if (dictHit) return dictHit;
  if (!isAutoTranslateEnabled() || looksRussian(sanitized)) return sanitized;

  const lang = targetLang || getTranslateTargetLang();
  const db = getDb();
  const hash = cacheKey(sanitized, lang);
  const cached = db.prepare("SELECT translated FROM translation_cache WHERE hash = ? AND lang = ?").get(hash, lang) as
    | { translated: string }
    | undefined;
  if (cached) return cached.translated;

  try {
    const translated = sanitizeGameText(await remoteTranslate(sanitized, lang)) ?? sanitized;
    db.prepare("INSERT OR REPLACE INTO translation_cache (hash, lang, translated) VALUES (?, ?, ?)").run(
      hash,
      lang,
      translated,
    );
    return translated;
  } catch {
    return sanitized;
  }
}

export async function translateFields<T extends { title?: string; description?: string | null; instructions?: string | null }>(
  fields: T,
  options?: { force?: boolean },
): Promise<T> {
  const force = options?.force ?? isAutoTranslateEnabled();
  const title = fields.title ? sanitizeGameText(fields.title) ?? fields.title : fields.title;
  const description = fields.description ? sanitizeGameText(fields.description) : fields.description;
  const instructions = fields.instructions ? sanitizeGameText(fields.instructions) : fields.instructions;
  if (!force) return { ...fields, title, description, instructions };

  const [tTitle, tDesc, tInstr] = await Promise.all([
    title ? translateText(title) : Promise.resolve(title),
    description ? translateText(description) : Promise.resolve(description),
    instructions ? translateText(instructions) : Promise.resolve(instructions),
  ]);
  return { ...fields, title: tTitle ?? title, description: tDesc ?? description, instructions: tInstr ?? instructions };
}

async function mapPool<T, R>(items: T[], worker: (item: T) => Promise<R>): Promise<R[]> {
  const out: R[] = new Array(items.length);
  let i = 0;
  async function run() {
    while (i < items.length) {
      const idx = i++;
      out[idx] = await worker(items[idx]);
    }
  }
  await Promise.all(Array.from({ length: Math.min(POOL, items.length) }, run));
  return out;
}

export async function translateAllGamesInDb(options?: { limit?: number; batchSize?: number }) {
  const db = getDb();
  const batchSize = options?.batchSize ?? 400;
  const maxTotal = options?.limit ?? Number.MAX_SAFE_INTEGER;

  const updateGame = db.prepare(
    `UPDATE games SET title = ?, description = ?, instructions = ?, updated_at = datetime('now') WHERE id = ?`,
  );
  const updateSiteGame = db.prepare(
    `UPDATE site_games SET title = ?, description = ?, instructions = ? WHERE game_id = ?`,
  );

  let translated = 0;
  let processed = 0;

  while (processed < maxTotal) {
    const rows = db
      .prepare(
        `SELECT id, title, description, instructions FROM games
         WHERE title GLOB '*[A-Za-z]*' OR description GLOB '*[A-Za-z]*' OR instructions GLOB '*[A-Za-z]*'
         ORDER BY updated_at ASC LIMIT ?`,
      )
      .all(batchSize) as { id: string; title: string; description: string | null; instructions: string | null }[];

    if (rows.length === 0) break;

    await mapPool(rows, async (row) => {
      const next = await translateFields(
        { title: row.title, description: row.description, instructions: row.instructions },
        { force: true },
      );
      if (next.title !== row.title || next.description !== row.description || next.instructions !== row.instructions) {
        updateGame.run(next.title, next.description, next.instructions, row.id);
        updateSiteGame.run(next.title, next.description, next.instructions, row.id);
        translated++;
      } else {
        updateGame.run(row.title, row.description, row.instructions, row.id);
      }
    });

    processed += rows.length;
    console.log(`[translate] batch ${rows.length}, processed ${processed}, translated ${translated}`);
    if (rows.length < batchSize) break;
  }

  return { translated, total: processed };
}

export async function translateUntranslatedBatch(limit = 200): Promise<{ translated: number }> {
  const db = getDb();
  const rows = db
    .prepare(
      `SELECT id, title, description, instructions FROM games
       WHERE title GLOB '*[A-Za-z]*' ORDER BY updated_at ASC LIMIT ?`,
    )
    .all(limit) as { id: string; title: string; description: string | null; instructions: string | null }[];

  const updateGame = db.prepare(
    `UPDATE games SET title = ?, description = ?, instructions = ?, updated_at = datetime('now') WHERE id = ?`,
  );
  const updateSiteGame = db.prepare(
    `UPDATE site_games SET title = ?, description = ?, instructions = ? WHERE game_id = ?`,
  );

  let translated = 0;
  await mapPool(rows, async (row) => {
    const next = await translateFields(
      { title: row.title, description: row.description, instructions: row.instructions },
      { force: true },
    );
    if (next.title !== row.title || next.description !== row.description || next.instructions !== row.instructions) {
      updateGame.run(next.title, next.description, next.instructions, row.id);
      updateSiteGame.run(next.title, next.description, next.instructions, row.id);
      translated++;
    }
  });
  return { translated };
}
