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

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

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

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

const cache = new InMemoryCache();

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

const participantModel = {
  _id: true,
  title: true,
  firstName: true,
  lastName: true,
  email: true,
  phone: true,
  language: true,
  function: true,
  company: true,
  productId: true,
  participated: true,
  emailSent: true,
};

const { Zeus, $ } = zeus;

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.listMethod;

  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 zeusQuery = Zeus.query({
    [options.detailMethod]: [apiInput || {}, outputModel],
  });

  const query = gql`
    ${zeusQuery}
  `;

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

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

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

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

  const mutate = gql`
    ${zeusMutation}
  `;

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

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

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

  const zeusMutation = Zeus.mutation({
    [options.editMethod]: [
      {
        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: options.editMethod,
    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 zeusMutation = Zeus.mutation({
    [options.deleteMethod]: [
      {
        id: $`id`,
      },
      { _id: true },
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: options.deleteMethod,
    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.getAllEvent);

  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,
  });
}

// PARTICIPANT PART
function* loadParticipant(action) {
  const { id, ...inputs } = action.payload;

  const zeusQuery = Zeus.query({
    eventsGetOne: [
      {
        id,
      },
      {
        _id: true,
        getParticipants: [
          {
            ...inputs,
          },
          {
            ...participantModel,
          },
        ],
      },
    ],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: 'eventsGetOne',
    actionRes: actions.participant.load.result.action,
    callback: action.payload.callback,
    client,
  });
}

function* addParticipant(action) {
  const zeusMutation = Zeus.mutation({
    eventsAddOneParticipant: [
      {
        input: $`input`,
        id: $`id`,
      },
      participantModel,
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'eventsAddOneParticipant',
    data: action.payload,
    actionRes: actions.participant.add.result.action,
    callback: action.payload.callback,
    client,
  });
}

function* removeParticipant(action) {
  const zeusMutation = Zeus.mutation({
    eventsRemoveOneParticipant: [
      {
        participantId: $`id`,
        id: $`eventId`,
      },
      participantModel,
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'eventsRemoveOneParticipant',
    data: action.payload,
    actionRes: actions.participant.remove.result.action,
    client,
  });
}

function* exportParticipant(action) {
  const { callback, ...data } = action.payload;

  const zeusQuery = Zeus.query({
    exportParticipants: [
      data,
      {
        url: true,
      },
    ],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: 'exportParticipants',
    actionRes: actions.participant.export.result.action,
    callback,
    client,
  });
}

function* exportParticipantPDF(action) {
  const { callback, ...data } = action.payload;

  const zeusQuery = Zeus.query({
    exportParticipantsPdf: [
      data,
      {
        url: true,
      },
    ],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: 'exportParticipantsPdf',
    actionRes: actions.participant.exportPDF.result.action,
    callback,
    client,
  });
}

function* exportAttestationPDF(action) {
  const { callback, ...data } = action.payload;

  const zeusQuery = Zeus.query({
    exportAttestationPdf: [
      data,
      {
        url: true,
      },
    ],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: 'exportAttestationPdf',
    actionRes: actions.participant.exportAttestationPDF.result.action,
    callback,
    client,
  });
}

function* markParticipantParticipate(action) {
  const zeusMutation = Zeus.mutation({
    eventsSetParticipantParticipated: [{ input: $`data` }, participantModel],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'eventsSetParticipantParticipated',
    data: action.payload,
    callback: action.payload.callback,
    actionRes: actions.participant.markAsParticipate.result.action,
    client,
  });
}

function* sendParticipantAttestation(action) {
  const zeusMutation = Zeus.mutation({
    eventSendParticipantAttestation: [{ input: $`data` }, participantModel],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'eventSendParticipantAttestation',
    data: action.payload,
    callback: action.payload.callback,
    actionRes: actions.participant.sendAttestation.result.action,
    client,
  });
}

function* cancelParticipantParticipation(action) {
  const zeusMutation = Zeus.mutation({
    eventsSetParticipantUnparticipated: [{ input: $`data` }, participantModel],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'eventsSetParticipantUnparticipated',
    data: action.payload,
    callback: action.payload.callback,
    actionRes: actions.participant.cancelParticipation.result.action,
    client,
  });
}

// EVENT
function treatPublishingError(err) {
  if (err && err[0] && err[0].message === 'missingInput') {
    const inputs = [];
    if (err[0].data) {
      for (let index = 0; index < Object.keys(err[0].data).length; index++) {
        const element = Object.keys(err[0].data)[index];
        if (!isNaN(parseInt(element, 10))) {
          inputs.push(err[0].data[element]);
        }
      }
    }

    return `Some input is missing to continue, please complete the following input : ${inputs.join()}`;
  }

  return err;
}

function* eventPublish(action) {
  const zeusMutation = Zeus.mutation({
    eventsPublish: [
      {
        id: $`id`,
      },
      {
        _id: true,
        publicationDate: true,
        published: true,
      },
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'eventsPublish',
    data: action.payload,
    actionRes: actions.event.publish.result.action,
    noLoading: true,
    treatError: e => treatPublishingError(e),
    client,
  });
}

function* eventUnpublish(action) {
  const zeusMutation = Zeus.mutation({
    eventsUnpublish: [
      {
        id: $`id`,
      },
      {
        _id: true,
        publicationDate: true,
        published: true,
      },
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'eventsUnpublish',
    data: action.payload,
    actionRes: actions.event.unpublish.result.action,
    noLoading: true,
    treatError: e => treatPublishingError(e),
    client,
  });
}

function* eventWatchers() {
  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),
    // Participant
    yield takeEvery(actions.participant.load.request.constant, loadParticipant),
    yield takeEvery(actions.participant.add.request.constant, addParticipant),
    yield takeEvery(actions.participant.remove.request.constant, removeParticipant),
    yield takeEvery(actions.participant.export.request.constant, exportParticipant),
    yield takeEvery(actions.participant.exportPDF.request.constant, exportParticipantPDF),
    yield takeEvery(
      actions.participant.exportAttestationPDF.request.constant,
      exportAttestationPDF,
    ),
    yield takeEvery(
      actions.participant.markAsParticipate.request.constant,
      markParticipantParticipate,
    ),
    yield takeEvery(
      actions.participant.cancelParticipation.request.constant,
      cancelParticipantParticipation,
    ),
    yield takeEvery(
      actions.participant.sendAttestation.request.constant,
      sendParticipantAttestation,
    ),
    // Event
    yield takeEvery(actions.event.publish.request.constant, eventPublish),
    yield takeEvery(actions.event.unpublish.request.constant, eventUnpublish),
  ]);
}

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