import { promises as fs } from 'node:fs';
import { randomBytes } from 'node:crypto';
import path from 'node:path';
import { imageFileInfo, recordEvent, upsertImageAsset } from './auditDb';
import type { ExportManifest, GenSession, UploadedImage, UploadedImageRole } from './types';

const ROOT = path.join(process.cwd(), 'data');
const SESS_DIR = path.join(ROOT, 'sessions');
const SEL_DIR = path.join(ROOT, 'selected');
const REF_DIR = path.join(ROOT, 'refs');
const GEN_DIR = path.join(ROOT, 'generated');
const PACK_DIR = path.join(ROOT, 'packs');
const ANCHOR_DIR = path.join(ROOT, 'anchors');
const UPLOAD_DIR = path.join(ROOT, 'uploads');
const EXPORT_DIR = path.join(ROOT, 'exports');

const BUCKET_DIRS = {
  generated: GEN_DIR,
  selected: SEL_DIR,
  refs: REF_DIR,
  packs: PACK_DIR,
  anchors: ANCHOR_DIR,
  uploads: UPLOAD_DIR,
} as const;

async function ensureDirs() {
  await Promise.all([SESS_DIR, SEL_DIR, REF_DIR, GEN_DIR, PACK_DIR, ANCHOR_DIR, UPLOAD_DIR, EXPORT_DIR].map(d => fs.mkdir(d, { recursive: true })));
}

export async function saveSession(s: GenSession) {
  await ensureDirs();
  await fs.writeFile(path.join(SESS_DIR, `${s.id}.json`), JSON.stringify(s, null, 2), 'utf-8');
  indexSessionImages(s);
  recordEvent({
    action: 'session.saved',
    sessionId: s.id,
    targetType: 'session',
    targetId: s.id,
    status: 'ok',
    metadata: {
      images: s.images.length,
      packs: s.packs?.length ?? 0,
      packAssets: (s.packs ?? []).reduce((sum, pack) => sum + pack.assets.length, 0),
      inputMode: s.inputMode ?? 'idea',
    },
  });
}

export async function loadSession(id: string): Promise<GenSession | null> {
  try {
    const raw = await fs.readFile(path.join(SESS_DIR, `${id}.json`), 'utf-8');
    return JSON.parse(raw);
  } catch {
    return null;
  }
}

export async function listSessions(): Promise<GenSession[]> {
  await ensureDirs();
  const files = await fs.readdir(SESS_DIR);
  const all = await Promise.all(
    files.filter(f => f.endsWith('.json')).map(async f => {
      const raw = await fs.readFile(path.join(SESS_DIR, f), 'utf-8');
      return JSON.parse(raw) as GenSession;
    })
  );
  return all.sort((a, b) => b.createdAt - a.createdAt);
}

function extFromMime(mime: string): string {
  if (mime.includes('jpeg')) return 'jpg';
  if (mime.includes('svg')) return 'svg';
  if (mime.includes('png')) return 'png';
  if (mime.includes('webp')) return 'webp';
  return 'bin';
}

function safePart(input: string): string {
  return input
    .normalize('NFKD')
    .replace(/[^a-zA-Z0-9._-]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 60) || 'image';
}

function localFileFromUrl(url: string): { bucket: keyof typeof BUCKET_DIRS; filename: string; filePath: string } | null {
  const match = url.match(/^\/api\/img\/(generated|selected|refs|packs|anchors|uploads)\/([^/?#]+)$/);
  if (!match) return null;
  const bucket = match[1] as keyof typeof BUCKET_DIRS;
  const filename = decodeURIComponent(match[2]);
  return { bucket, filename, filePath: path.join(BUCKET_DIRS[bucket], filename) };
}

function packKindFromPackId(packId: string): string | null {
  return packId.match(/^pack_([^_]+)_/)?.[1] ?? null;
}

function indexSessionImages(session: GenSession) {
  for (const image of session.images) {
    const local = localFileFromUrl(image.url);
    if (!local) continue;
    const info = imageFileInfo(local.filePath);
    upsertImageAsset({
      filename: local.filename,
      url: image.url,
      bucket: local.bucket,
      sessionId: session.id,
      targetId: image.id,
      width: info.width,
      height: info.height,
      sizeBytes: info.sizeBytes,
      current: image.status === 'selected' || image.status === 'pending',
      origin: local.bucket === 'selected' ? 'selected' : local.bucket === 'uploads' ? 'upload' : 'generated',
      status: image.status,
    });
  }

  for (const upload of session.uploadedImages ?? []) {
    const local = localFileFromUrl(upload.url);
    if (!local) continue;
    const info = imageFileInfo(local.filePath);
    upsertImageAsset({
      filename: local.filename,
      url: upload.url,
      bucket: local.bucket,
      sessionId: session.id,
      targetId: upload.id,
      title: upload.originalFilename ?? upload.filename,
      width: info.width,
      height: info.height,
      sizeBytes: info.sizeBytes,
      current: true,
      origin: 'upload',
      status: upload.role,
    });
  }

  if (session.characterSpec?.cleanReferenceImageUrl) {
    const local = localFileFromUrl(session.characterSpec.cleanReferenceImageUrl);
    if (local) {
      const info = imageFileInfo(local.filePath);
      upsertImageAsset({
        filename: local.filename,
        url: session.characterSpec.cleanReferenceImageUrl,
        bucket: local.bucket,
        sessionId: session.id,
        targetId: session.characterSpec.sourceImageId,
        title: `${session.characterSpec.name} L1 anchor`,
        width: info.width,
        height: info.height,
        sizeBytes: info.sizeBytes,
        current: true,
        origin: 'anchor',
        status: 'clean_reference',
      });
    }
  }

  for (const pack of session.packs ?? []) {
    for (const asset of pack.assets) {
      const local = localFileFromUrl(asset.url);
      if (!local) continue;
      const info = imageFileInfo(local.filePath);
      upsertImageAsset({
        filename: local.filename,
        url: asset.url,
        bucket: local.bucket,
        sessionId: session.id,
        packId: pack.id,
        kind: pack.kind,
        templateId: asset.templateId,
        title: asset.title,
        aspectRatio: asset.aspectRatio,
        width: info.width,
        height: info.height,
        sizeBytes: info.sizeBytes,
        current: true,
        origin: local.bucket === 'uploads' ? 'upload' : 'pack',
        status: asset.status,
      });
    }
  }
}

export async function saveGeneratedImage(sessionId: string, imageId: string, dataUrl: string): Promise<string> {
  await ensureDirs();
  const m = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
  if (!m) throw new Error('Invalid data URL');
  const ext = extFromMime(m[1]);
  const file = path.join(GEN_DIR, `${sessionId}_${imageId}.${ext}`);
  const buffer = Buffer.from(m[2], 'base64');
  await fs.writeFile(file, buffer);
  const filename = `${sessionId}_${imageId}.${ext}`;
  const url = `/api/img/generated/${filename}`;
  const info = imageFileInfo(file);
  upsertImageAsset({
    filename,
    url,
    bucket: 'generated',
    sessionId,
    targetId: imageId,
    width: info.width,
    height: info.height,
    sizeBytes: info.sizeBytes,
    current: false,
    origin: 'generated',
    status: 'saved',
  });
  recordEvent({ action: 'image.saved', sessionId, targetType: 'image', targetId: imageId, status: 'ok', metadata: { bucket: 'generated', filename, bytes: buffer.length } });
  return url;
}

export async function savePackImage(packId: string, assetId: string, dataUrl: string): Promise<string> {
  await ensureDirs();
  const m = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
  if (!m) throw new Error('Invalid data URL');
  const ext = extFromMime(m[1]);
  const file = path.join(PACK_DIR, `${packId}_${assetId}.${ext}`);
  const buffer = Buffer.from(m[2], 'base64');
  await fs.writeFile(file, buffer);
  const filename = `${packId}_${assetId}.${ext}`;
  const url = `/api/img/packs/${filename}`;
  const info = imageFileInfo(file);
  upsertImageAsset({
    filename,
    url,
    bucket: 'packs',
    packId,
    kind: packKindFromPackId(packId),
    targetId: assetId,
    width: info.width,
    height: info.height,
    sizeBytes: info.sizeBytes,
    current: false,
    origin: 'pack',
    status: 'saved',
  });
  recordEvent({ action: 'image.saved', targetType: 'pack_asset', targetId: assetId, status: 'ok', metadata: { bucket: 'packs', packId, filename, bytes: buffer.length } });
  return url;
}

export async function saveAnchorImage(sessionId: string, imageId: string, dataUrl: string): Promise<string> {
  await ensureDirs();
  const m = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
  if (!m) throw new Error('Invalid data URL');
  const ext = extFromMime(m[1]);
  const safeImageId = imageId.replace(/[^a-zA-Z0-9_-]+/g, '-');
  const filename = `${sessionId}_${safeImageId}_clean.${ext}`;
  const file = path.join(ANCHOR_DIR, filename);
  const buffer = Buffer.from(m[2], 'base64');
  await fs.writeFile(file, buffer);
  const url = `/api/img/anchors/${filename}`;
  const info = imageFileInfo(file);
  upsertImageAsset({
    filename,
    url,
    bucket: 'anchors',
    sessionId,
    targetId: imageId,
    width: info.width,
    height: info.height,
    sizeBytes: info.sizeBytes,
    current: true,
    origin: 'anchor',
    status: 'clean_reference',
  });
  recordEvent({ action: 'image.saved', sessionId, targetType: 'anchor', targetId: imageId, status: 'ok', metadata: { bucket: 'anchors', filename, bytes: buffer.length } });
  return url;
}

export async function saveUploadedImage(opts: {
  buffer: Buffer;
  mimeType: string;
  originalFilename?: string;
  role: UploadedImageRole;
  accessoryName?: string;
  needsCleanup?: boolean;
}): Promise<UploadedImage> {
  await ensureDirs();
  const uploadedAt = Date.now();
  const id = `upl_${uploadedAt.toString(36)}_${randomBytes(3).toString('hex')}`;
  const ext = extFromMime(opts.mimeType);
  const baseName = opts.originalFilename ? safePart(path.parse(opts.originalFilename).name) : 'upload';
  const filename = `${id}_${baseName}.${ext}`;
  const file = path.join(UPLOAD_DIR, filename);
  await fs.writeFile(file, opts.buffer);
  const uploaded = {
    id,
    url: `/api/img/uploads/${filename}`,
    filename,
    originalFilename: opts.originalFilename,
    mimeType: opts.mimeType,
    uploadedAt,
    role: opts.role,
    accessoryName: opts.accessoryName,
    needsCleanup: opts.needsCleanup ?? true,
  };
  const info = imageFileInfo(file);
  upsertImageAsset({
    filename,
    url: uploaded.url,
    bucket: 'uploads',
    targetId: id,
    title: uploaded.originalFilename ?? filename,
    width: info.width,
    height: info.height,
    sizeBytes: info.sizeBytes,
    current: true,
    origin: 'upload',
    status: opts.role,
  });
  recordEvent({ action: 'upload.saved', targetType: 'upload', targetId: id, status: 'ok', metadata: { filename, originalFilename: opts.originalFilename, role: opts.role, bytes: opts.buffer.length } });
  return uploaded;
}

export async function copyToSelected(sessionId: string, imageId: string, srcUrl: string): Promise<string> {
  await ensureDirs();
  // srcUrl 形如 /api/img/generated/xxx.png
  const match = srcUrl.match(/\/api\/img\/generated\/(.+)$/);
  if (!match) return srcUrl;
  const filename = match[1];
  const src = path.join(GEN_DIR, filename);
  const dst = path.join(SEL_DIR, filename);
  await fs.copyFile(src, dst);
  const url = `/api/img/selected/${filename}`;
  const info = imageFileInfo(dst);
  upsertImageAsset({
    filename,
    url,
    bucket: 'selected',
    sessionId,
    targetId: imageId,
    width: info.width,
    height: info.height,
    sizeBytes: info.sizeBytes,
    current: true,
    origin: 'selected',
    status: 'selected',
  });
  recordEvent({ action: 'image.selected_copy', sessionId, targetType: 'image', targetId: imageId, status: 'ok', metadata: { filename } });
  return url;
}

export type ImageBucket = 'generated' | 'selected' | 'refs' | 'packs' | 'anchors' | 'uploads';

export async function readImageFile(bucket: ImageBucket, filename: string): Promise<{ buf: Buffer; type: string } | null> {
  try {
    const dir = bucket === 'generated' ? GEN_DIR
      : bucket === 'selected' ? SEL_DIR
        : bucket === 'refs' ? REF_DIR
          : bucket === 'packs' ? PACK_DIR
            : bucket === 'anchors' ? ANCHOR_DIR
              : UPLOAD_DIR;
    const buf = await fs.readFile(path.join(dir, filename));
    const ext = path.extname(filename).slice(1).toLowerCase();
    const type = ext === 'jpg' ? 'image/jpeg'
      : ext === 'svg' ? 'image/svg+xml'
      : `image/${ext}`;
    return { buf, type };
  } catch {
    return null;
  }
}

export async function readImageUrl(url: string): Promise<{ buf: Buffer; type: string; filename: string }> {
  const dataMatch = url.match(/^data:([^;]+);base64,(.+)$/);
  if (dataMatch) {
    return {
      buf: Buffer.from(dataMatch[2], 'base64'),
      type: dataMatch[1],
      filename: `anchor.${extFromMime(dataMatch[1])}`,
    };
  }

  const localMatch = url.match(/^\/api\/img\/(generated|selected|refs|packs|anchors|uploads)\/([^/?#]+)$/);
  if (localMatch) {
    const bucket = localMatch[1] as ImageBucket;
    const filename = decodeURIComponent(localMatch[2]);
    const image = await readImageFile(bucket, filename);
    if (!image) throw new Error(`anchor image not found: ${url}`);
    return { ...image, filename };
  }

  if (/^https?:\/\//i.test(url)) {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`failed to fetch anchor image ${res.status}: ${url}`);
    const type = res.headers.get('content-type')?.split(';')[0] || 'image/png';
    const filename = new URL(url).pathname.split('/').pop() || `remote.${extFromMime(type)}`;
    return { buf: Buffer.from(await res.arrayBuffer()), type, filename };
  }

  throw new Error(`unsupported anchor image URL: ${url}`);
}

export async function saveRefImage(sessionId: string, idx: number, dataUrl: string): Promise<string> {
  await ensureDirs();
  const m = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
  if (!m) throw new Error('Invalid ref data URL');
  const ext = extFromMime(m[1]);
  const file = path.join(REF_DIR, `${sessionId}_ref${idx}.${ext}`);
  const buffer = Buffer.from(m[2], 'base64');
  await fs.writeFile(file, buffer);
  const filename = `${sessionId}_ref${idx}.${ext}`;
  const url = `/api/img/refs/${filename}`;
  const info = imageFileInfo(file);
  upsertImageAsset({
    filename,
    url,
    bucket: 'refs',
    sessionId,
    targetId: `ref${idx}`,
    width: info.width,
    height: info.height,
    sizeBytes: info.sizeBytes,
    current: true,
    origin: 'ref',
    status: 'reference',
  });
  recordEvent({ action: 'image.saved', sessionId, targetType: 'ref', targetId: `ref${idx}`, status: 'ok', metadata: { bucket: 'refs', filename, bytes: buffer.length } });
  return url;
}

export async function saveExportManifest(manifest: ExportManifest): Promise<string> {
  await ensureDirs();
  const filename = `${manifest.sessionId}_${manifest.packKind}_${manifest.version}_manifest.json`;
  await fs.writeFile(path.join(EXPORT_DIR, filename), JSON.stringify(manifest, null, 2), 'utf-8');
  recordEvent({
    action: 'export.manifest_saved',
    sessionId: manifest.sessionId,
    targetType: 'manifest',
    targetId: manifest.id,
    status: 'ok',
    metadata: { filename, packId: manifest.packId, packKind: manifest.packKind, files: manifest.files.length },
  });
  return `/api/export/${filename}`;
}
