import React, { useContext, useState } from "react";

import api from "../../services/manifestsAPI";
import { uploadFile, getCancelToken } from "../../services/uploadFile";
import { useStatusContext } from "./StatusContext";
import {
  validateManifest,
  validateManifestECUs,
} from "../../utils/manifestValidation";

const ManifestCreateContext = React.createContext();

const checkExistingManifest = async (data, token) => {
  const check = {
    name: data.name,
    version: data.version,
  };
  const { data: result } = await api.getManifests(check, token);
  if (result.length > 0)
    throw new Error(`Update ${data.name} ${data.version} already exists`);
};

const ECUTemplate = {
  name: "AGS",
  version: "",
  hw_version: "",
  configuration_mask: "",
  configuration: "",
  files: [],
  manifest_id: 0,
};

const FileTemplate = {
  offset: "0",
  checksum: "",
  type: "none",
};

export const ManifestCreateProvider = ({ children }) => {
  const { setMessage } = useStatusContext();
  const [busy, setBusy] = useState(false);
  const [uploadProgress, setUploadProgress] = useState(0);
  const [uploadStatus, setUploadStatus] = useState(null);
  const [cancelUploadToken, setCancelUploadToken] = useState(null);
  const [uploadFileIndex, setUploadFileIndex] = useState(0);
  const [uploadedFiles, setUploadedFiles] = useState([]);
  const [ecus, setECUs] = useState([]);
  const [ecuIndex, setECUIndex] = useState(0);

  const addECU = () => {
    try {
      const result = ecus.concat(
        Object.assign({ data_id: ecuIndex }, ECUTemplate)
      );
      setECUIndex(ecuIndex + 1);
      setECUs(result);
    } catch (err) {
      setMessage(err.message);
    }
  };

  const deleteECU = (data_id) => {
    try {
      const result = ecus.filter((item) => item.data_id !== data_id);
      setECUs(result);
    } catch (err) {
      setMessage(err.message);
    }
  };

  const getECU = (ecu_data_id) =>
    ecus.find((item) => item.data_id === ecu_data_id);

  const getECUFile = (ecu, filename) =>
    ecu.files.find((file) => file.filename === filename);

  const validateECUFiles = (ecu, files) => {
    files.forEach((file) => {
      if (file.size === 0) throw new Error(`${file.name} is 0 size`);
      const result = getECUFile(ecu, file.name);
      if (result) throw new Error(`${file.name} already exists`);
    });
  };

  const getAllECUFiles = () => {
    let result = [];

    for (let i = 0, len = ecus.length; i < len; i++) {
      const ecu = ecus[i];
      result = result.concat(ecu.files);
    }

    return result;
  };

  const addECUFile = (ecu_data_id, items) => {
    try {
      const ecu = getECU(ecu_data_id);
      if (ecu === undefined) return;

      const files = Array.from(items);
      validateECUFiles(ecu, files);

      const total = ecu.files.length;
      const result = files.map((file, index) =>
        Object.assign(
          { order: total + index, file: file, filename: file.name },
          FileTemplate
        )
      );
      ecu.files = ecu.files.concat(result);
      updateECUs();
    } catch (err) {
      setMessage(err.message);
    }
  };

  const sortECUFiles = (ecu) => {
    ecu.files.sort((x, y) => {
      if (x.order === y.order) return 0;
      return x.order < y.order ? -1 : 1;
    });
  };

  const deleteECUFile = (ecu_data_id, filename) => {
    try {
      const ecu = getECU(ecu_data_id);
      if (ecu === undefined) return;
      ecu.files = ecu.files.filter((file) => file.filename !== filename);
      sortECUFiles(ecu);
      updateECUs();
    } catch (err) {
      setMessage(err.message);
    }
  };

  const updateECUs = () => {
    setECUs(ecus.concat([]));
  };

  const createManifest = async (data, token) => {
    let result;
    let currentFileIndex = 0;
    const incremFileIndex = () => {
      setUploadFileIndex(currentFileIndex);
      currentFileIndex++;
    };

    try {
      setBusy(true);
      validateManifest(data, token);
      validateManifestECUs(ecus);
      await checkExistingManifest(data, token);

      setUploadedFiles(getAllECUFiles());
      if (result !== null) result = await api.createManifest(data, token);
      if (result.error)
        throw new Error(`Create manifest error. ${result.message}`);

      for (let i = 0, len = ecus.length; i < len; i++) {
        const ecu = ecus[i];
        ecu.manifest_id = result.id;
        await createManifestECU(ecu, token, incremFileIndex);
      }
    } finally {
      setBusy(false);
      setUploadFileIndex(0);
      setUploadedFiles([]);
    }

    return result;
  };

  const createManifestECU = async (ecu, token, incremFileIndexFn) => {
    const { files, ...data } = ecu;

    const result = await api.createManifestECU(data, token);
    if (result.error)
      throw new Error(`Create manifest error. ${result.message}`);

    for (let i = 0, len = ecu.files.length; i < len; i++) {
      const file = ecu.files[i];
      file.manifest_ecu_id = result.id;
      incremFileIndexFn();
      const resp = await uploadECUFile(file, token);
      if (resp.error)
        throw new Error(`Upload manifest file error. ${resp.error}`);
    }
  };

  const cancelUpload = () => {
    if (cancelUploadToken) cancelUploadToken.cancel();
    setBusy(false);
    setUploadStatus("Upload cancelled");
    setCancelUploadToken(null);
    setUploadProgress(0);
  };

  const uploadECUFile = async (file, accessToken) => {
    const cancel = getCancelToken();

    setUploadProgress(0);
    setUploadStatus(`Uploading ${file.filename}`);
    setCancelUploadToken(cancel);

    const result = await uploadFile(
      file,
      accessToken,
      setUploadProgress,
      cancel.token
    );
    if (result.message) {
      throw new Error(`${result.error}. ${result.message}`);
    }

    setUploadStatus(`Uploaded ${file.filename}`);
    setCancelUploadToken(null);
    setUploadProgress(100);

    return result;
  };

  return (
    <ManifestCreateContext.Provider
      value={{
        busy,
        ecus,
        uploadedFiles,
        uploadFileIndex,
        uploadProgress,
        uploadStatus,
        addECU,
        addECUFile,
        cancelUpload,
        createManifest,
        createManifestECU,
        deleteECU,
        deleteECUFile,
        updateECUs,
      }}
    >
      {children}
    </ManifestCreateContext.Provider>
  );
};

export const useManifestCreateContext = () => useContext(ManifestCreateContext);
