import {
  StoredObject,
  StoredObjectStatus,
  StoredObjectStatusChange,
  StoredObjectVersion,
} from "../../types";
import { makeFetch } from "../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";

const MIMES_EDIT = new Set([
  "application/vnd.ms-powerpoint",
  "application/vnd.ms-excel",
  "application/vnd.oasis.opendocument.text",
  "application/vnd.oasis.opendocument.text-flat-xml",
  "application/vnd.oasis.opendocument.spreadsheet",
  "application/vnd.oasis.opendocument.spreadsheet-flat-xml",
  "application/vnd.oasis.opendocument.presentation",
  "application/vnd.oasis.opendocument.presentation-flat-xml",
  "application/vnd.oasis.opendocument.graphics",
  "application/vnd.oasis.opendocument.graphics-flat-xml",
  "application/vnd.oasis.opendocument.chart",
  "application/msword",
  "application/vnd.ms-excel",
  "application/vnd.ms-powerpoint",
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "application/vnd.ms-word.document.macroEnabled.12",
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
  "application/vnd.ms-excel.sheet.macroEnabled.12",
  "application/vnd.openxmlformats-officedocument.presentationml.presentation",
  "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
  "application/x-dif-document",
  "text/spreadsheet",
  "text/csv",
  "application/x-dbase",
  "text/rtf",
  "text/plain",
  "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
]);

const MIMES_VIEW = new Set([
  ...MIMES_EDIT,
  [
    "image/svg+xml",
    "application/vnd.sun.xml.writer",
    "application/vnd.sun.xml.calc",
    "application/vnd.sun.xml.impress",
    "application/vnd.sun.xml.draw",
    "application/vnd.sun.xml.writer.global",
    "application/vnd.sun.xml.writer.template",
    "application/vnd.sun.xml.calc.template",
    "application/vnd.sun.xml.impress.template",
    "application/vnd.sun.xml.draw.template",
    "application/vnd.oasis.opendocument.text-master",
    "application/vnd.oasis.opendocument.text-template",
    "application/vnd.oasis.opendocument.text-master-template",
    "application/vnd.oasis.opendocument.spreadsheet-template",
    "application/vnd.oasis.opendocument.presentation-template",
    "application/vnd.oasis.opendocument.graphics-template",
    "application/vnd.ms-word.template.macroEnabled.12",
    "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
    "application/vnd.ms-excel.template.macroEnabled.12",
    "application/vnd.openxmlformats-officedocument.presentationml.template",
    "application/vnd.ms-powerpoint.template.macroEnabled.12",
    "application/vnd.wordperfect",
    "application/x-aportisdoc",
    "application/x-hwp",
    "application/vnd.ms-works",
    "application/x-mswrite",
    "application/vnd.lotus-1-2-3",
    "image/cgm",
    "image/vnd.dxf",
    "image/x-emf",
    "image/x-wmf",
    "application/coreldraw",
    "application/vnd.visio2013",
    "application/vnd.visio",
    "application/vnd.ms-visio.drawing",
    "application/x-mspublisher",
    "application/x-sony-bbeb",
    "application/x-gnumeric",
    "application/macwriteii",
    "application/x-iwork-numbers-sffnumbers",
    "application/vnd.oasis.opendocument.text-web",
    "application/x-pagemaker",
    "application/x-fictionbook+xml",
    "application/clarisworks",
    "image/x-wpg",
    "application/x-iwork-pages-sffpages",
    "application/x-iwork-keynote-sffkey",
    "application/x-abiword",
    "image/x-freehand",
    "application/vnd.sun.xml.chart",
    "application/x-t602",
    "image/bmp",
    "image/png",
    "image/gif",
    "image/tiff",
    "image/jpg",
    "image/jpeg",
    "application/pdf",
  ],
]);

export interface SignedUrlGet {
  method: "GET" | "HEAD";
  url: string;
  expires: number;
  object_name: string;
}

function is_extension_editable(mimeType: string): boolean {
  return MIMES_EDIT.has(mimeType);
}

function is_extension_viewable(mimeType: string): boolean {
  return MIMES_VIEW.has(mimeType);
}

function build_convert_link(uuid: string) {
  return `/chill/wopi/convert/${uuid}`;
}

function build_download_info_link(
  storedObject: StoredObject,
  atVersion: null | StoredObjectVersion,
): string {
  const url = `/api/1.0/doc-store/async-upload/temp_url/${storedObject.uuid}/generate/get`;

  if (null !== atVersion) {
    const params = new URLSearchParams({ version: atVersion.filename });

    return url + "?" + params.toString();
  }

  return url;
}

async function download_info_link(
  storedObject: StoredObject,
  atVersion: null | StoredObjectVersion,
): Promise<SignedUrlGet> {
  return makeFetch("GET", build_download_info_link(storedObject, atVersion));
}

function build_wopi_editor_link(uuid: string, returnPath?: string) {
  if (returnPath === undefined) {
    returnPath =
      window.location.pathname + window.location.search + window.location.hash;
  }

  return (
    `/chill/wopi/edit/${uuid}?returnPath=` + encodeURIComponent(returnPath)
  );
}

function download_doc(url: string): Promise<Blob> {
  return window.fetch(url).then((r) => {
    if (r.ok) {
      return r.blob();
    }

    throw new Error("Could not download document");
  });
}

async function download_and_decrypt_doc(
  storedObject: StoredObject,
  atVersion: null | StoredObjectVersion,
): Promise<Blob> {
  const algo = "AES-CBC";

  const atVersionToDownload = atVersion ?? storedObject.currentVersion;

  if (null === atVersionToDownload) {
    throw new Error("no version associated to stored object");
  }

  let downloadInfo;
  if (
    typeof storedObject._links !== "undefined" &&
    typeof storedObject._links.downloadLink !== "undefined"
  ) {
    downloadInfo = storedObject._links.downloadLink;
  } else {
    downloadInfo = await download_info_link(storedObject, atVersionToDownload);
  }

  const rawResponse = await window.fetch(downloadInfo.url);

  if (!rawResponse.ok) {
    throw new Error(
      "error while downloading raw file " +
        rawResponse.status +
        " " +
        rawResponse.statusText,
    );
  }

  if (atVersionToDownload.iv.length === 0) {
    return rawResponse.blob();
  }

  const rawBuffer = await rawResponse.arrayBuffer();
  try {
    const key = await window.crypto.subtle.importKey(
      "jwk",
      atVersionToDownload.keyInfos,
      { name: algo },
      false,
      ["decrypt"],
    );
    const iv = Uint8Array.from(atVersionToDownload.iv);
    const decrypted = await window.crypto.subtle.decrypt(
      { name: algo, iv: iv },
      key,
      rawBuffer,
    );

    return Promise.resolve(new Blob([decrypted]));
  } catch (e) {
    console.error("encounter error while keys and decrypt operations");
    console.error(e);

    throw e;
  }
}

/**
 * Fetch the stored object as a pdf.
 *
 * If the document is already in a pdf on the server side, the document is retrieved "as is" from the usual
 * storage.
 */
async function download_doc_as_pdf(storedObject: StoredObject): Promise<Blob> {
  if (null === storedObject.currentVersion) {
    throw new Error("the stored object does not count any version");
  }

  if (storedObject.currentVersion?.type === "application/pdf") {
    return download_and_decrypt_doc(storedObject, storedObject.currentVersion);
  }

  const convertLink = build_convert_link(storedObject.uuid);
  const response = await fetch(convertLink);

  if (!response.ok) {
    throw new Error("Could not convert the document: " + response.status);
  }

  return response.blob();
}

async function is_object_ready(
  storedObject: StoredObject,
): Promise<StoredObjectStatusChange> {
  const new_status_response = await window.fetch(
    `/api/1.0/doc-store/stored-object/${storedObject.uuid}/is-ready`,
  );

  if (!new_status_response.ok) {
    throw new Error("could not fetch the new status");
  }

  return await new_status_response.json();
}

export {
  build_convert_link,
  build_wopi_editor_link,
  download_info_link,
  download_and_decrypt_doc,
  download_doc,
  download_doc_as_pdf,
  is_extension_editable,
  is_extension_viewable,
  is_object_ready,
};
