import { promises as fs } from 'node:fs';
import path from 'node:path';
import { NextResponse } from 'next/server';
import { imageFileInfo, recordEvent, upsertImageAsset } from '@/lib/auditDb';
import { loadSession } from '@/lib/storage';
import type { AssetPack, ToyAsset } from '@/lib/types';

export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';

type GalleryItem = {
  filename: string;
  url: string;
  group: string;
  label: string;
  sizeKb: number;
  mtime: number;
  current: boolean;
  width: number | null;
  height: number | null;
  aspectRatio?: string;
  packId?: string;
  templateId?: string;
};

function escapeHtml(input: string): string {
  return input
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

type CurrentAssetMeta = {
  pack: AssetPack;
  asset: ToyAsset;
};

function packGroup(filename: string): string {
  const match = filename.match(/^pack_([^_]+)_/);
  return match?.[1] ?? 'unknown';
}

function fileLabel(filename: string): string {
  return filename
    .replace(/^pack_[^_]+_[^_]+_[^_]+_/, '')
    .replace(/\.[a-z0-9]+$/i, '')
    .replace(/_/g, ' ');
}

function cssRatio(item: GalleryItem): string {
  if (item.width && item.height) return `${item.width} / ${item.height}`;
  if (item.aspectRatio && item.aspectRatio !== 'long') return item.aspectRatio.replace(':', ' / ');
  if (item.aspectRatio === 'long') return '1 / 3';
  return '1 / 1';
}

async function listPackItems(currentAssets: Map<string, CurrentAssetMeta>, sessionId: string): Promise<GalleryItem[]> {
  const dir = path.join(process.cwd(), 'data', 'packs');
  let files: string[] = [];
  try {
    files = await fs.readdir(dir);
  } catch {
    return [];
  }

  const items = await Promise.all(
    files
      .filter(filename => /\.(png|jpe?g|webp)$/i.test(filename))
      .map(async filename => {
        const filePath = path.join(dir, filename);
        const stat = await fs.stat(filePath);
        const info = imageFileInfo(filePath);
        const meta = currentAssets.get(filename);
        const item = {
          filename,
          url: `/api/img/packs/${encodeURIComponent(filename)}`,
          group: meta?.pack.kind ?? packGroup(filename),
          label: meta?.asset.title ?? fileLabel(filename),
          sizeKb: Math.round(stat.size / 1024),
          mtime: stat.mtimeMs,
          current: Boolean(meta),
          width: info.width,
          height: info.height,
          aspectRatio: meta?.asset.aspectRatio,
          packId: meta?.pack.id,
          templateId: meta?.asset.templateId,
        } satisfies GalleryItem;
        upsertImageAsset({
          filename,
          url: item.url,
          bucket: 'packs',
          sessionId,
          packId: meta?.pack.id,
          targetId: meta?.asset.id,
          kind: meta?.pack.kind ?? packGroup(filename),
          templateId: meta?.asset.templateId,
          title: item.label,
          aspectRatio: item.aspectRatio,
          width: item.width,
          height: item.height,
          sizeBytes: stat.size,
          current: Boolean(meta),
          origin: meta ? 'current' : 'archived',
          status: meta?.asset.status ?? 'archived',
        });
        return item;
      }),
  );

  return items.sort((a, b) => {
    if (a.current !== b.current) return a.current ? -1 : 1;
    if (a.group !== b.group) return a.group.localeCompare(b.group);
    return a.mtime - b.mtime;
  });
}

function renderItems(title: string, items: GalleryItem[]): string {
  const cards = items.map(item => `
    <article class="card" style="--ratio: ${cssRatio(item)}">
      <a href="${item.url}" target="_blank" rel="noreferrer">
        <img class="thumb" src="${item.url}" loading="lazy" alt="${escapeHtml(item.filename)}" />
        <img class="zoom" src="${item.url}" loading="lazy" alt="" />
      </a>
      <div class="meta">
        <strong>${escapeHtml(item.group)} · ${escapeHtml(item.label)}</strong>
        <span>${escapeHtml(item.filename)}</span>
        <span>${item.width && item.height ? `${item.width}×${item.height} · ` : ''}${item.sizeKb} KB</span>
      </div>
    </article>
  `).join('');

  return `
    <section>
      <h2>${escapeHtml(title)} <small>${items.length}</small></h2>
      <div class="grid">${cards || '<p class="empty">No images.</p>'}</div>
    </section>
  `;
}

function renderPage(opts: {
  sessionId: string;
  current: GalleryItem[];
  archived: GalleryItem[];
  packsSummary: string;
  sourceHtml: string;
  auditUrl: string;
}): string {
  const total = opts.current.length + opts.archived.length;
  return `<!doctype html>
<html lang="zh">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>${escapeHtml(opts.sessionId)} Gallery</title>
  <style>
    :root { color-scheme: dark; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0b0b0d; color: #f5f5f5; }
    body { margin: 0; padding: 28px; }
    header { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin-bottom: 24px; }
    h1 { margin: 0 0 8px; font-size: 24px; }
    h2 { margin: 34px 0 14px; font-size: 18px; }
    small, .muted { color: #9ca3af; font-weight: 400; }
    .pillrow { display: flex; flex-wrap: wrap; gap: 8px; }
    .pill { border: 1px solid #2d2f36; border-radius: 999px; padding: 6px 10px; background: #15161b; color: #d1d5db; font-size: 12px; }
    .source { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 10px; }
    .source a { color: #f5f5f5; text-decoration: none; border: 1px solid #2d2f36; border-radius: 10px; padding: 8px 10px; background: #15161b; }
    .toplinks { display: flex; gap: 10px; align-items: center; justify-content: flex-end; margin-top: 10px; }
    .toplinks a { color: #f5f5f5; text-decoration: none; border: 1px solid #3b3e47; border-radius: 10px; padding: 8px 10px; background: #1e2027; font-size: 12px; }
    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 14px; }
    .card { position: relative; background: #15161b; border: 1px solid #2d2f36; border-radius: 12px; overflow: visible; }
    .card a { position: relative; display: block; border-radius: 12px 12px 0 0; background: #fff; overflow: hidden; }
    .thumb { width: 100%; aspect-ratio: var(--ratio, 1 / 1); object-fit: contain; display: block; background: #fff; }
    .zoom { display: none; position: fixed; left: var(--zoom-left, 20px); top: var(--zoom-top, 20px); width: min(620px, 82vw); max-height: 82vh; object-fit: contain; z-index: 100; background: #fff; border: 1px solid #3b3e47; border-radius: 14px; box-shadow: 0 24px 90px rgba(0,0,0,.72); pointer-events: none; }
    .card:hover { z-index: 20; border-color: #565b68; }
    .card:hover .zoom { display: block; }
    .meta { padding: 10px; display: grid; gap: 5px; font-size: 11px; color: #9ca3af; }
    .meta strong { color: #f5f5f5; font-size: 12px; line-height: 1.25; }
    .empty { color: #9ca3af; }
  </style>
  <script>
    window.addEventListener('pointermove', function (event) {
      if (event.pointerType === 'touch') return;
      var card = event.target && event.target.closest ? event.target.closest('.card') : null;
      if (!card) return;
      var zoom = card.querySelector('.zoom');
      if (!zoom) return;
      var gap = 18;
      var margin = 12;
      var thumb = card.querySelector('.thumb');
      var rect = thumb ? thumb.getBoundingClientRect() : { width: 1, height: 1 };
      var ratio = rect.width && rect.height ? rect.width / rect.height : 1;
      var width = Math.min(620, Math.max(280, window.innerWidth * 0.42));
      var height = Math.min(window.innerHeight * 0.82, width / ratio);
      var left = event.clientX + gap;
      var top = event.clientY + gap;
      if (left + width > window.innerWidth - margin) left = event.clientX - width - gap;
      if (top + height > window.innerHeight - margin) top = window.innerHeight - height - margin;
      zoom.style.setProperty('--zoom-left', Math.max(margin, left) + 'px');
      zoom.style.setProperty('--zoom-top', Math.max(margin, top) + 'px');
    }, { passive: true });
  </script>
</head>
<body>
  <header>
    <div>
      <h1>MUSE MATE 生成图库</h1>
      <div class="muted">Session: ${escapeHtml(opts.sessionId)}</div>
      ${opts.sourceHtml}
    </div>
    <div>
      <div class="pillrow">
        <span class="pill">总图 ${total}</span>
        <span class="pill">当前有效 ${opts.current.length}</span>
        <span class="pill">历史未挂载 ${opts.archived.length}</span>
        ${opts.packsSummary}
      </div>
      <div class="toplinks"><a href="${opts.auditUrl}">操作记录 / 数据库</a></div>
    </div>
  </header>
  ${renderItems('当前 session 有效图', opts.current)}
  ${renderItems('历史生成图 / 未挂载但已保存', opts.archived)}
</body>
</html>`;
}

export async function GET(_req: Request, ctx: { params: Promise<{ sessionId: string }> }) {
  const { sessionId } = await ctx.params;
  if (!/^s_[a-z0-9_-]+$/i.test(sessionId)) {
    return NextResponse.json({ error: 'bad sessionId' }, { status: 400 });
  }

  const session = await loadSession(sessionId);
  if (!session) return NextResponse.json({ error: 'session not found' }, { status: 404 });

  recordEvent({ action: 'gallery.view', sessionId, targetType: 'session', targetId: sessionId, status: 'ok' });
  const currentAssets = new Map<string, CurrentAssetMeta>();
  for (const pack of session.packs ?? []) {
    for (const asset of pack.assets ?? []) {
      const filename = asset.url?.match(/\/api\/img\/packs\/([^/?#]+)$/)?.[1];
      if (filename) currentAssets.set(decodeURIComponent(filename), { pack, asset });
    }
  }
  const allItems = await listPackItems(currentAssets, sessionId);
  const sourceLinks = [
    session.characterSpec?.cleanReferenceImageUrl ? `<a href="${session.characterSpec.cleanReferenceImageUrl}" target="_blank" rel="noreferrer">L1 白底锚图</a>` : '',
    ...(session.uploadedImages ?? []).map(upload => `<a href="${upload.url}" target="_blank" rel="noreferrer">上传图 ${escapeHtml(upload.originalFilename ?? upload.filename)}</a>`),
  ].filter(Boolean).join('');
  const packsSummary = (session.packs ?? []).map(pack => (
    `<span class="pill">${escapeHtml(pack.kind)} ${pack.status} ${pack.assets.length}</span>`
  )).join('');

  return new NextResponse(renderPage({
    sessionId,
    current: allItems.filter(item => item.current),
    archived: allItems.filter(item => !item.current),
    packsSummary,
    sourceHtml: sourceLinks ? `<div class="source">${sourceLinks}</div>` : '',
    auditUrl: `/api/audit/${encodeURIComponent(sessionId)}`,
  }), {
    headers: {
      'Content-Type': 'text/html; charset=utf-8',
      'Cache-Control': 'no-store',
    },
  });
}
