import { captureException } from '@sentry/browser';
import Compressor from 'compressorjs';
import { format } from 'date-fns';
import {
  IconFile,
  IconFileSpreadsheet,
  IconFileWord,
  IconGif,
  IconPdf,
  IconPhoto,
  IconVideo,
} from '@material-hu/icons/tabler';
import { alpha, type Theme } from '@material-hu/mui/styles';
import { type FileAsset } from '@material-hu/types/attachments';

import defaultIcon from 'src/assets/images/defaultIcon.webp';
import imageIcon from 'src/assets/images/imageIcon.webp';
import videoIcon from 'src/assets/images/videoIcon.webp';
import excelIcon from 'src/assets/svg/excel.svg';
import defaultFileIcon from 'src/assets/svg/file.svg';
import gifIcon from 'src/assets/svg/gif.svg';
import pdfIcon from 'src/assets/svg/pdf.svg';
import wordIcon from 'src/assets/svg/word.svg';
import { tokenManager } from 'src/config/tokens';
import {
  IMAGE_COMPRESSION_MAX_DIMENSION,
  IMAGE_COMPRESSION_MIME_TYPE,
  IMAGE_COMPRESSION_QUALITY,
  SIGNS,
} from 'src/constants/attachments';
import { FileExtensions } from 'src/constants/filesPreview';
import { uploadImage } from 'src/services/attachments';
import {
  type Attachment,
  FileTypes,
  type FormFile,
} from 'src/types/attachments';
import { type DownloadOptions } from 'src/types/files';
import { bytesToSize, sizeToBytes } from 'src/utils/bytes';

import { type LokaliseTFunction } from './i18n';

export const sanitizeFileName = (name: string): string =>
  name
    .trim()
    .normalize('NFD') // decompose accented chars into base + combining mark (e.g. "é" → "e" + "´")
    .replace(/[\u0300-\u036f]/g, '') // remove the combining marks, leaving only ASCII base chars
    .replace(/\s+/g, '-') // spaces → hyphens
    .replace(/[^\w.-]/g, '_'); // remaining URL-unsafe chars → underscores

export const getFileNameExtension = (fileName: string) =>
  fileName?.split('.').pop();

export const getTypeName = (type: string) => {
  switch (type) {
    case 'VIDEO':
      return 'video';
    case 'IMAGE':
      return 'image';
    case 'AUDIO':
      return 'audio';
    default:
      return 'file';
  }
};

export const downloadFile = (data: BlobPart, title: string) => {
  const blob = new Blob([data]);
  const downloadUrl = URL.createObjectURL(blob);

  const link = document.createElement('a');
  link.href = downloadUrl;
  link.download = title;

  document.body.appendChild(link);
  link.click();

  document.body.removeChild(link);
  URL.revokeObjectURL(downloadUrl);
};

export const megabytesToBytes = (megabytes: number) => megabytes * 1024 * 1024;

export const getSimplifiedFileName = (name: string, type: string) => {
  const extension = getFileNameExtension(name);
  const typeName = getTypeName(type);
  const id = crypto.getRandomValues(new Uint16Array(1))[0];
  return `${typeName}-${id}.${extension}`;
};

export const getAttachment = (file: FormFile): Attachment => ({
  url: (file.fileObject && URL.createObjectURL(file.fileObject)) || file.url,
  type: file.type as FileTypes,
  size: typeof file.size === 'string' ? file.size : bytesToSize(file.size ?? 0),
  name: file.name ?? undefined,
  position: file.position,
  height: file.height ?? null,
  width: file.width ?? null,
  lastModified: file.fileObject?.lastModified,
});

export const urlToFile = async (url: string, filename?: string) => {
  const response = await fetch(url);
  const blob = await response.blob();
  const name = filename || getFileName(url, true);
  return new File([blob], name, { type: blob.type });
};

export const getFileName = (url: string, includeExtension = false) => {
  const urlObject = new URL(url);
  const { pathname } = urlObject;
  const parts = pathname.split('/');
  const fullFileName = parts[parts.length - 1];
  if (includeExtension) return fullFileName;
  return fullFileName.split('.')[0];
};

export const getFormFileFromAttachment = (file: Attachment): FormFile => ({
  name: file.name ?? null,
  url: file.url,
  size: sizeToBytes(file.size ?? ''),
  mime: file.type,
  fileObject: null,
  type: file.type,
  toUpload: false,
  height: file.height ?? undefined,
  width: file.width ?? undefined,
  position: file.position ?? undefined,
});

export const getNewFileForReplacement = (
  file: Attachment,
  data: File,
): FormFile => ({
  name: file?.name ?? null,
  size: data?.size,
  mime: data?.type,
  width: file?.width ?? undefined,
  height: file?.height ?? undefined,
  fileObject: data,
  type: FileTypes.IMAGE,
  toUpload: true,
});

export const getAttachmentFromPictureUrl = (url: string): Attachment => ({
  url,
  type: FileTypes.IMAGE,
  size: null,
  name: 'image',
  position: null,
  height: null,
  width: null,
});

export const getAttachmentFromFileUrl = (
  url: string,
  name: string = 'file',
) => ({
  url,
  type: FileTypes.FILE,
  size: null,
  name,
  position: null,
  height: null,
  width: null,
});

export const getBlob = async (url: string) => {
  try {
    const response = await fetch(url, { cache: 'reload' });
    const blob = await response.blob();

    return blob;
  } catch (err) {
    captureException(err, {
      extra: {
        url,
        message: 'Failed to fetch blob from URL',
      },
    });
    return null;
  }
};

export const getBlobUrl = (blob: Blob | null): string | null => {
  if (!blob) return null;

  return window.URL.createObjectURL(blob);
};

export const openBlob = (blob: Blob | null, blobUrl: string) => {
  const link = document.createElement('a');
  link.href = blobUrl;
  link.target = '_blank';
  link.rel = 'noopener noreferrer';
  link.type = blob?.type ?? '';
  link.click();
};

export type QueryParams = {
  query?: string;
  order?: string;
  orderBy?: string;
  page?: number;
  limit?: number;
};

export const filesTableHeader = (t: LokaliseTFunction) => [
  {
    id: 'FILE_NAME',
    text: t('TITLE'),
  },
  {
    id: 'CREATOR',
    text: t('OWNER'),
  },
  {
    id: 'UPDATED_AT',
    text: t('UPDATED_AT'),
  },
  {
    id: 'SIZE',
    text: t('SIZE'),
  },
  { id: 'ACTIONS', text: '' },
];

export const removeQueryParams = (url?: string) =>
  url ? url.split(SIGNS.QUESTION_MARK)[0] : '';

export const getFileExtension = (file: Attachment | FileAsset) => {
  const { name = '', url = '' } = file || {};
  const splitName = name
    ? name.split(SIGNS.DOT)
    : removeQueryParams(url).split('/').slice(-1).toString();
  const extension = splitName[splitName.length - 1]?.toLowerCase();
  return extension;
};

export const getFileIcon = (file: Attachment | FileAsset, preview = false) => {
  const extension = getFileExtension(file);
  switch (extension) {
    case FileExtensions.PDF:
      return pdfIcon;
    case FileExtensions.DOC:
    case FileExtensions.DOCX:
      return wordIcon;
    case FileExtensions.XLS:
    case FileExtensions.XLSX:
      return excelIcon;
    case FileExtensions.GIF:
      return gifIcon;
    case FileExtensions.PNG:
    case FileExtensions.JPG:
    case FileExtensions.JPEG:
      return imageIcon;
    case FileExtensions.MOV:
    case FileExtensions.MP4:
      return videoIcon;
    default:
      if (preview) {
        return defaultIcon;
      }
      return defaultFileIcon;
  }
};

export const getHuGoFileIcon = (file: Attachment) => {
  const extension = getFileExtension(file);
  switch (extension) {
    case FileExtensions.PDF:
      return IconPdf;
    case FileExtensions.DOC:
    case FileExtensions.DOCX:
      return IconFileWord;
    case FileExtensions.XLS:
    case FileExtensions.XLSX:
    case FileExtensions.CSV:
      return IconFileSpreadsheet;
    case FileExtensions.GIF:
      return IconGif;
    case FileExtensions.PNG:
    case FileExtensions.JPG:
    case FileExtensions.JPEG:
      return IconPhoto;
    case FileExtensions.MOV:
    case FileExtensions.MP4:
      return IconVideo;
    case FileExtensions.MP3:
    case FileExtensions.WAV:
    case FileExtensions.OGG:
    case FileExtensions.AAC:
    case FileExtensions.M4A:
      return IconFile;
    default:
      return IconFile;
  }
};

export const isImageFile = (ext?: string, type?: FileTypes) =>
  type === FileTypes.IMAGE ||
  ext === FileExtensions.PNG ||
  ext === FileExtensions.JPG ||
  ext === FileExtensions.GIF ||
  ext === FileExtensions.JPEG;

export const isVideoFile = (ext?: string, type?: FileTypes) =>
  type === FileTypes.VIDEO ||
  ext === FileExtensions.MP4 ||
  ext === FileExtensions.MOV;

export const isPdf = (ext?: string) => ext === FileExtensions.PDF;

export const isFileSupported = (ext?: string, type?: FileTypes) =>
  isImageFile(ext, type) || isVideoFile(ext, type) || isPdf(ext);

export const isFolder = (type?: FileTypes) => type === FileTypes.FOLDER;

export const getScalePdf = (value: number) => {
  switch (value) {
    case 1:
      return 1.1;
    case 2:
      return 1.3;
    case 3:
      return 1.5;
    case 4:
      return 1.7;
    default:
      return 1;
  }
};

export const getDownloadName = (title: string, extension?: string) => {
  const date = format(new Date(), 'yyyy-MM-dd_HH-mm-ss');
  if (extension) {
    return title + date + extension;
  }
  return title + date;
};

export const download = (url: string | null, title: string) => {
  if (!url) return;
  const link = document.createElement('a');
  link.href = url;
  link.setAttribute('download', title);

  link.click();
  link.remove();
};

export const downloadBlob = (data: BlobPart, title: string) => {
  const url = getBlobUrl(new Blob([data]));
  download(url, title);
};

export const downloadInChunks = async (
  url: string,
  title: string,
  options: DownloadOptions = {},
): Promise<boolean> => {
  const defaultChunkSize = 10 * 1024 * 1024;
  const { chunkSize = defaultChunkSize, onProgress } = options;

  try {
    const headResponse = await fetch(url, { method: 'HEAD', cache: 'reload' });
    const contentLength = headResponse.headers.get('content-length');
    const contentType = headResponse.headers.get('content-type');
    const acceptRanges = headResponse.headers.get('accept-ranges');

    if (!contentLength || !contentType) {
      return false;
    }

    const totalSize = parseInt(contentLength);

    if (acceptRanges !== 'bytes') {
      const testResponse = await fetch(url, {
        headers: { Range: 'bytes=0-0' },
        cache: 'reload',
      });
      if (testResponse.status !== 206) {
        return false;
      }
    }

    const chunks: ArrayBuffer[] = [];
    let loaded = 0;

    for (let start = 0; start < totalSize; start += chunkSize) {
      const end = Math.min(start + chunkSize - 1, totalSize - 1);

      const response = await fetch(url, {
        headers: { Range: `bytes=${start}-${end}` },
        cache: 'reload',
      });

      if (!response.ok && response.status !== 206) {
        throw new Error(`Chunk download failed: ${response.status}`);
      }

      const chunk = await response.arrayBuffer();
      chunks.push(chunk);

      loaded += chunk.byteLength;

      if (onProgress) {
        onProgress({
          loaded,
          total: totalSize,
          percentage: Math.round((loaded / totalSize) * 100),
        });
      }
    }

    const blob = new Blob(chunks, {
      type: contentType,
    });
    const blobUrl = getBlobUrl(blob);
    download(blobUrl, title);

    return true;
  } catch (err) {
    captureException(err, {
      extra: {
        url,
        message: 'Failed to download file in chunks',
      },
    });
    return false;
  }
};

const downloadWithDirectLink = (url: string, title: string) => {
  const link = document.createElement('a');
  link.href = url;
  link.target = '_blank';
  link.rel = 'noopener noreferrer';
  link.setAttribute('download', title);
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
};

export const downloadUrl = async (
  url: string,
  title: string,
  options?: DownloadOptions,
) => {
  try {
    const headResponse = await fetch(url, { method: 'HEAD', cache: 'reload' });
    const contentLength = headResponse.headers.get('content-length');
    const fileSizeInMB = contentLength
      ? parseInt(contentLength) / (1024 * 1024)
      : 0;

    if (fileSizeInMB > 100) {
      const success = await downloadInChunks(url, title, options);
      if (success) return;
    }
  } catch {
    // HEAD or chunked download failed (e.g., CORS restriction) — skip to blob attempt
  }

  const blob = await getBlob(url);
  if (blob) {
    const blobUrl = getBlobUrl(blob);
    download(blobUrl, title);
    return;
  }

  downloadWithDirectLink(url, title);
};

export const fileToAttachment = (file: File) =>
  new Promise<Partial<FileAsset>>(resolve => {
    const objectURL = URL.createObjectURL(file);
    const result = {
      url: undefined,
      previewUrl: undefined,
      previewType: undefined,
      file,
      name: file.name,
      size: bytesToSize(file.size),
      id: undefined,
    };

    if (isImage(file)) {
      const i = new Image();
      i.onload = () => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => {
          resolve({
            ...result,
            type: FileTypes.IMAGE,
            width: i.width,
            height: i.height,
          });
        };
      };
      i.src = objectURL;
    } else if (isVideo(file)) {
      const v = document.createElement('video');
      v.src = objectURL;
      // eslint-disable-next-line func-names
      v.addEventListener('loadedmetadata', function () {
        resolve({
          ...result,
          type: FileTypes.VIDEO,
          width: this.videoWidth,
          height: this.videoHeight,
        });
      });
    } else {
      resolve({
        ...result,
        type: FileTypes.FILE,
        width: undefined,
        height: undefined,
      });
    }
  });

export const getArrayBuffer = (file: File): Promise<ArrayBuffer> =>
  new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = () => resolve(reader.result as ArrayBuffer);
    reader.onerror = error => reject(error);
  });

export const isImage = (file: File) => file.type.includes('image');
export const isVideo = (file: File) => file.type.includes('video');

export const getContentType = (filename: string) => {
  switch (getFileNameExtension(filename)) {
    case 'ppsm':
      return 'application/vnd.ms-powerpoint.slideshow.macroenabled.12';
    case 'pptm':
      return 'application/vnd.ms-powerpoint.presentation.macroEnabled.12';
    case 'ppsx':
      return 'application/vnd.openxmlformats-officedocument.presentationml.slideshow';
    case 'xlsm':
      return 'application/vnd.ms-excel.sheet.macroEnabled';
    case 'xlsb':
      return 'application/vnd.ms-excel.sheet.binary.macroEnabled.12';
    default:
      return '';
  }
};

export const blobToFile = (blob: Blob, filename: string) =>
  new File([blob], filename, { type: blob.type });

export const compressFile = (
  file: File,
  options?: {
    width?: number;
    height?: number;
    quality?: number;
    mimeType?: string;
  },
) => {
  const {
    width = IMAGE_COMPRESSION_MAX_DIMENSION,
    height = IMAGE_COMPRESSION_MAX_DIMENSION,
    quality = IMAGE_COMPRESSION_QUALITY,
    mimeType = IMAGE_COMPRESSION_MIME_TYPE,
  } = options || {};

  return new Promise<File>((resolve, reject) => {
    if (isImage(file) && !file.type.includes('gif')) {
      return new Compressor(file, {
        quality,
        maxWidth: width,
        maxHeight: height,
        mimeType: mimeType,
        success: (compressedResult: File) =>
          resolve(blobToFile(compressedResult, compressedResult.name)),
        error: err => reject(err),
      });
    }
    return resolve(file);
  });
};

export const addFileType = (file: File) => {
  if (file.type) {
    return file;
  }

  return new File([file], file.name, { type: getContentType(file.name) });
};

export const getFileType = (extension: string) => {
  switch (extension?.toLowerCase()) {
    case 'jpg':
    case 'jpeg':
    case 'png':
    case 'webp':
      return FileTypes.IMAGE;
    case 'mpeg':
    case 'webm':
    case 'mov':
    case 'mp4':
    case 'avi':
      return FileTypes.VIDEO;
    case 'csv':
    case 'xls':
    case 'xlsx':
    case 'xlsm':
    case 'xlsb':
      return FileTypes.EXCEL;
    case 'pdf':
      return FileTypes.PDF;
    case 'pptx':
    case 'ppt':
    case 'ppsm':
    case 'ppsx':
      return FileTypes.PPT;
    case 'doc':
    case 'docx':
      return FileTypes.DOC;
    case 'gif':
      return FileTypes.GIF;
    case 'mp3':
    case 'wav':
    case 'ogg':
    case 'aac':
    case 'm4a':
      return FileTypes.AUDIO;
    default:
      return FileTypes.FILE;
  }
};

export const getDropzoneStyles = (
  isDragActive: boolean,
  loadingFiles: File[],
  theme: Theme,
) => {
  if (isDragActive) {
    return {
      border: `1px dashed ${theme.palette.primary.main}`,
      backgroundColor: `${alpha(theme.palette.primary.main, 0.08)}`,
    };
  } else if (loadingFiles.length > 0) {
    return {
      border: '1px dashed #0000001F',
      backgroundColor: '#f0f0f0',
      cursor: 'not-allowed',
    };
  } else {
    return {
      border: '1px dashed #0000001F',
      backgroundColor: '#FFFFFF',
    };
  }
};

export const preprocessImage = async (image: {
  file: File | null;
  url: string | null;
}) => {
  let imageUrl = image?.url ? image?.url.split('?')[0] : undefined;

  if (image?.file) {
    const bannerFile = image.file;
    const { url } = await uploadImage({
      name: bannerFile.name,
      size: bannerFile.size,
      mime: bannerFile.type,
      fileObject: bannerFile,
      type: FileTypes.IMAGE,
    });
    imageUrl = url;
  }
  return imageUrl;
};

export const createLocalUrl = (file: File) => {
  return (window.URL || window.webkitURL).createObjectURL(file);
};

export const revokeLocalUrl = (url: string) => {
  (window.URL || window.webkitURL).revokeObjectURL(url);
};

type Subtitle = { startTime: number; endTime: number; text: string };

export const parseVTTFile = (vttText: string) => {
  const subtitles: Subtitle[] = [];
  const lines = vttText.split('\n');
  let currentSubtitle: Subtitle = { startTime: 0, endTime: 0, text: '' };

  lines.forEach((line, index) => {
    const trimmedLine = line.trim();

    if (!trimmedLine || trimmedLine === 'WEBVTT') return;

    const match = trimmedLine.match(
      /^(\d{2}):(\d{2}):(\d{2})\.(\d{3}) --> (\d{2}):(\d{2}):(\d{2})\.(\d{3})$/,
    );
    if (match) {
      currentSubtitle.startTime =
        parseInt(match[1]) * 3600 +
        parseInt(match[2]) * 60 +
        parseInt(match[3]) +
        parseInt(match[4]) / 1000;
      currentSubtitle.endTime =
        parseInt(match[5]) * 3600 +
        parseInt(match[6]) * 60 +
        parseInt(match[7]) +
        parseInt(match[8]) / 1000;
      return;
    }

    if (currentSubtitle.startTime && currentSubtitle.endTime) {
      const cleanedLine = trimmedLine.replace(/<[^>]*>/g, '');
      const separator = currentSubtitle.text ? '\n' : '';
      currentSubtitle.text = currentSubtitle.text + separator + cleanedLine;
    }

    if (!lines[index + 1]?.trim() && currentSubtitle.text) {
      subtitles.push({ ...currentSubtitle });
      currentSubtitle = { startTime: 0, endTime: 0, text: '' };
    }
  });

  return subtitles;
};

export const getIcsFileName = (calendarEvent: string) => {
  const eventNameMatch = calendarEvent.match(/NAME:(.*?)(?:\r?\n|$)/);

  const fileName =
    eventNameMatch && eventNameMatch.length > 1 ? eventNameMatch[1] : 'event';

  return `${fileName}.ics`;
};

export const safeConvertFileAssetToAttachment = (
  fileAsset: FileAsset,
): Attachment => {
  const { externalReference, ...rest } = fileAsset;

  const type =
    (fileAsset.type as FileTypes) ??
    getFileType(getFileNameExtension(fileAsset.name) ?? '');

  return {
    ...rest,
    type,
    position: 0,
    ...(externalReference && {
      externalReference: {
        id: externalReference,
        origin: 'PUBLIC_URL',
      },
    }),
  } as Attachment;
};

export const getCopyImage = async (url: string): Promise<Blob> => {
  const { accessToken } = tokenManager.getCorrectToken();

  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  const blob = await response.blob();

  if (blob.type !== 'image/jpeg') return blob;

  const imageBitmap = await createImageBitmap(blob);

  const canvas = document.createElement('canvas');
  canvas.width = imageBitmap.width;
  canvas.height = imageBitmap.height;

  const ctx = canvas.getContext('2d');
  if (!ctx) throw new Error();

  ctx.drawImage(imageBitmap, 0, 0);

  return new Promise<Blob>((resolve, reject) => {
    canvas.toBlob(
      convertedBlob => {
        if (convertedBlob) resolve(convertedBlob);
        else reject(new Error());
      },
      'image/png',
      IMAGE_COMPRESSION_QUALITY,
    );
  });
};

export const base64ToFile = (dataUrl: string, filename: string): File => {
  const [header, base64Body] = dataUrl.split(',');
  const mime = header?.match(/:(.*?);/)?.[1] ?? 'image/png';
  const byteString = atob(base64Body ?? '');
  const bytes = new Uint8Array(byteString.length);
  for (let i = 0; i < byteString.length; i++) {
    bytes[i] = byteString.charCodeAt(i);
  }
  return new File([bytes], filename, { type: mime });
};
