import { HttpError } from './errors.js';

export async function parseMultipartUpload(req, options) {
  const contentType = req.headers['content-type'] || '';
  const boundaryMatch = contentType.match(/multipart\/form-data;\s*boundary=(?:"([^"]+)"|([^;]+))/i);
  if (!boundaryMatch) {
    throw new HttpError(415, 'Expected multipart form data.');
  }

  const body = await readRequestBody(req, options.maxRequestBytes);
  const boundary = Buffer.from(`--${boundaryMatch[1] || boundaryMatch[2]}`);
  const parts = splitMultipart(body, boundary);
  let upload = null;

  for (const part of parts) {
    const separator = part.indexOf('\r\n\r\n');
    if (separator === -1) continue;
    const headerText = part.subarray(0, separator).toString('latin1');
    const content = trimTrailingCrlf(part.subarray(separator + 4));
    const disposition = headerText.match(/^content-disposition:\s*form-data;\s*(.+)$/im);
    if (!disposition) continue;

    const attrs = parseDispositionAttrs(disposition[1]);
    if (attrs.name !== options.fieldName || !attrs.filename) continue;
    if (upload) throw new HttpError(400, 'Upload one image at a time.');
    if (content.length > options.maxFileBytes) throw new HttpError(413, 'Image exceeds the 5 MB limit.');
    upload = {
      filename: cleanFilename(attrs.filename),
      buffer: Buffer.from(content)
    };
  }

  if (!upload) throw new HttpError(400, 'Missing image file.');
  return upload;
}

async function readRequestBody(req, maxBytes) {
  const chunks = [];
  let total = 0;
  for await (const chunk of req) {
    total += chunk.length;
    if (total > maxBytes) {
      throw new HttpError(413, 'Upload request is too large.');
    }
    chunks.push(chunk);
  }
  return Buffer.concat(chunks, total);
}

function splitMultipart(body, boundary) {
  const parts = [];
  let cursor = body.indexOf(boundary);
  while (cursor !== -1) {
    cursor += boundary.length;
    if (body[cursor] === 0x2d && body[cursor + 1] === 0x2d) break;
    if (body[cursor] === 0x0d && body[cursor + 1] === 0x0a) cursor += 2;
    const next = body.indexOf(boundary, cursor);
    if (next === -1) break;
    parts.push(body.subarray(cursor, next));
    cursor = next;
  }
  return parts;
}

function trimTrailingCrlf(buffer) {
  if (buffer.length >= 2 && buffer[buffer.length - 2] === 0x0d && buffer[buffer.length - 1] === 0x0a) {
    return buffer.subarray(0, -2);
  }
  return buffer;
}

function parseDispositionAttrs(value) {
  const attrs = {};
  for (const part of value.split(';')) {
    const [rawKey, ...rawValue] = part.trim().split('=');
    if (!rawKey || rawValue.length === 0) continue;
    attrs[rawKey.toLowerCase()] = rawValue.join('=').trim().replace(/^"|"$/g, '');
  }
  return attrs;
}

function cleanFilename(filename) {
  const base = filename.split(/[\\/]/).pop() || 'upload';
  return base.replace(/[^\w.-]+/g, '_').slice(0, 120);
}
