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

import { zeus, config } from 'config/general';
// Apis from parent
import { api } from 'store/apis';
// Action type
import { StoreAction } from 'store/utils/actions';
import { apolloCall } from 'store/utils/sagas';

import genericActions from 'store/generic/actions';
import gql from 'graphql-tag';
import _ from 'lodash';
import { gqlConvert, filterUpdateData } from 'store/utils/adminHelpers';
import unflatten from 'unflatten';
import { deepOmit } from 'store/utils/helper';
import genericSelectors from 'store/generic/selectors';
import accountSelectors from 'store/account/selectors';
import account from 'store/account';
import selectors from 'store/content/selectors';
import actions from './actions';

function* genericCall(payload) {
  const selectedResource = yield select(accountSelectors.getSelectedResource);

  const options = config.accountOptions;
  if (selectedResource && !payload.noHttpHeader) {
    const accountResourceOptions = options.accountResourceOptions;

    if (accountResourceOptions && accountResourceOptions.httpHeader) {
      payload.headers = {
        ...payload.headers,
        [accountResourceOptions.httpHeader]:
          selectedResource[accountResourceOptions.idKey] ||
          selectedResource.id ||
          selectedResource._id,
      };
    }
  }
  yield call(apolloCall, {
    ...payload,
  });
}

export function* genericList(action, paginationData, actionRes, otherData) {
  const payload = action.payload || {};

  const { Zeus, $ } = zeus;

  const { options, inputs } = payload;

  const headers = options.headers;

  const resourceName =
    options.resourceList || options.resource || options.listMethod || `${options.name}GetMany`;

  const apiInput = {
    ...options.inputModel,
    ...inputs,
  };

  if (paginationData) {
    apiInput.pagination = $`pagination`;
  }

  const lg = yield select(selectors.getLanguage);

  // treatment model
  const fields = headers.reduce(
    (prev, current) => {
      if (current?.type === 'multi') {
        prev[current.key] = false;
        prev[`${current.key}.${lg}`] = true;
        return prev;
      }
      if (current.noKey) {
        return prev;
      }
      if (current.apolloKey || current.key) {
        prev[current.apolloKey || current.key] = true;
      }
      if (current.id) {
        prev[current.id] = true;
      }
      return prev;
    },
    {
      _id: true,
    },
  );

  const outputModel = unflatten(fields);

  let zeusQuery;

  try {
    zeusQuery = Zeus.query({
      [resourceName]: [apiInput, outputModel],
    });
  } catch (error) {
    console.log('error list', error);
    return;
  }

  const query = gql`
    ${zeusQuery}
  `;

  const data = { ...otherData };
  if (paginationData && Object.keys(paginationData).length !== 0) {
    data.pagination = paginationData;
  }

  yield call(genericCall, {
    objectReturn: resourceName,
    noHttpHeader: options.noHttpHeader,
    query,
    data,
    actionRes,
    callback: payload.callback,
    callbackError: payload.callbackError,
    noLoading: true,
  });
}

function* genericApolloList(action): void {
  const payload = action.payload || {};

  const { limit, order } = payload;

  const currentData = yield select(genericSelectors.getGenericData);

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

  let paginationData = {
    sort: order || config.defaultSort,
    limit: !payload.noLimit && parseInt(limit || config.defaultLimit, 10),
    skip: 0,
  };

  if (payload?.options?.noPagination) paginationData = null;

  yield call(genericList, action, paginationData, genericActions.list.result.action);
}

/**
 * List more
 */

function* genericApolloListMore(action): void {
  const payload = action.payload || {};

  const { limit, order } = payload;

  const currentData = yield select(genericSelectors.getGenericData);

  const paginationData = {
    sort: order || config.defaultSort,
    skip: parseInt((currentData && currentData.data && currentData.data.length) || 0, 10),
    limit: parseInt(limit || config.defaultLimit, 10),
  };

  yield call(genericList, action, paginationData, genericActions.listMore.result.action);
}

/**
 * genericApolloSearch
 */
function* genericApolloSearch(action): void {
  const payload = action.payload || {};

  const { search, order, limit } = payload;

  const currentData = yield select(genericSelectors.getGenericData);

  let skip;
  if (currentData && currentData.resource !== payload.options.resourceList) {
    skip = 0;
  } else {
    skip = parseInt((currentData && currentData.data && currentData.data.length) || 0, 10);
  }

  const paginationData = {
    sort: order || config.defaultSort,
    skip,
    limit: parseInt(limit || config.defaultLimit, 10),
  };

  console.log(payload);

  yield call(genericList, action, paginationData, genericActions.search.result.action, {
    ...search,
  });
}

function* genericApolloDetail(action): void {
  const payload = action.payload || {};
  const { Zeus } = zeus;

  const { options, inputs, id, callback, actionRes } = payload;

  const model = options.model || options.fields;

  if (!model) {
    console.log('no model');
    return;
  }

  const resourceName = options.resource || options.detailMethod || `${options.name}GetOne`;

  const outputModel = gqlConvert(model, options);

  const apiInput = {
    id,
    ...inputs,
  };

  let zeusQuery;
  try {
    zeusQuery = Zeus.query({
      [resourceName]: [apiInput, outputModel],
    });
  } catch (error) {
    console.log('error detail', error);
    return;
  }

  const query = gql`
    ${zeusQuery}
  `;

  yield call(genericCall, {
    objectReturn: resourceName,
    noHttpHeader: options.noHttpHeader,
    query,
    actionRes: actionRes || genericActions.detail.result.action,
    callback,
    noLoading: true,
  });
}

function* genericApolloUpdate(action): void {
  const payload = action.payload || {};
  const { Zeus, $ } = zeus;

  const { options, inputs, id, data, noLoading, subResource, noFeedback, success } = payload;

  // let { data } = payload;
  // data = deepOmit(data, '__typename');

  const model = options.model || options.fields;

  if (!model) {
    console.log('no model');
    return;
  }

  const resourceUpdate = options.resourceUpdate || options.editMethod || `${options.name}EditOne`;

  if (!resourceUpdate) {
    console.error('NO RESOURCE UPDATE PROVIDED');
    return;
  }

  const outputModel = gqlConvert(model, options);

  const apiInput = {
    id,
    input: $`data`,
    ...inputs,
  };

  let zeusMutation;
  try {
    zeusMutation = Zeus.mutation({
      [resourceUpdate]: [apiInput, outputModel],
    });
  } catch (error) {
    console.log('error update', error);
    return;
  }

  const mutation = gql`
    ${zeusMutation}
  `;

  let filteredData = data;
  if (!payload?.noFilterData) {
    filteredData = filterUpdateData(model, data);
  }

  yield call(genericCall, {
    mutate: mutation,
    objectReturn: resourceUpdate,
    noHttpHeader: options.noHttpHeader,
    noLoading,
    noFeedback,
    success,
    data: {
      data: deepOmit(filteredData, '__typename'),
    },
    callback: payload.callback,
    callbackError: payload.callbackError,
    actionRes: !subResource && genericActions.update.result.action,
  });

  // If account resource => reload it after updating of a new one
  if (options.isAccountResource) {
    yield put(account.actions.loadAccountResource.request.action());
  }
}

function* genericApolloCreate(action): void {
  const payload = action.payload || {};
  const { Zeus, $ } = zeus;

  const { options, subResource, data, inputs, noLoading, noFeedback, success } = payload;

  const model = options.model || options.fields;

  console.log(payload);

  if (!model) {
    console.log('no model');
    return;
  }

  const resourceCreate = options.resourceCreate || options.addMethod || `${options.name}AddOne`;

  if (!resourceCreate) {
    console.error('NO RESOURCE CREATE PROVIDED');
    return;
  }

  const outputModel = gqlConvert(model, options);

  const apiInput = {
    input: $`data`,
    ...inputs,
  };

  const mutationObj = {
    [resourceCreate]: [apiInput, outputModel],
  };

  let zeusMutation;
  try {
    zeusMutation = Zeus.mutation(mutationObj);
  } catch (error) {
    console.log('error create', error);
    return;
  }

  const mutation = gql`
    ${zeusMutation}
  `;

  yield call(genericCall, {
    mutate: mutation,
    objectReturn: resourceCreate,
    noHttpHeader: options.noHttpHeader,
    data: {
      data,
    },
    noLoading,
    noFeedback,
    success,
    callback: payload.callback,
    callbackError: payload.callbackError,
    actionRes: !subResource && genericActions.add.result.action,
  });

  // If account resource => reload it after creation of a new one
  if (options.isAccountResource) {
    yield put(account.actions.loadAccountResource.request.action());
  }
}

function* genericApolloDelete(action) {
  const payload = action.payload || {};
  const { Zeus } = zeus;

  const { options, id, subResource, inputs, outputs, noLoading, noFeedback, success } = payload;

  const model = options.model || options.fields;

  if (!model) {
    console.log('no model');
    return;
  }

  const resourceDelete =
    options.resourceDelete || options.deleteMethod || `${options.name}DeleteOne`;

  if (!resourceDelete) {
    console.error('NO RESOURCE DELETE PROVIDED');
    return;
  }

  const outputModel = {
    _id: true,
    ...outputs,
  };

  const apiInput = {
    id,
    ...inputs,
  };

  const mutationObj = {
    [resourceDelete]: [apiInput, outputModel],
  };

  console.log('mutationOb', mutationObj);

  let zeusMutation;
  try {
    zeusMutation = Zeus.mutation(mutationObj);
  } catch (error) {
    console.log('error delete', error);
    return;
  }

  const mutation = gql`
    ${zeusMutation}
  `;

  yield call(genericCall, {
    mutate: mutation,
    objectReturn: resourceDelete,
    noHttpHeader: options.noHttpHeader,
    noLoading,
    noFeedback,
    success,
    callback: payload.callback,
    callbackError: payload.callbackError,
    actionRes: !subResource && genericActions.delete.result.action,
  });

  // If account resource => reload it
  if (options.isAccountResource) {
    yield put(account.actions.loadAccountResource.request.action());
  }
}

function* genericApolloWatchers(): void {
  yield all([
    yield takeEvery(actions.list.request.constant, genericApolloList),
    yield takeEvery(actions.listMore.request.constant, genericApolloListMore),
    yield takeEvery(actions.search.request.constant, genericApolloSearch),

    yield takeLatest(actions.detail.request.constant, genericApolloDetail),
    yield takeLatest(actions.add.request.constant, genericApolloCreate),
    yield takeLatest(actions.update.request.constant, genericApolloUpdate),
    yield takeLatest(actions.delete.request.constant, genericApolloDelete),
  ]);
}

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