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

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

import { config, zeus } from 'config/general';
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
import fetch from 'cross-fetch';
import countryModel from 'config/data/countryModel';

import gql from 'graphql-tag';
import unflatten from 'unflatten';
import { gqlConvert, filterUpdateData } from 'store/utils/adminHelpers';
import _ from 'lodash';
import { deepOmit } from 'store/utils/helper';
import adminConfig from 'config/admin';
import actions from './actions';
import selectors from './selectors';

const cache = new InMemoryCache();

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

const { Zeus, $ } = zeus;

const orderModel = adminConfig?.adminShop?.ressource?.orders?.model;

function* loadData(action) {
  const { options } = action.payload;
  const fields = options.headers.reduce(
    (prev, current) => {
      if (current.key) {
        prev[current.key] = true;
      }
      if (current.id) {
        prev[current.id] = true;
      }
      return prev;
    },
    {
      _id: true,
    },
  );

  const outputModel = unflatten(fields);

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

  const inputModel = {
    pagination: { limit: 100, skip: 0, sort: options.sort || null },
    ...options.inputModel,
  };

  const zeusQuery = Zeus.query({
    [methodName]: [inputModel, outputModel],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    actionRes: actions.load.result.action,
    client,
  });
}

function* loadOneData(action) {
  const { options, apiInput } = action.payload;

  const outputModel = gqlConvert(options.model, options);

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

  const zeusQuery = Zeus.query({
    [methodName]: [apiInput || {}, outputModel],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: methodName,
    actionRes: actions.loadOne.result.action,
    callback: action.payload.callback,
    client,
  });
}

function* addData(action) {
  const { options } = action.payload;

  const outputModel = gqlConvert(options.model, options);

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

  const zeusMutation = Zeus.mutation({
    [resourceCreate]: [
      {
        input: $`data`,
      },
      outputModel,
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  const omitFields = ['finalPrice', 'published', 'publicationDate', 'createdAt', 'updatedAt'];

  yield call(apolloCall, {
    mutate,
    objectReturn: resourceCreate,
    data: deepOmit(action.payload, omitFields),
    actionRes: actions.add.result.action,
    callback: action.payload.callback,
    client,
  });
}

function* editData(action) {
  const { options } = action.payload;

  const outputModel = gqlConvert(options.model, options);

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

  const zeusMutation = Zeus.mutation({
    [resourceUpdate]: [
      {
        id: $`id`,
        input: $`data`,
      },
      outputModel,
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  const filteredData = filterUpdateData(options.model, action.payload.data);

  const data = filteredData;

  yield call(apolloCall, {
    mutate,
    objectReturn: resourceUpdate,
    data: { ...action.payload, data: deepOmit(data, ['_id', '__typename']) },
    actionRes: actions.edit.result.action,
    callback: action.payload.callback,
    client,
  });
}

function* deleteData(action) {
  const { options, resource } = action.payload;
  const resourceDelete =
    options.resourceDelete || options.deleteMethod || `${options.name}DeleteOne`;

  const zeusMutation = Zeus.mutation({
    [resourceDelete]: [
      {
        id: $`id`,
      },
      { _id: true },
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: resourceDelete,
    data: action.payload,
    actionRes: actions.delete.result.action,
    callback: action.payload.callback,
    client,
  });
}

function* searchData(action) {
  const { search, order, limit, options, inputs } = action.payload;

  const selectData = yield select(selectors.getAllShop);

  const currentData =
    selectData && options && options.resourceList && selectData[options.resourceList];

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

  const fields = options.headers.reduce(
    (prev, current) => {
      if (current.key) {
        prev[current.key] = true;
      }
      if (current.id) {
        prev[current.id] = true;
      }
      return prev;
    },
    {
      _id: true,
    },
  );

  const outputModel = unflatten(fields);

  const zeusQuery = Zeus.query({
    [options.resourceList]: [
      {
        pagination: paginationData,
        ...inputs,
      },
      outputModel,
    ],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: options.resourceList,
    actionRes: actions.search.result.action,
    callback: action.payload.callback,
    client,
  });
}

// VAT
function* loadVATValues(action) {
  const zeusQuery = Zeus.query({
    vatClassGetOne: [
      {
        id: action.payload.id,
      },
      {
        _id: true,
        values: countryModel,
      },
    ],
  });
  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: 'vatClassGetOne.values',
    actionRes: actions.vat.loadValues.result.action,
    noLoading: true,
    client,
  });
}

// ORDERS
function returnOrderModel() {
  const outputModel = gqlConvert(orderModel);
  return outputModel;
}

function* orderMarkPaid(action) {
  const zeusMutation = Zeus.mutation({
    ordersMarkOneAsPaid: [{ id: $`id` }, returnOrderModel()],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'ordersMarkOneAsPaid',
    data: action.payload,
    actionRes: actions.order.markPaid.result.action,
    client,
  });
}

function* orderMarkUnPaid(action) {
  const zeusMutation = Zeus.mutation({
    ordersMarkOneAsUnPaid: [{ id: $`id` }, returnOrderModel()],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'ordersMarkOneAsUnPaid',
    data: action.payload,
    actionRes: actions.order.markUnPaid.result.action,
    client,
  });
}

function* orderCancel(action) {
  const zeusMutation = Zeus.mutation({
    ordersCancelOne: [{ id: $`id` }, returnOrderModel()],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'ordersCancelOne',
    data: action.payload,
    actionRes: actions.order.cancel.result.action,
    client,
  });
}

function* orderReimburse(action) {
  const zeusMutation = Zeus.mutation({
    ordersReimburseOne: [{ orderId: $`id` }, returnOrderModel()],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'ordersReimburseOne',
    data: action.payload,
    actionRes: actions.order.reimburse.result.action,
    client,
  });
}

function* shopWatchers() {
  yield all([
    yield takeEvery(actions.load.request.constant, loadData),
    yield takeEvery(actions.loadOne.request.constant, loadOneData),
    yield takeEvery(actions.add.request.constant, addData),
    yield takeEvery(actions.edit.request.constant, editData),
    yield takeEvery(actions.delete.request.constant, deleteData),
    yield takeEvery(actions.search.request.constant, searchData),

    // VAT
    yield takeEvery(actions.vat.loadValues.request.constant, loadVATValues),

    // Order
    yield takeEvery(actions.order.markPaid.request.constant, orderMarkPaid),
    yield takeEvery(actions.order.markUnPaid.request.constant, orderMarkUnPaid),
    yield takeEvery(actions.order.cancel.request.constant, orderCancel),
    yield takeEvery(actions.order.reimburse.request.constant, orderReimburse),
  ]);
}

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