import { Notify } from 'quasar';
import { i18n } from '@plugins/i18n';
import get from 'lodash/get';
import set from 'lodash/set';
import omit from 'lodash/omit';
import cloneDeep from 'lodash/cloneDeep';
import merge from 'lodash/merge';
import last from 'lodash/last';
import { DictionaryApi } from '@openapi/registry-service';
import { DefaultApi } from '@openapi/search';
import { ActionTree, GetterTree, MutationTree } from 'vuex';
import { toString } from 'lodash';

const dictionaryApi = new DictionaryApi();

interface State {
  referenceBooks: Record<string, any>;
}

interface CodeBookParams {
  book: string;
  value: any;
  valueName: string;
}

interface AppliedReferenceBookParams {
  key: string;
  id: string;
  valueName: string;
}

export const state: State = {
  referenceBooks: {}, // Хранилище всех справочников, чтобы не загружать их повторно
};

export const getters: GetterTree<State, State> = {
  referenceBooks(state) {
    return state.referenceBooks;
  },
  appliedReferenceBook: (state) => ({
    key,
    id,
    valueName = 'value',
  }: AppliedReferenceBookParams) => {
    let value = key;
    const referenceBook = state.referenceBooks[id];

    if (referenceBook && referenceBook[key]) {
      value = get(referenceBook[key], `${valueName}`)
        ? get(referenceBook[key], `${valueName}`)
        : '';
    }

    return {
      key,
      value,
    };
  },
  getCodeInBookByValue: (state) => ({
    book,
    value,
    valueName = 'value',
  }: CodeBookParams): string | undefined => {
    const referenceBook = state.referenceBooks[book];

    for (const code in referenceBook) {
      if (
        referenceBook.hasOwn(code) &&
        referenceBook[code][valueName] === value
      ) {
        return code;
      }
    }

    return undefined;
  },
};

export const mutations: MutationTree<State> = {
  addReferenceBook(state, { id, referenceBook }) {
    const referenceBooks = set(state.referenceBooks, `[${id}]`, referenceBook);

    state.referenceBooks = cloneDeep(referenceBooks);
  },
  referenceBooks(state, referenceBooks) {
    state.referenceBooks = cloneDeep(referenceBooks);
  },
  removeReferenceBook(state, { id }) {
    const referenceBooks = omit(state.referenceBooks, id);

    state.referenceBooks = cloneDeep(referenceBooks);
  },
};

const promisePool: Record<string, Promise<unknown>> = {};

export const actions: ActionTree<State, State> = {
  fetchReferenceBook(
    { commit, getters, dispatch },
    {
      force, // Флаг, отвечающий за принудительную загрузку с сервера
      id, // Id справочника (см. тут: https://git.devsun.ru/projects/PGS/repos/importdicts/browse/dicts). Сейчас используется имя файла
      keyName = 'code', // Имя поля, по которому настроить выдачу ключей справочника
      useAuth = true,
      limit = id === 'doc_types' ? 5000 : 300,
    } = {}
  ) {
    const localReferenceBook: Record<string, any> = getters.referenceBooks[id];

    if (!force && localReferenceBook) {
      return localReferenceBook;
    }

    const queryKey = `${id}.${force}.${keyName}.${useAuth}`;

    if (!promisePool[queryKey]) {
      promisePool[queryKey] = new Promise((resolve, reject) => {
        try {
          dictionaryApi
            .searchDictionary(`name==${id}`)
            .then(async ({ data }) => {
              if (data.length) {
                // todo hacky getting last element
                const referenceBook = await dispatch(
                  'convertReferenceBookFromRegistry',
                  {
                    referenceBook: last(data),
                    localReferenceBook,
                    keyName,
                  }
                );
                commit('addReferenceBook', {
                  id,
                  referenceBook,
                });
                resolve(referenceBook);
              } else {
                Notify.create({
                  color: 'negative',
                  icon: 'error',
                  message: `${i18n.t('referenceBooks.notFound')} ${id}`,
                });
                console.warn('---------------------------');
                console.warn('---------------------------');
                console.warn(
                  '👻 Dictionary',
                  id,
                  ' is missing in registry, trying to fetch from elastic'
                );
                console.warn('---------------------------');
                console.warn('---------------------------');

                return searchInElastic();
              }
            });

          const searchInElastic = async () => {
            const requestBody = {
              size: limit,
              query: {
                match_all: {},
              },
            };

            let attemptsLeft: number = parseInt(
              process.env.VUE_APP_SEARCH_REQUESTS_COUNT || '5'
            );
            const delay: number = parseInt(
              process.env.VUE_APP_SEARCH_REQUEST_INTERVAL || '5000'
            );

            async function getReferenceBook() {
              const searchApi = new DefaultApi();
              attemptsLeft -= 1;

              await searchApi
                .schemaPaging(id, requestBody)
                .then(({ error, data }: any) => {
                  if (!error) {
                    let referenceBook = (data?.results || []).reduce(
                      (accumulator: number, result: Record<string, any>) => {
                        const key = result.data[keyName];
                        const value = omit(result.data, keyName);

                        merge(accumulator, {
                          [key]: value,
                        });

                        return accumulator;
                      },
                      {}
                    );

                    referenceBook = merge(
                      {},
                      localReferenceBook || {},
                      referenceBook
                    );

                    commit('addReferenceBook', {
                      id,
                      referenceBook,
                    });

                    return resolve(referenceBook);
                  } else {
                    if (attemptsLeft > 0) {
                      Notify.create({
                        color: 'negative',
                        icon: 'error',
                        message: `${i18n.t(
                          'referenceBooks.attemptsLeft'
                        )} ${attemptsLeft}`,
                      });
                      setTimeout(getReferenceBook, delay);
                    } else {
                      Notify.create({
                        color: 'negative',
                        icon: 'error',
                        message: toString(
                          i18n.t('referenceBooks.noAttemptsLeft')
                        ),
                      });
                      return reject(error);
                    }
                  }
                });
            }

            await getReferenceBook();
          };
        } catch (e) {
          reject(e);
        } finally {
          delete promisePool[queryKey];
        }
      });
    }

    return promisePool[queryKey];
  },
  initReferenceBooks({ commit = {}, dispatch }, referenceBooks): Promise<void> {
    return new Promise((resolve, reject) => {
      try {
        let i = 0;

        for (const referenceBook of referenceBooks) {
          dispatch('fetchReferenceBook', {
            ...referenceBook,
          })
            // eslint-disable-next-line no-loop-func
            .finally(() => {
              i++;

              if (i >= referenceBooks.length) {
                return resolve();
              }
            });
        }
      } catch (e) {
        return reject(e);
      }
    });
  },
  convertReferenceBookFromRegistry(ctx, { referenceBook }) {
    return (referenceBook?.documents || []).reduce((result: any, next: any) => {
      merge(result, {
        [next.code]: omit(next, 'code'),
      });

      return result;
    }, {});
  },
};
