'use strict';
//
// Disk extraction for chat attachments. Frontend ships base64 data URLs in
// the request body; once validated we extract the binary to disk under the
// chat-message id and store relative paths in the DB. This keeps the brain
// SQLite database lean (no multi-MB blobs in rows) and makes attachments
// browseable from the filesystem.
//
// Layout:
//   ${dataDir}/chat-attachments/<chat_message_id>/<sanitized-name>
//
// Where `${dataDir}` resolves to `process.env.WALL_E_DATA_DIR` (see
// brain.js for the canonical resolver). One directory per message; multiple
// files when a single turn carries multiple attachments.

const fs = require('fs');
const path = require('path');

const SUBDIR = 'chat-attachments';

function getRoot(dataDir) {
  const base = dataDir
    || process.env.WALL_E_DATA_DIR
    || path.join(process.env.HOME || '', '.walle', 'data');
  return path.join(base, SUBDIR);
}

/**
 * Make a name filesystem-safe: keep alnum + dash/underscore/dot, replace
 * everything else with `_`. Bound length so absurd names don't blow path
 * limits.
 */
function sanitizeName(name, fallback = 'attachment') {
  const base = (name || fallback).normalize('NFKD').replace(/[^A-Za-z0-9._-]/g, '_');
  return base.slice(0, 120) || fallback;
}

/**
 * Persist a parsed attachment array to disk. Returns the array of stored
 * descriptors (no base64; the disk path replaces it). Resolves all writes
 * synchronously — typical attachment count is small (≤10), per-attachment
 * size ≤25MB, so the cost is bounded and we want failure to surface
 * before the chat reply lands.
 *
 * @param {string} messageId - chat_messages.id (UUID)
 * @param {ParsedAttachment[]} parsed - from parseAndValidateAttachments()
 * @param {{ dataDir?: string }} [opts]
 * @returns {Array<{type, name, mediaType, byteSize, path}>}
 */
function persistAttachments(messageId, parsed, opts = {}) {
  if (!messageId) throw new Error('persistAttachments requires messageId');
  if (!Array.isArray(parsed) || parsed.length === 0) return [];

  const root = getRoot(opts.dataDir);
  const dir = path.join(root, messageId);
  fs.mkdirSync(dir, { recursive: true });

  const out = [];
  const usedNames = new Set();
  for (const a of parsed) {
    let name = sanitizeName(a.name);
    // Prevent collisions when two attachments arrive with the same sanitized
    // name. Append `-2`, `-3`, etc. before the extension.
    let unique = name;
    let n = 2;
    while (usedNames.has(unique)) {
      const ext = path.extname(name);
      const stem = ext ? name.slice(0, -ext.length) : name;
      unique = `${stem}-${n}${ext}`;
      n++;
    }
    usedNames.add(unique);

    const filePath = path.join(dir, unique);
    fs.writeFileSync(filePath, Buffer.from(a.base64, 'base64'), { mode: 0o600 });

    out.push({
      type: a.type,
      name: unique,
      mediaType: a.mediaType,
      byteSize: a.byteSize,
      // Path relative to the data dir root — same convention used elsewhere
      // (e.g. listening recordings). Absolute path can be reconstructed via
      // resolveAttachmentPath() below.
      path: path.join(SUBDIR, messageId, unique),
    });
  }
  return out;
}

/**
 * Read a stored attachment back as a Buffer.
 * Validates that the resolved path stays under chat-attachments/ to prevent
 * traversal via a tampered DB row.
 */
function readAttachment(relativePath, opts = {}) {
  const root = getRoot(opts.dataDir);
  const dataDir = path.dirname(root); // strip /chat-attachments
  const abs = path.resolve(dataDir, relativePath);
  if (!abs.startsWith(root + path.sep) && abs !== root) {
    throw new Error(`attachment path traversal: ${relativePath}`);
  }
  return fs.readFileSync(abs);
}

/**
 * Remove all attachments for a given message id (used when a chat row is
 * deleted). No-ops if the dir doesn't exist.
 */
function removeAttachmentsForMessage(messageId, opts = {}) {
  if (!messageId) return;
  const dir = path.join(getRoot(opts.dataDir), messageId);
  try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
}

module.exports = {
  SUBDIR,
  getRoot,
  persistAttachments,
  readAttachment,
  removeAttachmentsForMessage,
  sanitizeName,
};
