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

const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);

export function validateImage(buffer, limits) {
  if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
    throw new HttpError(400, 'Upload is empty.');
  }
  if (buffer.length > limits.maxBytes) {
    throw new HttpError(413, 'Image exceeds the 5 MB limit.');
  }

  const image = detectImage(buffer);
  if (!image) {
    throw new HttpError(415, 'Only PNG and JPEG images are accepted.');
  }
  if (image.width < 1 || image.height < 1) {
    throw new HttpError(400, 'Image dimensions are invalid.');
  }
  if (image.width > limits.maxWidth || image.height > limits.maxHeight) {
    throw new HttpError(413, `Image dimensions must be at most ${limits.maxWidth}x${limits.maxHeight}.`);
  }
  if (image.width * image.height > limits.maxPixels) {
    throw new HttpError(413, 'Image has too many pixels.');
  }
  return { ...image, byteSize: buffer.length };
}

export function detectImage(buffer) {
  if (buffer.subarray(0, 8).equals(PNG_SIGNATURE) && buffer.length >= 24) {
    return {
      format: 'png',
      ext: 'png',
      mime: 'image/png',
      width: buffer.readUInt32BE(16),
      height: buffer.readUInt32BE(20)
    };
  }

  if (buffer.length > 4 && buffer[0] === 0xff && buffer[1] === 0xd8) {
    const jpeg = readJpegDimensions(buffer);
    if (jpeg) {
      return {
        format: 'jpeg',
        ext: 'jpg',
        mime: 'image/jpeg',
        width: jpeg.width,
        height: jpeg.height
      };
    }
  }

  return null;
}

function readJpegDimensions(buffer) {
  let offset = 2;
  while (offset < buffer.length) {
    while (buffer[offset] === 0xff) offset += 1;
    const marker = buffer[offset];
    offset += 1;

    if (marker === 0xd9 || marker === 0xda) return null;
    if (offset + 2 > buffer.length) return null;

    const length = buffer.readUInt16BE(offset);
    if (length < 2 || offset + length > buffer.length) return null;

    if (isStartOfFrame(marker)) {
      if (length < 7) return null;
      return {
        height: buffer.readUInt16BE(offset + 3),
        width: buffer.readUInt16BE(offset + 5)
      };
    }

    offset += length;
  }
  return null;
}

function isStartOfFrame(marker) {
  return (
    (marker >= 0xc0 && marker <= 0xc3) ||
    (marker >= 0xc5 && marker <= 0xc7) ||
    (marker >= 0xc9 && marker <= 0xcb) ||
    (marker >= 0xcd && marker <= 0xcf)
  );
}
