import { select, takeEvery, takeLatest, all, call } from 'redux-saga/effects';
import { api } from 'store/apis';
import { apiCall } from 'store/utils/sagas';
import { config } from 'config/general';
import { downloadFile } from 'store/utils/view';
import _ from 'lodash';
import actions from './actions';
import selectors from './selectors';

const extractApiData = (payload): Record<string, any> => {
  const output = {};
  if (!payload) return output;

  const keys = Object.keys(payload);
  for (let i = 0; i < keys.length; i += 1) {
    const key = keys[i];
    const elem = payload[key];
    // avoid sending not necessary parameters to backend
    if (['resourceType', 'api', 'resourceSubType', 'resourceId', 'filterHash'].indexOf(key) >= 0) {
      continue;
    }
    if (typeof elem !== 'function') {
      output[key] = elem;
    }
  }

  if (output.resourceType) output.resourceType = undefined;
  return output;
};

const getGenericApi = (payload, type): void => {
  const resourceType = payload.resourceType;

  const genericApi = api[resourceType] || api.dynamic(resourceType);

  if (type && genericApi[type]) return genericApi[type];

  return genericApi;
};

/**
 * - Get the resource api from the config
 * - Get the sub resource api api call
 * - If the sub resource does not exist :  use dynamicLinked with the resourceTypes
 */
const getLinkedApi = (payload, type): void => {
  const parentType = payload.parentType;
  const resourceType = payload.resourceType;

  const resourceApi = api[parentType] || api.dynamic(parentType);

  let genericApi;

  if (resourceApi && resourceType) {
    genericApi = resourceApi[resourceType];
  }
  if (type && genericApi) {
    genericApi = (genericApi && genericApi[type]) || genericApi;
  }

  if (!genericApi) genericApi = api.dynamicLinked(payload)[type];

  return genericApi;
};

/**
 * Listing data and more
 */
function* genericList(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'list');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'list');
  }
  // Deprecated
  if (payload.resourceSubType) {
    genericApi = getLinkedApi(payload, 'list');
  }

  if (!genericApi) return;

  const currentData = yield select(selectors.getGenericData);

  if (currentData && currentData.data && currentData.data.length > 0) {
    // cached data => do nothing
    return;
  }

  const apiData = extractApiData(payload);

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    api: genericApi,
    data: {
      ...apiData,
      limit: !payload.noLimit && (payload.limit || config.defaultLimit),
    },
    actionRes: !payload.callback && actions.list.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
    actionSuccess: payload.actionSuccess,
    noLoading: true,
  });
}

function* genericListMore(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'list');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'list');
  }

  if (!genericApi) return;

  const currentData = yield select(selectors.getGenericData);

  const apiData = extractApiData(payload);

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    api: genericApi,
    data: {
      ...apiData,
      more: undefined,
      offset: (currentData && currentData.data && currentData.data.length) || 0,
      limit: payload.limit || config.defaultLimit,
    },
    actionRes: actions.listMore.result.action,
    noLoading: true,
    callback: payload.callback,
    callbackError: payload.callbackError,
  });
}
/**
 * Generic search
 */
function* genericSearch(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'search');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'search');
  }

  if (!genericApi) return;

  const apiData = extractApiData(payload);

  if (apiData.search === '') {
    apiData.search = undefined;
  }

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    api: genericApi,
    data: {
      ...apiData,
      limit: !payload.noLimit && (payload.limit || config.defaultLimit),
    },
    actionRes: !payload.callback && actions.list.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
    actionSuccess: payload.actionSuccess || false,
    noLoading: true,
  });
}

/**
 * Get one
 */
function* genericDetail(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'detail');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'detail');
  }

  if (!genericApi) return;

  const data = extractApiData(payload.data);

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    data,
    id: payload.resourceId,
    api: genericApi,
    noLoading: payload.noLoading,
    noFeedback: payload.noFeedback,
    noReset: payload.noReset,
    noSuccess: payload.noSuccess,
    reset: payload.reset,
    actionRes: actions.detail.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
  });
}

/**
 * Update
 */
function* genericUpdate(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'update');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'update');
  }

  const data = extractApiData(payload.data);

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    id: payload.resourceId,
    api: genericApi,
    data,
    noFeedback: payload.noFeedback,
    noReset: payload.noReset,
    noSuccess: payload.noSuccess,
    reset: payload.reset,
    noLoading: !payload.doLoading,
    actionRes: actions.update.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
  });
}

/**
 * Fast Update
 */

function* genericFastUpdate(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'update');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'update');
  }

  const data = extractApiData(payload.data);

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    id: payload.resourceId,
    api: genericApi,
    data,
    noFeedback: payload.noFeedback,
    noReset: payload.noReset,
    noSuccess: payload.noSuccess,
    reset: payload.reset,
    noLoading: true,
    actionRes: actions.fastUpdate.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
    transform: payload.transform,
  });
}

/**
 * Add
 */
function* genericAdd(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'add');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'add');
  }

  if (!genericApi) return;

  const data = extractApiData(payload.data);

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    api: genericApi,
    data,
    noFeedback: payload.noFeedback,
    noReset: payload.noReset,
    noSuccess: payload.noSuccess,
    reset: payload.reset,
    noLoading: true,
    actionRes: actions.add.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
  });
}

/**
 * Delete
 */
function* genericDelete(action): void {
  const payload = action.payload;

  let genericApi = getGenericApi(payload, 'delete');
  if (payload.parentType && payload.parentId) {
    genericApi = getLinkedApi(payload, 'delete');
  }

  if (!genericApi) return;

  yield call(apiCall, {
    // Linked info
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    id: payload.resourceId,
    api: genericApi,
    noLoading: payload.noLoading,
    noFeedback: payload.noFeedback,
    noReset: payload.noReset,
    noSuccess: payload.noSuccess,
    reset: payload.reset,
    actionRes: actions.delete.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
  });
}

/**
 * Upload
 */
function* genericUpload(action): void {
  const payload = action.payload;

  let genericUploadApi;

  if (payload.api) genericUploadApi = _.get(api, payload.api);
  else if (payload.url) genericUploadApi = api.dynamicApi(payload.url, payload.method);

  const apiData = extractApiData(payload.data);

  yield call(apiCall, {
    api: genericUploadApi,
    noLoading: payload.noLoading,
    fileUpload: true,
    fileName: payload.fileName,
    data: {
      ...apiData,
      file: payload.file || apiData.file,
    },
    progress: payload.progress,
    callback: payload.callback,
    callbackError: payload.callbackError,
  });
}

function* genericUploadS3(action): void {
  // Payload here is the fusion get the response of getUploadUrlS3 and the original payload given to getUploadUrlS3
  const payload = action.payload || {};

  // upload url come from the BE
  const genericUploadApi = api.dynamicApi(payload.uploadUrl, 'PUT');

  yield call(apiCall, {
    api: genericUploadApi,
    // Should investigate if could be removed
    url: payload.uploadUrl,
    data: {
      file: payload.data.file,
      type: payload.data.contentType,
      name: payload.fileName,
    },
    progress: payload.progress,
    callback: () => payload.callback(payload),
    noLoading: payload.noLoading,
    raw: true,
    noToken: true,
    fileUploadS3: true,
    headers: {
      'Content-Type': payload.data.contentType,
    },
  });
}
/**
 * 1. Get the url from the server in order to avoid having to store the keys and secrets locally
 * 2. upload to s3 with the obtained url (see genericUploadS3)
 */
function* getUploadUrlS3(action): void {
  const payload = action.payload || {};

  let genericUploadApi;

  if (payload.api) genericUploadApi = _.get(api, payload.api);
  else if (payload.url) genericUploadApi = api.dynamicApi(payload.url, payload.method);

  const apiData = extractApiData(payload.data);

  yield call(apiCall, {
    api: genericUploadApi,
    noLoading: payload.noLoading,
    data: {
      ...apiData,
    },
    actionRes: e => actions.uploadS3.request.action({ ...payload, ...e }),
  });
}

/**
 * Export
 */
function* genericExport(action): void {
  const payload = action.payload;

  let genericExportApi;

  if (payload.api) genericExportApi = _.get(api, payload.api);
  else if (payload.url) genericExportApi = api.dynamicDownload(payload.url);

  if (!genericExportApi) return;

  const response = yield call(apiCall, {
    ...payload,
    api: genericExportApi,
    // if not a get
    method: payload.method,
    type: payload.resourceType,
    actionRes: actions.export.result.action,
  });

  if (response) {
    downloadFile(response, `${payload.resourceType}-exported.csv`);
  }
}

function* genericAction(action) {
  const payload = action.payload || {};

  let genericApi;

  if (payload.api) genericApi = _.get(api, payload.api);
  else if (payload.url) genericApi = api.dynamicApi(payload.url, payload.method);

  if (!genericApi) {
    console.error('Provide a api existing in the config for this action', action);
    return;
  }
  const data = extractApiData(payload.data);

  yield call(apiCall, {
    // data to send
    ...payload,
    parentType: payload.parentType,
    parentId: payload.parentId,
    resourceType: payload.resourceType,
    // rest of config
    resourceId: payload.resourceId,
    data,
    success: payload.success,
    api: genericApi,
    actionRes: actions.genericAction.result.action,
    callback: payload.callback,
    callbackError: payload.callbackError,
    noLoading: payload.noLoading,
    noFeedback: payload.noFeedback,
  });
}

function* genericWatchers(): void {
  yield all([
    yield takeLatest(actions.list.request.constant, genericList),
    yield takeLatest(actions.listMore.request.constant, genericListMore),
    yield takeEvery(actions.search.request.constant, genericSearch),
    yield takeEvery(actions.detail.request.constant, genericDetail),
    yield takeEvery(actions.update.request.constant, genericUpdate),
    yield takeEvery(actions.fastUpdate.request.constant, genericFastUpdate),
    yield takeEvery(actions.add.request.constant, genericAdd),
    yield takeEvery(actions.delete.request.constant, genericDelete),
    // Upload + Export
    yield takeEvery(actions.upload.request.constant, genericUpload),
    yield takeEvery(actions.uploadS3Url.request.constant, getUploadUrlS3),
    yield takeEvery(actions.uploadS3.request.constant, genericUploadS3),
    yield takeEvery(actions.export.request.constant, genericExport),
    // Generic api actions
    yield takeEvery(actions.genericAction.request.constant, genericAction),
  ]);
}

export default function* saga(): void {
  yield call(genericWatchers);
}
