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, createHttpLink, InMemoryCache } from '@apollo/client';
import fetch from 'cross-fetch';

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

const cache = new InMemoryCache();

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

const { Zeus, $ } = zeus;

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

  const fields = action.payload.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 || action.payload.resource;

  const inputModel = {
    pagination: { limit: 100, skip: 0 },
    ...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: action.payload,
    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 fieldsToOmit = ['_id', '__typename'].concat(options.fieldsToAvoidUpdate || []);

  let data;
  if (options.fieldsToKeep) {
    data = _.pick(action.payload.data, options.fieldsToKeep);
  } else {
    data = deepOmit(action.payload.data, fieldsToOmit);
  }

  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, skip, options, inputs } = action.payload;

  const selectData = yield select(selectors.getAllBlog);

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

  let skipNumber;

  if (skip !== null) {
    skipNumber = skip;
  } else {
    skipNumber = parseInt((currentData && currentData.data && currentData.data.length) || 0, 10);
  }

  const paginationData = {
    sort: order || config.defaultSort,
    skip: skipNumber,
    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,
  });
}

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

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'articlePublish',
    data: action.payload,
    actionRes: actions.publish.result.action,
    noLoading: true,
    client,
  });
}

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

  const mutate = gql`
    ${zeusMutation}
  `;

  yield call(apolloCall, {
    mutate,
    objectReturn: 'articleUnpublish',
    data: action.payload,
    actionRes: actions.unpublish.result.action,
    noLoading: true,
    client,
  });
}

function* addArticlePermission(action) {
  const zeusMutation = Zeus.mutation({
    articlePermissionsAddOne: [
      {
        input: $`input`,
        id: $`id`,
      },
      {
        _id: true,
        r: true,
        w: true,
        d: true,
      },
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

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

function* removeArticlePermission(action) {
  const zeusMutation = Zeus.mutation({
    articlePermissionsRemoveOne: [
      {
        input: $`input`,
        id: $`id`,
      },
      {
        _id: true,
        r: true,
        w: true,
        d: true,
      },
    ],
  });

  const mutate = gql`
    ${zeusMutation}
  `;

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

function* loadArticlePermission(action) {
  const zeusQuery = Zeus.query({
    article: [
      {
        ...action.payload,
      },
      {
        authorId: true,
        _id: true,
        r: true,
        w: true,
        d: true,
      },
    ],
  });

  const query = gql`
    ${zeusQuery}
  `;

  yield call(apolloCall, {
    query,
    objectReturn: 'article',
    actionRes: actions.loadPermission.result.action,
    client,
  });
}

function* blogWatchers() {
  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),
    yield takeEvery(actions.publish.request.constant, publishArticle),
    yield takeEvery(actions.unpublish.request.constant, unpublishArticle),
    yield takeEvery(actions.loadPermission.request.constant, loadArticlePermission),
    yield takeEvery(actions.addPermission.request.constant, addArticlePermission),
    yield takeEvery(actions.removePermission.request.constant, removeArticlePermission),
  ]);
}

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