import { BadRequestException } from '@nestjs/common';
import { extname } from 'path';

export type AllowedMediaType = 'image' | 'audio' | 'video';

export type MediaUploadFile = {
  originalname?: string;
  mimetype?: string;
  size?: number;
  buffer?: Buffer;
};

export const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.webp'] as const;
export const IMAGE_MIMETYPES = ['image/jpeg', 'image/png', 'image/webp'] as const;

export const AUDIO_EXTENSIONS = ['.mp3', '.m4a', '.wav', '.aac', '.ogg', '.webm'] as const;
export const AUDIO_MIMETYPES = [
  'audio/mpeg',
  'audio/mp3',
  'audio/mp4',
  'audio/x-m4a',
  'audio/m4a',
  'audio/wav',
  'audio/x-wav',
  'audio/wave',
  'audio/aac',
  'audio/ogg',
  'audio/webm',
] as const;

export const VIDEO_EXTENSIONS = ['.mp4', '.mov', '.webm'] as const;
export const VIDEO_MIMETYPES = ['video/mp4', 'video/quicktime', 'video/webm'] as const;

export const MEDIA_MAX_SIZE_BYTES = {
  postsImage: 10 * 1024 * 1024,
  postsAudio: 20 * 1024 * 1024,
  postsVideo: 100 * 1024 * 1024,
  userImage: 5 * 1024 * 1024,
  marketplaceImage: 8 * 1024 * 1024,
  supportImage: 5 * 1024 * 1024,
  collaborationAudio: 20 * 1024 * 1024,
} as const;

const EXTENSIONS_BY_TYPE: Record<AllowedMediaType, readonly string[]> = {
  image: IMAGE_EXTENSIONS,
  audio: AUDIO_EXTENSIONS,
  video: VIDEO_EXTENSIONS,
};

const EXTENSION_BY_MIMETYPE: Record<AllowedMediaType, Record<string, string>> = {
  image: {
    'image/jpeg': '.jpg',
    'image/png': '.png',
    'image/webp': '.webp',
  },
  audio: {
    'audio/mpeg': '.mp3',
    'audio/mp3': '.mp3',
    'audio/mp4': '.m4a',
    'audio/x-m4a': '.m4a',
    'audio/m4a': '.m4a',
    'audio/wav': '.wav',
    'audio/x-wav': '.wav',
    'audio/wave': '.wav',
    'audio/aac': '.aac',
    'audio/ogg': '.ogg',
    'audio/webm': '.webm',
  },
  video: {
    'video/mp4': '.mp4',
    'video/quicktime': '.mov',
    'video/webm': '.webm',
  },
};

export type AssertAllowedMediaOptions = {
  invalidMessage?: string;
  maxSizeBytes?: number;
  maxSizeMessage?: string;
};

export function getFileExtension(filename?: string): string {
  return extname(filename ?? '').toLowerCase();
}

export function isAllowedImageFile(file: MediaUploadFile): boolean {
  return isAllowedMediaFile(file, 'image');
}

export function isAllowedAudioFile(file: MediaUploadFile): boolean {
  return isAllowedMediaFile(file, 'audio');
}

export function isAllowedVideoFile(file: MediaUploadFile): boolean {
  return isAllowedMediaFile(file, 'video');
}

export function resolveImageExtension(file: MediaUploadFile): string {
  return resolveMediaExtensionOrThrow(file, 'image');
}

export function resolveAudioExtension(file: MediaUploadFile): string {
  return resolveMediaExtensionOrThrow(file, 'audio');
}

export function resolveVideoExtension(file: MediaUploadFile): string {
  return resolveMediaExtensionOrThrow(file, 'video');
}

export function assertAllowedMediaFile(
  file: MediaUploadFile,
  type: AllowedMediaType,
  options: AssertAllowedMediaOptions = {},
): string {
  const extension = resolveMediaExtension(file, type);
  if (!extension) {
    throw new BadRequestException(options.invalidMessage ?? defaultInvalidMessage(type));
  }

  if (options.maxSizeBytes && typeof file.size === 'number' && file.size > options.maxSizeBytes) {
    throw new BadRequestException(options.maxSizeMessage ?? `${type} file is too large`);
  }

  return extension;
}

export function getStaticMediaContentType(extension: string, filePath = ''): string | undefined {
  const normalizedExtension = extension.toLowerCase();
  switch (normalizedExtension) {
    case '.jpg':
    case '.jpeg':
      return 'image/jpeg';
    case '.png':
      return 'image/png';
    case '.webp':
      return 'image/webp';
    case '.mp3':
      return 'audio/mpeg';
    case '.m4a':
      return 'audio/mp4';
    case '.wav':
      return 'audio/wav';
    case '.aac':
      return 'audio/aac';
    case '.ogg':
      return 'audio/ogg';
    case '.mov':
      return 'video/quicktime';
    case '.mp4':
      return 'video/mp4';
    case '.webm':
      return isAudioUploadPath(filePath) ? 'audio/webm' : 'video/webm';
    default:
      return undefined;
  }
}

function isAllowedMediaFile(file: MediaUploadFile, type: AllowedMediaType): boolean {
  return !!resolveMediaExtension(file, type);
}

function resolveMediaExtensionOrThrow(file: MediaUploadFile, type: AllowedMediaType): string {
  const extension = resolveMediaExtension(file, type);
  if (!extension) {
    throw new BadRequestException(defaultInvalidMessage(type));
  }
  return extension;
}

function resolveMediaExtension(file: MediaUploadFile, type: AllowedMediaType): string | null {
  const extension = getFileExtension(file.originalname);
  const mimetype = file.mimetype?.toLowerCase();
  const mimeExtension = mimetype ? EXTENSION_BY_MIMETYPE[type][mimetype] : undefined;
  const extensionAllowed = EXTENSIONS_BY_TYPE[type].includes(extension);

  if (extension && !extensionAllowed) {
    return null;
  }
  if (mimetype && !mimeExtension) {
    return null;
  }
  if (extensionAllowed && mimeExtension && !extensionsAreCompatible(extension, mimeExtension)) {
    return null;
  }

  const resolved = extensionAllowed ? extension : mimeExtension;
  if (!resolved) {
    return null;
  }
  if (file.buffer && file.buffer.length >= 12 && !hasExpectedFileSignature(file.buffer, resolved)) {
    return null;
  }
  return resolved;
}

function extensionsAreCompatible(left: string, right: string): boolean {
  const normalize = (value: string) => (value === '.jpeg' ? '.jpg' : value);
  return normalize(left) === normalize(right);
}

function hasExpectedFileSignature(buffer: Buffer, extension: string): boolean {
  const ascii = buffer.subarray(0, 16).toString('ascii');
  switch (extension) {
    case '.jpg':
    case '.jpeg':
      return buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff;
    case '.png':
      return buffer.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]));
    case '.webp':
      return ascii.startsWith('RIFF') && ascii.slice(8, 12) === 'WEBP';
    case '.wav':
      return ascii.startsWith('RIFF') && ascii.slice(8, 12) === 'WAVE';
    case '.ogg':
      return ascii.startsWith('OggS');
    case '.webm':
      return buffer[0] === 0x1a && buffer[1] === 0x45 && buffer[2] === 0xdf && buffer[3] === 0xa3;
    case '.mp3':
      return ascii.startsWith('ID3') || (buffer[0] === 0xff && (buffer[1] & 0xe0) === 0xe0);
    case '.aac':
      return buffer[0] === 0xff && (buffer[1] & 0xf6) === 0xf0;
    case '.m4a':
    case '.mp4':
    case '.mov':
      return ascii.slice(4, 8) === 'ftyp';
    default:
      return false;
  }
}

function defaultInvalidMessage(type: AllowedMediaType): string {
  if (type === 'image') {
    return 'File must be jpg, jpeg, png, or webp';
  }
  if (type === 'video') {
    return 'File must be mp4, mov, or webm';
  }
  return 'File must be mp3, m4a, wav, aac, ogg, or webm';
}

function isAudioUploadPath(filePath: string): boolean {
  const normalizedPath = filePath.replace(/\\/g, '/').toLowerCase();
  return (
    normalizedPath.includes('/audio/') ||
    normalizedPath.includes('/audios/') ||
    normalizedPath.includes('/ai-music/') ||
    normalizedPath.includes('/collaboration-requests/audio/')
  );
}
