import crypto from 'node:crypto';
import fs from 'node:fs/promises';
import path from 'node:path';

export async function createStore({ dataDir }) {
  const root = path.resolve(dataDir);
  const indexDir = path.join(root, 'index');
  const indexFile = path.join(indexDir, 'memes.jsonl');
  await fs.mkdir(indexDir, { recursive: true });
  const entries = await loadIndex(indexFile, root);
  const byId = new Map(entries.map((entry) => [entry.id, entry]));
  let writeChain = Promise.resolve();

  return {
    count(status) {
      return status ? entries.filter((entry) => entry.status === status).length : entries.length;
    },
    get(id) {
      return byId.get(id) || null;
    },
    absolutePath(storageKey) {
      return path.join(root, storageKey);
    },
    list({ page, pageSize }) {
      const visible = entries.filter((entry) => entry.status === 'approved');
      const total = visible.length;
      const totalPages = Math.max(1, Math.ceil(total / pageSize));
      const safePage = Math.min(page, totalPages);
      const start = (safePage - 1) * pageSize;
      return {
        page: safePage,
        pageSize,
        total,
        totalPages,
        memes: visible.slice(start, start + pageSize).map(toPublicMeme)
      };
    },
    listForReview({ status = 'pending' } = {}) {
      return {
        total: entries.filter((entry) => entry.status === status).length,
        memes: entries.filter((entry) => entry.status === status).map(toPublicMeme)
      };
    },
    async approve(ids) {
      const approved = [];
      for (const id of ids) {
        const record = byId.get(id);
        if (!record || record.status !== 'pending') continue;
        record.status = 'approved';
        record.reviewedAt = new Date().toISOString();
        record.moderationReason = `${record.moderationReason || 'Queued for review.'} Admin approved.`;
        approved.push(toPublicMeme(record));
      }
      if (approved.length > 0) {
        writeChain = writeChain.then(() => persistAll(root, indexFile, entries));
        await writeChain;
      }
      return approved;
    },
    async delete(ids) {
      const deleted = [];
      for (const id of ids) {
        const index = entries.findIndex((entry) => entry.id === id);
        if (index === -1) continue;
        const [record] = entries.splice(index, 1);
        byId.delete(id);
        deleted.push(id);
        await fs.rm(path.join(root, record.storageKey), { force: true });
        await fs.rm(path.join(root, record.metaKey), { force: true });
      }
      if (deleted.length > 0) {
        writeChain = writeChain.then(() => persistAll(root, indexFile, entries));
        await writeChain;
      }
      return deleted;
    },
    async incrementMetric(id, metric) {
      const record = byId.get(id);
      if (!record) return null;
      if (metric !== 'viewCount' && metric !== 'downloadCount') return toPublicMeme(record);

      record[metric] = Number.isFinite(record[metric]) ? record[metric] + 1 : 1;
      writeChain = writeChain.then(() => persistRecord(root, indexFile, entries, record));
      await writeChain;
      return toPublicMeme(record);
    },
    async save({
      buffer,
      image,
      originalName,
      createdAt = new Date().toISOString(),
      demo = false,
      status = 'approved',
      moderationScore = demo ? 90 : 0,
      moderationReason = '',
      originalMime = image.mime
    }) {
      const id = crypto.createHash('sha256').update(buffer).digest('hex');
      const existing = byId.get(id);
      if (existing) return toPublicMeme(existing);

      const date = createdAt.slice(0, 10).replaceAll('-', '/');
      const shard = `${id.slice(0, 2)}/${id.slice(2, 4)}`;
      const storageKey = `memes/${date}/${shard}/${id}.${image.ext}`;
      const metaKey = `meta/${date}/${shard}/${id}.json`;
      const record = {
        id,
        createdAt,
        originalName,
        byteSize: image.byteSize,
        width: image.width,
        height: image.height,
        mime: image.mime,
        originalMime,
        ext: image.ext,
        status,
        moderationScore,
        moderationReason,
        viewCount: 0,
        downloadCount: 0,
        storageKey,
        metaKey,
        demo
      };

      await fs.mkdir(path.dirname(path.join(root, storageKey)), { recursive: true });
      await fs.mkdir(path.dirname(path.join(root, metaKey)), { recursive: true });
      await fs.writeFile(path.join(root, storageKey), buffer, { flag: 'wx' }).catch((error) => {
        if (error.code !== 'EEXIST') throw error;
      });
      await persistRecord(root, indexFile, [...entries, record], record, { writeFileFlag: 'wx' });

      entries.unshift(record);
      entries.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
      byId.set(id, record);
      return toPublicMeme(record);
    }
  };
}

async function loadIndex(indexFile, root) {
  let content = '';
  try {
    content = await fs.readFile(indexFile, 'utf8');
  } catch (error) {
    if (error.code !== 'ENOENT') throw error;
  }

  const recordsById = new Map();
  for (const line of content.split('\n')) {
    if (!line.trim()) continue;
    try {
      const record = JSON.parse(line);
      record.status = record.status || 'approved';
      record.moderationScore = Number.isFinite(record.moderationScore) ? record.moderationScore : 80;
      record.moderationReason = record.moderationReason || '';
      record.originalMime = record.originalMime || record.mime;
      record.viewCount = Number.isFinite(record.viewCount) ? record.viewCount : 0;
      record.downloadCount = Number.isFinite(record.downloadCount) ? record.downloadCount : 0;
      const mediaPath = path.join(root, record.storageKey);
      const relative = path.relative(root, mediaPath);
      if (relative.startsWith('..') || path.isAbsolute(relative)) continue;
      await fs.access(mediaPath);
      recordsById.set(record.id, record);
    } catch {
      // Ignore corrupt index lines; individual metadata files remain on disk for recovery.
    }
  }
  const records = [...recordsById.values()];
  records.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
  return records;
}

async function persistRecord(root, indexFile, entries, record, options = {}) {
  await fs.writeFile(path.join(root, record.metaKey), `${JSON.stringify(record, null, 2)}\n`, {
    flag: options.writeFileFlag || 'w'
  }).catch((error) => {
    if (options.writeFileFlag === 'wx' && error.code === 'EEXIST') return;
    throw error;
  });
  await persistAll(root, indexFile, entries);
}

async function persistAll(root, indexFile, entries) {
  await Promise.all(entries.map((entry) => fs.writeFile(
    path.join(root, entry.metaKey),
    `${JSON.stringify(entry, null, 2)}\n`
  ).catch((error) => {
    if (error.code !== 'ENOENT') throw error;
  })));
  await fs.writeFile(indexFile, `${entries.map((entry) => JSON.stringify(entry)).join('\n')}\n`);
}

function toPublicMeme(record) {
  return {
    id: record.id,
    createdAt: record.createdAt,
    byteSize: record.byteSize,
    width: record.width,
    height: record.height,
    mime: record.mime,
    originalMime: record.originalMime,
    status: record.status,
    moderationScore: record.moderationScore,
    moderationReason: record.moderationReason,
    viewCount: record.viewCount,
    downloadCount: record.downloadCount,
    originalName: record.originalName,
    url: `/media/${record.id}`,
    downloadUrl: `/download/${record.id}`,
    demo: Boolean(record.demo)
  };
}
