/* eslint-disable import/no-import-module-exports */
/* eslint-disable no-async-promise-executor */
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable no-restricted-syntax */
/**
 * This file handles attachments for us.
 * If the attachment filepath is an encrypted one. It will decrypt it, cache it, and return the blob url to it.
 * An interval is run from time to time to cleanup old blobs loaded and not needed anymore (based on last access timestamp).
 *
 *
 */
import path from 'path';

import * as fse from 'fs-extra';

import { DURATION } from '../constants';
import { makeObjectUrl, urlToBlob } from '../../types/attachments/VisualAttachment';
import {
  getAbsoluteAttachmentPath as msgGetAbsoluteAttachmentPath,
  getAttachmentPath,
} from '../../types/MessageAttachment';
import { decryptAttachmentBufferRenderer } from '../../util/local_attachments_encrypter';

export const urlToDecryptedBlobMap = new Map<
  string,
  { decrypted: string; lastAccessTimestamp: number; forceRetain: boolean }
>();
export const urlToDecryptingPromise = new Map<string, Promise<string>>();

export const cleanUpOldDecryptedMedias = () => {
  const currentTimestamp = Date.now();
  let countCleaned = 0;
  let countKept = 0;
  let keptAsAvatars = 0;

  window?.log?.info('Starting cleaning of medias blobs...');
  for (const iterator of urlToDecryptedBlobMap) {
    if (
      iterator[1].forceRetain &&
      iterator[1].lastAccessTimestamp < currentTimestamp - DURATION.DAYS * 7
    ) {
      // keep forceRetained items for at most 7 days
      keptAsAvatars++;
    } else if (iterator[1].lastAccessTimestamp < currentTimestamp - DURATION.HOURS * 1) {
      // if the last access is older than one hour, revoke the url and remove it.

      URL.revokeObjectURL(iterator[1].decrypted);
      urlToDecryptedBlobMap.delete(iterator[0]);
      countCleaned++;
    } else {
      countKept++;
    }
  }
  window?.log?.info(
    `Clean medias blobs: cleaned/kept/keptAsAvatars: ${countCleaned}:${countKept}:${keptAsAvatars}`
  );
};

export const getLocalAttachmentPath = () => {
  return getAttachmentPath();
};

export const getAbsoluteAttachmentPath = (url: string) => {
  return msgGetAbsoluteAttachmentPath(url);
};

export const readFileContent = async (url: string) => {
  return fse.readFile(url);
};

export const getDecryptedMediaUrl = async (
  url: string,
  contentType: string,
  isAvatar: boolean
): Promise<string> => {
  if (!url) {
    return url;
  }
  if (url.startsWith('blob:')) {
    return url;
  }

  const isAbsolute = path.isAbsolute(url);

  if (
    (isAbsolute &&
      exports.getLocalAttachmentPath &&
      url.startsWith(exports.getLocalAttachmentPath())) ||
    fse.pathExistsSync(exports.getAbsoluteAttachmentPath(url))
  ) {
    // this is a file encoded by session on our current attachments path.
    // we consider the file is encrypted.
    // if it's not, the hook caller has to fallback to setting the img src as an url to the file instead and load it
    if (urlToDecryptedBlobMap.has(url)) {
      // typescript does not realize that the `has()` above makes sure the `get()` is not undefined

      // refresh the last access timestamp so we keep the one being currently in use
      const existing = urlToDecryptedBlobMap.get(url);
      const existingObjUrl = existing?.decrypted as string;

      urlToDecryptedBlobMap.set(url, {
        decrypted: existingObjUrl,
        lastAccessTimestamp: Date.now(),
        forceRetain: existing?.forceRetain || false,
      });

      return existingObjUrl;
    }

    if (urlToDecryptingPromise.has(url)) {
      return urlToDecryptingPromise.get(url) as Promise<string>;
    }

    urlToDecryptingPromise.set(
      url,
      new Promise(async (resolve, reject) => {
        // window.log.debug('about to read and decrypt file :', url, path.isAbsolute(url));
        try {
          const absUrl = path.isAbsolute(url) ? url : getAbsoluteAttachmentPath(url);
          const encryptedFileContent = await readFileContent(absUrl);
          const decryptedContent = await decryptAttachmentBufferRenderer(
            encryptedFileContent.buffer
          );
          if (decryptedContent?.length) {
            const arrayBuffer = decryptedContent.buffer;
            const obj = makeObjectUrl(arrayBuffer, contentType);

            if (!urlToDecryptedBlobMap.has(url)) {
              urlToDecryptedBlobMap.set(url, {
                decrypted: obj,
                lastAccessTimestamp: Date.now(),
                forceRetain: isAvatar,
              });
            }
            // window.log.debug(' file decrypted :', url, ' as ', obj);
            urlToDecryptingPromise.delete(url);
            resolve(obj);
            return;
          }
          // failed to decrypt, fallback to url image loading
          // it might be a media we received before the update encrypting attachments locally.
          urlToDecryptingPromise.delete(url);
          window.log.info('error decrypting file :', url);
          resolve(url);
        } catch (e) {
          window.log.warn(e);
          reject(e.message);
        }
      })
    );
    return urlToDecryptingPromise.get(url) as Promise<string>;
  }
  // Not sure what we got here. Just return the file.

  return url;
};

/**
 *
 * Returns the already decrypted URL or null
 */
export const getAlreadyDecryptedMediaUrl = (url: string): string | null => {
  if (!url) {
    return null;
  }
  if (url.startsWith('blob:')) {
    return url;
  }
  if (exports.getLocalAttachmentPath() && url.startsWith(exports.getLocalAttachmentPath())) {
    if (urlToDecryptedBlobMap.has(url)) {
      const existing = urlToDecryptedBlobMap.get(url);

      const existingObjUrl = existing?.decrypted as string;
      urlToDecryptedBlobMap.set(url, {
        decrypted: existingObjUrl,
        lastAccessTimestamp: Date.now(),
        forceRetain: existing?.forceRetain || false,
      });
      return existingObjUrl;
    }
  }
  return null;
};

export const getDecryptedBlob = async (url: string, contentType: string): Promise<Blob> => {
  const decryptedUrl = await getDecryptedMediaUrl(url, contentType, false);
  return urlToBlob(decryptedUrl);
};

/**
 * This function should only be used for testing purpose
 */
export const resetDecryptedUrlForTesting = () => {
  urlToDecryptedBlobMap.clear();
  urlToDecryptingPromise.clear();
};
