import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import {
  DATA_ROOT,
  createMovesetMedia,
  listCharacters,
  listMovesetEntries,
  listMovesetMedia,
  upsertCharacter,
  upsertMovesetEntry,
} from "./db.js";

const SECTION_GROUPS = [
  ["Ground", [
    ["neutral_a", "Neutral A", "attack"],
    ["side_a", "Side A", "attack"],
    ["up_a", "Up A", "attack"],
    ["down_a", "Down A", "attack"],
    ["dash_attack", "Dash Attack", "attack"],
  ]],
  ["Specials", [
    ["neutral_b", "Neutral B", "special"],
    ["side_b", "Side B", "special"],
    ["up_b", "Up B", "special"],
    ["down_b", "Down B", "special"],
  ]],
  ["Aerials", [
    ["neutral_air", "Neutral Air", "air"],
    ["forward_air", "Forward Air", "air"],
    ["back_air", "Back Air", "air"],
    ["up_air", "Up Air", "air"],
    ["down_air", "Down Air", "air"],
  ]],
  ["Throws", [
    ["grab", "Grab", "throw"],
    ["pummel", "Pummel", "throw"],
    ["forward_throw", "Forward Throw", "throw"],
    ["back_throw", "Back Throw", "throw"],
    ["up_throw", "Up Throw", "throw"],
    ["down_throw", "Down Throw", "throw"],
  ]],
  ["Other", [
    ["skins", "Skins", "other"],
    ["mechanic_notes", "Mechanic Notes", "other"],
    ["misc_notes", "Misc Notes", "other"],
  ]],
];

const DOCUMENT_BUCKETS = ["OG", "Unlockables", "DLC"];
const DOCUMENT_BUCKET_LABELS = {
  OG: "OGs",
  Unlockables: "Unlockables",
  DLC: "DLC",
};
const SECTION_COLORS = {
  attack: "#7f2e2e",
  special: "#1f4a2e",
  air: "#3d62b3",
  throw: "#6a44a8",
  other: "#5c687d",
};
const BUNDLE_VERSION = 1;
const BUNDLE_EXTENSION = ".animefightermoveset";

function text(value) {
  return String(value ?? "").trim();
}

function escapeHtml(value) {
  return String(value ?? "")
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll("\"", "&quot;");
}

function parseTags(tags) {
  const seen = new Set();
  const result = [];
  for (const raw of String(tags || "").split(",")) {
    const value = raw.trim();
    if (!value) continue;
    const key = value.toLowerCase();
    if (seen.has(key)) continue;
    seen.add(key);
    result.push(value);
  }
  return result;
}

function sanitizeFilename(value) {
  return String(value || "file")
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9._-]+/g, "-")
    .replace(/-+/g, "-");
}

function timestampStamp() {
  return new Date().toISOString().replace(/[:]/g, "-").replace(/\..+$/, "");
}

function ensureDir(dirPath) {
  fs.mkdirSync(dirPath, { recursive: true });
}

function relativeUploadAbsolutePath(relativeUploadPath) {
  return path.join(DATA_ROOT, relativeUploadPath);
}

function bucketOrderValue(bucket) {
  const index = DOCUMENT_BUCKETS.indexOf(bucket);
  return index === -1 ? Number.MAX_SAFE_INTEGER : index;
}

function groupEntriesByCharacter() {
  const byCharacter = new Map();
  for (const entry of listMovesetEntries()) {
    if (!byCharacter.has(entry.character_slug)) {
      byCharacter.set(entry.character_slug, new Map());
    }
    byCharacter.get(entry.character_slug).set(entry.section_key, entry);
  }
  return byCharacter;
}

function groupMediaByCharacter() {
  const byCharacter = new Map();
  for (const media of listMovesetMedia()) {
    if (!byCharacter.has(media.character_slug)) {
      byCharacter.set(media.character_slug, new Map());
    }
    const bySection = byCharacter.get(media.character_slug);
    if (!bySection.has(media.section_key)) {
      bySection.set(media.section_key, []);
    }
    bySection.get(media.section_key).push(media);
  }
  return byCharacter;
}

function displayMediaLabel(media) {
  return text(media.label || media.original_filename || media.stored_filename || "media");
}

function buildCharacterDocumentRow(character, entriesByCharacter, mediaByCharacter) {
  const entryMap = entriesByCharacter.get(character.slug) || new Map();
  const mediaMap = mediaByCharacter.get(character.slug) || new Map();
  const tags = parseTags(character.tags);
  const conceptArt = (mediaMap.get("concept_gallery") || []).map(displayMediaLabel);

  const sectionGroups = SECTION_GROUPS.map(([groupLabel, sections]) => {
    const items = sections.map(([sectionKey, sectionLabel, sectionType]) => {
      const entry = entryMap.get(sectionKey);
      const media = mediaMap.get(sectionKey) || [];
      const notes = text(entry?.notes);
      if (!notes && media.length === 0) {
        return null;
      }
      return {
        key: sectionKey,
        label: sectionLabel,
        type: sectionType,
        notes,
        media: media.map(displayMediaLabel),
      };
    }).filter(Boolean);

    return items.length ? { label: groupLabel, items } : null;
  }).filter(Boolean);

  return {
    slug: character.slug,
    display_name: character.display_name || character.slug,
    approval_status: character.approval_status || "unapproved",
    release_bucket: character.release_bucket,
    tags,
    unlock_rule: text(character.unlock_notes),
    concept_art: conceptArt,
    section_groups: sectionGroups,
    has_moves: sectionGroups.length > 0,
  };
}

export function buildDocumentExportModel() {
  const entriesByCharacter = groupEntriesByCharacter();
  const mediaByCharacter = groupMediaByCharacter();
  const characters = listCharacters()
    .filter((row) => DOCUMENT_BUCKETS.includes(row.release_bucket))
    .sort((a, b) => {
      const bucketDiff = bucketOrderValue(a.release_bucket) - bucketOrderValue(b.release_bucket);
      if (bucketDiff !== 0) return bucketDiff;
      return String(a.display_name || a.slug).localeCompare(String(b.display_name || b.slug));
    })
    .map((row) => buildCharacterDocumentRow(row, entriesByCharacter, mediaByCharacter));

  const buckets = DOCUMENT_BUCKETS.map((bucket) => ({
    key: bucket,
    label: DOCUMENT_BUCKET_LABELS[bucket] || bucket,
    characters: characters.filter((row) => row.release_bucket === bucket),
  })).filter((bucket) => bucket.characters.length > 0);

  return {
    generated_at: new Date().toISOString(),
    total_characters: characters.length,
    buckets,
  };
}

export function renderTextArchive(model) {
  const lines = [];
  lines.push("Anime Fighter Pro Moveset Archive");
  lines.push(`Generated: ${model.generated_at}`);
  lines.push(`Characters: ${model.total_characters}`);
  lines.push("");

  for (const bucket of model.buckets) {
    lines.push(bucket.label);
    lines.push("=".repeat(bucket.label.length));
    lines.push("");

    for (const character of bucket.characters) {
      lines.push(character.display_name);
      lines.push(`Bucket: ${bucket.label}`);
      lines.push(`Approval: ${character.approval_status === "approved" ? "Approved" : "Unapproved"}`);
      if (character.unlock_rule) {
        lines.push(`Unlock Rule: ${character.unlock_rule}`);
      }
      if (character.tags.length) {
        lines.push(`Tags: ${character.tags.join(", ")}`);
      }
      if (character.concept_art.length) {
        lines.push(`Concept Art: ${character.concept_art.join(", ")}`);
      }
      if (!character.has_moves) {
        lines.push("No moves entered yet.");
        lines.push("");
        continue;
      }
      lines.push("");
      for (const group of character.section_groups) {
        lines.push(`[${group.label}]`);
        for (const item of group.items) {
          lines.push(`${item.label}`);
          if (item.notes) {
            lines.push(item.notes);
          }
          if (item.media.length) {
            lines.push(`Preview Files: ${item.media.join(", ")}`);
          }
          lines.push("");
        }
      }
      lines.push("");
    }
  }

  return `${lines.join("\n").trim()}\n`;
}

function richArchiveStyles() {
  return `
    :root {
      color-scheme: light;
      --bg: #eef2f7;
      --panel: #ffffff;
      --line: #d2dbe5;
      --text: #182433;
      --muted: #64748b;
      --og: #31a256;
      --unlockables: #df60b3;
      --dlc: #d6a31c;
    }
    * { box-sizing: border-box; }
    body {
      margin: 0;
      padding: 28px;
      background: var(--bg);
      color: var(--text);
      font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
      line-height: 1.45;
    }
    h1, h2, h3, h4, p { margin: 0; }
    .archive-shell { display: grid; gap: 18px; }
    .archive-header {
      border: 1px solid var(--line);
      border-radius: 18px;
      background: linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(248,250,252,0.96) 100%);
      padding: 22px 24px;
    }
    .archive-eyebrow {
      font-size: 12px;
      font-weight: 700;
      letter-spacing: 0.12em;
      text-transform: uppercase;
      color: var(--muted);
      margin-bottom: 10px;
    }
    .archive-title {
      font-size: 32px;
      font-weight: 800;
      letter-spacing: -0.03em;
      margin-bottom: 10px;
    }
    .archive-subtitle {
      font-size: 15px;
      color: var(--muted);
    }
    .bucket-block {
      border: 1px solid var(--line);
      border-radius: 18px;
      background: rgba(255,255,255,0.9);
      padding: 18px;
      page-break-inside: avoid;
    }
    .bucket-block.bucket-og { border-top: 6px solid var(--og); }
    .bucket-block.bucket-unlockables { border-top: 6px solid var(--unlockables); }
    .bucket-block.bucket-dlc { border-top: 6px solid var(--dlc); }
    .bucket-head {
      display: flex;
      justify-content: space-between;
      align-items: baseline;
      gap: 12px;
      margin-bottom: 14px;
    }
    .bucket-name {
      font-size: 22px;
      font-weight: 800;
      letter-spacing: -0.02em;
    }
    .bucket-count {
      font-size: 13px;
      color: var(--muted);
    }
    .character-card {
      border: 1px solid var(--line);
      border-radius: 16px;
      background: var(--panel);
      padding: 16px;
      margin-top: 12px;
      page-break-inside: avoid;
    }
    .character-top {
      display: flex;
      justify-content: space-between;
      gap: 16px;
      align-items: baseline;
      margin-bottom: 12px;
      flex-wrap: wrap;
    }
    .character-name {
      font-size: 22px;
      font-weight: 800;
      letter-spacing: -0.02em;
    }
    .character-status {
      font-size: 12px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: 0.08em;
      color: var(--muted);
    }
    .meta-grid {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 10px;
      margin-bottom: 14px;
    }
    .meta-box {
      border: 1px solid var(--line);
      border-radius: 12px;
      background: #fbfcfe;
      padding: 10px 12px;
      min-height: 64px;
    }
    .meta-label {
      font-size: 11px;
      font-weight: 700;
      letter-spacing: 0.08em;
      text-transform: uppercase;
      color: var(--muted);
      margin-bottom: 6px;
    }
    .meta-value {
      font-size: 14px;
      white-space: pre-wrap;
    }
    .section-group {
      margin-top: 14px;
      border-top: 1px solid var(--line);
      padding-top: 14px;
    }
    .section-group-title {
      font-size: 13px;
      font-weight: 800;
      text-transform: uppercase;
      letter-spacing: 0.08em;
      color: var(--muted);
      margin-bottom: 10px;
    }
    .move-item {
      border-left: 4px solid #d1d9e2;
      padding: 8px 0 8px 12px;
      margin-bottom: 10px;
    }
    .move-item.type-attack { border-left-color: ${SECTION_COLORS.attack}; }
    .move-item.type-special { border-left-color: ${SECTION_COLORS.special}; }
    .move-item.type-air { border-left-color: ${SECTION_COLORS.air}; }
    .move-item.type-throw { border-left-color: ${SECTION_COLORS.throw}; }
    .move-item.type-other { border-left-color: ${SECTION_COLORS.other}; }
    .move-title {
      font-size: 15px;
      font-weight: 700;
      margin-bottom: 4px;
    }
    .move-notes {
      font-size: 14px;
      white-space: pre-wrap;
      margin-bottom: 6px;
    }
    .move-media {
      font-size: 12px;
      color: var(--muted);
    }
    .empty-notes {
      font-size: 14px;
      color: var(--muted);
      font-style: italic;
      margin-top: 6px;
    }
    @page {
      size: Letter;
      margin: 0.5in;
    }
  `;
}

function renderRichArchiveHtml(model) {
  return `<!doctype html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <title>Anime Fighter Pro Moveset Archive</title>
      <style>${richArchiveStyles()}</style>
    </head>
    <body>
      <main class="archive-shell">
        <header class="archive-header">
          <div class="archive-eyebrow">Anime Fighter Pro</div>
          <div class="archive-title">Moveset Archive</div>
          <div class="archive-subtitle">Generated ${escapeHtml(model.generated_at)} · ${model.total_characters} official characters</div>
        </header>
        ${model.buckets.map((bucket) => `
          <section class="bucket-block bucket-${bucket.key.toLowerCase()}">
            <div class="bucket-head">
              <div class="bucket-name">${escapeHtml(bucket.label)}</div>
              <div class="bucket-count">${bucket.characters.length} character${bucket.characters.length === 1 ? "" : "s"}</div>
            </div>
            ${bucket.characters.map((character) => `
              <article class="character-card">
                <div class="character-top">
                  <div class="character-name">${escapeHtml(character.display_name)}</div>
                  <div class="character-status">${character.approval_status === "approved" ? "Approved" : "Unapproved"}</div>
                </div>
                <div class="meta-grid">
                  <div class="meta-box">
                    <div class="meta-label">Unlock Rule</div>
                    <div class="meta-value">${escapeHtml(character.unlock_rule || "No unlock rule entered.")}</div>
                  </div>
                  <div class="meta-box">
                    <div class="meta-label">Tags</div>
                    <div class="meta-value">${escapeHtml(character.tags.length ? character.tags.join(", ") : "No tags")}</div>
                  </div>
                  <div class="meta-box">
                    <div class="meta-label">Concept Art</div>
                    <div class="meta-value">${escapeHtml(character.concept_art.length ? character.concept_art.join(", ") : "None listed")}</div>
                  </div>
                  <div class="meta-box">
                    <div class="meta-label">Bucket</div>
                    <div class="meta-value">${escapeHtml(bucket.label)}</div>
                  </div>
                </div>
                ${character.has_moves ? character.section_groups.map((group) => `
                  <section class="section-group">
                    <div class="section-group-title">${escapeHtml(group.label)}</div>
                    ${group.items.map((item) => `
                      <div class="move-item type-${item.type}">
                        <div class="move-title">${escapeHtml(item.label)}</div>
                        ${item.notes ? `<div class="move-notes">${escapeHtml(item.notes)}</div>` : ""}
                        ${item.media.length ? `<div class="move-media">Preview files: ${escapeHtml(item.media.join(", "))}</div>` : ""}
                      </div>
                    `).join("")}
                  </section>
                `).join("") : `<div class="empty-notes">No moves entered yet.</div>`}
              </article>
            `).join("")}
          </section>
        `).join("")}
      </main>
    </body>
  </html>`;
}

export function renderWordArchive(model) {
  return renderRichArchiveHtml(model);
}

export function renderPdfArchive(model) {
  return renderRichArchiveHtml(model);
}

export function renderExcelArchive(model) {
  const rows = [];
  for (const bucket of model.buckets) {
    for (const character of bucket.characters) {
      if (!character.has_moves) {
        rows.push({
          bucket: bucket.label,
          character: character.display_name,
          approval: character.approval_status === "approved" ? "Approved" : "Unapproved",
          unlock_rule: character.unlock_rule,
          tags: character.tags.join(", "),
          concept_art: character.concept_art.join(", "),
          section_group: "",
          section: "",
          notes: "No moves entered yet.",
          preview_files: "",
        });
        continue;
      }

      for (const group of character.section_groups) {
        for (const item of group.items) {
          rows.push({
            bucket: bucket.label,
            character: character.display_name,
            approval: character.approval_status === "approved" ? "Approved" : "Unapproved",
            unlock_rule: character.unlock_rule,
            tags: character.tags.join(", "),
            concept_art: character.concept_art.join(", "),
            section_group: group.label,
            section: item.label,
            notes: item.notes,
            preview_files: item.media.join(", "),
          });
        }
      }
    }
  }

  return `<!doctype html>
  <html lang="en">
    <head>
      <meta charset="UTF-8" />
      <title>Anime Fighter Pro Moveset Archive</title>
      <style>
        body { font-family: Arial, sans-serif; }
        table { border-collapse: collapse; width: 100%; }
        th, td { border: 1px solid #cfd6df; padding: 6px 8px; vertical-align: top; }
        th { background: #eef2f7; font-weight: 700; }
        td { white-space: pre-wrap; }
      </style>
    </head>
    <body>
      <table>
        <thead>
          <tr>
            <th>Bucket</th>
            <th>Character</th>
            <th>Approval</th>
            <th>Unlock Rule</th>
            <th>Tags</th>
            <th>Concept Art</th>
            <th>Section Group</th>
            <th>Section</th>
            <th>Notes</th>
            <th>Preview Files</th>
          </tr>
        </thead>
        <tbody>
          ${rows.map((row) => `
            <tr>
              <td>${escapeHtml(row.bucket)}</td>
              <td>${escapeHtml(row.character)}</td>
              <td>${escapeHtml(row.approval)}</td>
              <td>${escapeHtml(row.unlock_rule)}</td>
              <td>${escapeHtml(row.tags)}</td>
              <td>${escapeHtml(row.concept_art)}</td>
              <td>${escapeHtml(row.section_group)}</td>
              <td>${escapeHtml(row.section)}</td>
              <td>${escapeHtml(row.notes)}</td>
              <td>${escapeHtml(row.preview_files)}</td>
            </tr>
          `).join("")}
        </tbody>
      </table>
    </body>
  </html>`;
}

export function buildMovesetBundle() {
  return {
    bundle_type: "anime_fighter_pro_moveset_bundle",
    version: BUNDLE_VERSION,
    generated_at: new Date().toISOString(),
    characters: listCharacters(),
    entries: listMovesetEntries(),
    media: listMovesetMedia().map((row) => {
      const filePath = relativeUploadAbsolutePath(row.relative_upload_path);
      const fileExists = fs.existsSync(filePath);
      return {
        ...row,
        file_exists: fileExists,
        file_base64: fileExists ? fs.readFileSync(filePath).toString("base64") : "",
      };
    }),
  };
}

function extensionForMimeType(mimeType) {
  const map = {
    "image/png": ".png",
    "image/jpeg": ".jpg",
    "image/webp": ".webp",
    "image/gif": ".gif",
    "video/mp4": ".mp4",
    "video/quicktime": ".mov",
    "video/webm": ".webm",
  };
  return map[mimeType] || "";
}

export function defaultExportBasename() {
  return `anime-fighter-pro-moveset-archive-${timestampStamp()}`;
}

export function bundleExtension() {
  return BUNDLE_EXTENSION;
}

export function importMovesetBundle(bundle) {
  if (!bundle || bundle.bundle_type !== "anime_fighter_pro_moveset_bundle") {
    throw new Error("Invalid Anime Fighter Pro moveset file.");
  }

  const imported = {
    characters: 0,
    entries: 0,
    media: 0,
    skipped_media: 0,
  };

  for (const character of bundle.characters || []) {
    upsertCharacter(character);
    imported.characters += 1;
  }

  for (const entry of bundle.entries || []) {
    upsertMovesetEntry(entry);
    imported.entries += 1;
  }

  const existingMediaKeys = new Set(
    listMovesetMedia().map((row) => `${row.character_slug}::${row.section_key}::${row.original_filename}`),
  );
  const mediaRoot = path.join(DATA_ROOT, "storage", "uploads", "moveset-media", "imported-bundles");
  ensureDir(mediaRoot);

  for (const media of bundle.media || []) {
    const mediaKey = `${media.character_slug}::${media.section_key}::${media.original_filename}`;
    if (existingMediaKeys.has(mediaKey) || !media.file_base64) {
      imported.skipped_media += 1;
      continue;
    }

    const ext = path.extname(media.original_filename || "") || extensionForMimeType(media.mime_type) || "";
    const stem = path.basename(media.original_filename || media.label || "media", ext);
    const storedFilename = `${Date.now()}-${crypto.randomUUID()}-${sanitizeFilename(stem)}${ext}`;
    const absolutePath = path.join(mediaRoot, storedFilename);
    fs.writeFileSync(absolutePath, Buffer.from(media.file_base64, "base64"));

    createMovesetMedia({
      character_slug: media.character_slug,
      section_key: media.section_key,
      label: media.label,
      original_filename: media.original_filename,
      stored_filename: storedFilename,
      relative_upload_path: path.relative(DATA_ROOT, absolutePath),
      mime_type: media.mime_type,
    });

    existingMediaKeys.add(mediaKey);
    imported.media += 1;
  }

  return imported;
}
