import { put, takeLatest, all, call, takeEvery } from 'redux-saga/effects';

// Apis from parent
import { api } from 'store/apis';
// Action type
import { StoreAction } from 'store/utils/actions';
import { apolloCall, customFetch, genericApolloSaga, genericMutation, genericQuery } from 'store/utils/apollo';

import { config, zeus as zeusParent } from 'config/general';

import gql from 'graphql-tag';
import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import fetch from 'cross-fetch';
import app from 'store/app';
import apiCall from 'store/utils/sagas';
import { dynamicApi } from 'store/utils/api';
import * as tus from 'tus-js-client';

import actions from './actions';
import selectors from './selectors';
import * as zeus from './graphql-zeus';
import { myFileModel, fileUrlModel } from './models';

const { Zeus, $ } = zeus;
const { Zeus: ZeusParent, $: $parent } = zeusParent;

const cache = new InMemoryCache();

const client = new ApolloClient({
  link: createHttpLink({
    uri: config.api.uploadUrlApollo || config.api.baseUrlApollo || '',
    fetch: customFetch,
  }),
  cache,
});

function* uploadFile(action) {
  const payload = (action && action.payload) || {};
  const mutate = gql`
    mutation upload($data: NewFileInput!) {
      uploadFile(input: $data) {
        _id
        title
        fileType
        large
        medium
        small
      }
    }
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'uploadFile',
    client,
    variables: {
      data: payload.data,
    },
    callback: payload.callback,
    callbackError: payload.callbackError,
    progress: payload.progress,
    noLoading: payload.noLoading,
    loaderInfo: { upload: true },
    noAppHeaders: true,
  });
}

function* loadListFile() {
  const query = gql`
    {
      myFiles(pagination: { limit: 100, skip: 0 }) {
        _id
        title
        fileType
        large
        medium
        small
      }
    }
  `;

  yield call(apolloCall, {
    query,
    objectReturn: 'myFiles',
    actionRes: actions.list.result.action,
    noLoading: true,
    client,
    noAppHeaders: true,
  });
}

/**
 * Flow :
 * 1. Get upload URL
 * 2. Upload to S3
 * 3. Get file
 */
function* uploadS3(action: StoreAction) {
  const payload = action?.payload || {};
  const data = payload.data;

  const inputs = payload.inputs;

  const overwrite = action?.payload?.initUpload || action?.payload?.getOne;

  const initUpload = action?.payload?.initUpload || 'filesInitUpload';
  const getOne = action?.payload?.getOne || 'filesGetOne';

  const Z = overwrite ? ZeusParent : Zeus;
  const $function = overwrite ? $ : $parent;

  const mutationObj = {};
  // 1. Get file upload URL

  // case multiple files at once
  if (Array.isArray(data)) {
    mutationObj.__alias = {};
    for (let index = 0; index < data.length; index++) {
      mutationObj.__alias[`${initUpload}${index}`] = {
        [initUpload]: [
          {
            ...inputs,
            input: $function`input` + index,
          },
          fileUrlModel,
        ],
      };
    }
  }
  // case one file
  else {
    mutationObj[initUpload] = [
      {
        ...inputs,
        input: $function`input`,
      },
      fileUrlModel,
    ];
  }

  // With zeus v4
  // const zeusMutation = Z('mutation', zeusObj);
  const zeusMutation = Z.mutation(mutationObj);
  const mutation = gql`
    ${zeusMutation}
  `;

  // mutation data
  const mutationData = {};

  const fileData = item => {
    const input = {
      fileName: item.fileName,
      privateFile: item.privateFile,
      tagsIds: item.tagsId,
    };
    if (!overwrite) {
      input.ressourceModel = item.ressourceModel;
      input.ressourceId = item.ressourceId;
    }
    return input;
  };

  if (Array.isArray(data)) {
    for (let index = 0; index < data.length; index++) {
      const item = data[index];
      mutationData[`input${index}`] = fileData(item);
    }
  } else {
    mutationData.input = fileData(data);
  }

  // Graph call
  const responseUrl = yield call(apolloCall, {
    headers: payload.headers,
    noLoading: payload.noLoading,
    mutation,
    client: !overwrite && client,
    data: mutationData,
    callbackError: payload.callbackError,
  });

  let continueProcess = false;

  // 2. Upload the images on the url(s) received
  if (Array.isArray(data)) {
    const apiCalls = [];

    for (let index = 0; index < data.length; index++) {
      const item = data[index];
      // syntax for response aliases
      const url = responseUrl?.[`${initUpload}__alias__${initUpload}${index}`]?.uploadUrl;
      if (!url) continue;

      continueProcess = true;

      const uploadApi = dynamicApi(url, 'PUT');
      apiCalls.push(
        call(apiCall, {
          // ignore api option
          api: uploadApi,
          data: {
            file: item.file,
            name: item.fileName,
          },
          noLoading: payload.noLoading,
          fileUploadS3: true,
          private: !payload.public,
          raw: true,
          noToken: true,
          progress: event => payload.progress(event, index),
          callbackError: err => payload.callbackError(err, index),
        }),
      );
    }

    if (apiCalls.length) yield all(apiCalls);
  } else {
    const url = responseUrl?.[initUpload]?.uploadUrl;

    if (!url) return;

    const uploadApi = dynamicApi(url, 'PUT');

    yield call(apiCall, {
      // ignore api option
      api: uploadApi,
      data: {
        file: data.file,
        name: data.fileName,
      },
      noLoading: payload.noLoading,
      fileUploadS3: true,
      private: !payload.public,
      raw: true,
      noToken: true,
      progress: event => payload.progress(event),
      callbackError: err => payload.callbackError(err),
    });

    continueProcess = true;
  }

  // avoid going further if error
  if (!continueProcess) return;

  // 3. Get the uploaded file(s)
  const queryObj = {};

  if (Array.isArray(data)) {
    queryObj.__alias = {};
    for (let index = 0; index < data.length; index++) {
      const item = data[index];
      const id = responseUrl?.[`${initUpload}__alias__${initUpload}${index}`]?.file?._id;

      queryObj.__alias[`${getOne}${index}`] = {
        [getOne]: [
          {
            id,
          },
          myFileModel(item.size),
        ],
      };
    }
  } else {
    const id = responseUrl?.[initUpload]?.file?._id;

    queryObj[getOne] = [
      {
        id,
      },
      myFileModel(data.size),
    ];
  }

  const zeusQuery = Zeus.query(queryObj);

  const query = gql`
    ${zeusQuery}
  `;

  // 3. Get File url
  const responseFile = yield call(apolloCall, {
    noLoading: payload.noLoading,
    client: !overwrite && client,
    query,
    callbackError: payload.callbackError,
    headers: payload.headers,
  });

  let result;

  if (Array.isArray(data)) {
    result = [];

    for (let index = 0; index < data.length; index++) {
      const file = responseFile?.[`${getOne}__alias__${getOne}${index}`];
      // important to not have wrong file update
      if (!file) result[index] = null;

      result[index] = file;
    }
  } else {
    result = responseFile?.[getOne];
  }

  if (typeof payload.callback === 'function') {
    payload.callback(result);
  }

  yield put(actions.uploadS3.result.action(result, 'fulfilled'));
}

function* filesGetMine(action: StoreAction) {
  const payload = action.payload || {};

  const getMine = payload?.getMine || 'filesGetMine';
  const Z = payload?.getMine ? ZeusParent : Zeus;

  const search = payload.data || {};

  if (payload.limit) {
    search.pagination = {
      limit: payload.limit,
      skip: ((payload.page || 1) - 1) * payload.limit,
    };
  }

  const queryObj = {
    [getMine]: [search, myFileModel(payload.size)],
  };

  if (search.pagination) {
    queryObj[`${getMine}Count`] = [search];
  }

  // With zeus v4
  // const zeusQuery = Z('query', zeusObj);
  const zeusQuery = Z.query(queryObj);

  const query = gql`
    ${zeusQuery}
  `;

  payload.query = query;
  payload.client = !payload?.getMine && client;

  yield call(genericApolloSaga('', actions.filesGetMine.result.action), action);
}

function* filesGetOne(action: StoreAction) {
  const payload = action?.payload || {};

  const getOne = payload?.getOne || 'filesGetOne';
  const Z = payload?.getOne ? ZeusParent : Zeus;

  return yield call(
    genericQuery({
      objectReturn: getOne,
      Z,
      actionRes: actions.filesGetOne,
      search: {
        id: payload.id,
      },
      model: myFileModel(payload.size),
      client: !payload?.getOne && client,
    }),
    action,
  );
}

function* filesDeleteOne(action: StoreAction) {
  const payload = action?.payload || {};

  const deleteOne = payload?.deleteOne || 'filesDeleteOne';
  const Z = payload?.deleteOne ? ZeusParent : Zeus;

  return yield call(
    genericMutation({
      objectReturn: deleteOne,
      client: !payload?.deleteOne && client,
      model: myFileModel(payload.size),
      inputs: {
        id: payload.id,
      },
      Z,
      action: actions.filesDeleteOne,
    }),
    action,
  );
}

function* uploadVimeo(action: StoreAction): object {
  const payload = action.payload;

  const vimeoPost = yield call(fetch, 'https://api.vimeo.com/me/videos', {
    method: 'POST',
    headers: {
      Accept: 'application/vnd.vimeo.*+json;version=3.4',
      'Content-Type': 'application/json',
      Authorization: `bearer ${config.vimeoToken}`,
    },
    body: JSON.stringify({
      upload: {
        approach: 'tus',
        size: payload?.data?.file?.size,
      },
    }),
  });
  const res = yield vimeoPost.json();

  const upload = new tus.Upload(payload?.data?.file, {
    uploadUrl: res?.upload?.upload_link,
    onError: payload.callbackError,
    onSuccess: () => payload.callback && payload.callback(res),
    onProgress: payload.progress,
  });

  upload.start();
}

function* fileWatchers() {
  yield all([
    takeEvery(actions.list.request.constant, loadListFile),
    takeEvery(actions.upload.request.constant, uploadFile),
    takeEvery(actions.uploadS3.request.constant, uploadS3),
    takeEvery(actions.filesGetMine.request.constant, filesGetMine),
    takeEvery(actions.filesGetOne.request.constant, filesGetOne),
    takeEvery(actions.filesDeleteOne.request.constant, filesDeleteOne),
    takeEvery(actions.uploadVimeo.request.constant, uploadVimeo),
  ]);
}

export default function* saga() {
  yield call(fileWatchers);
}
