import _ from 'lodash';
import urlcat from 'urlcat';

import { createApiCall } from '../apiCall';
import { StartAction, SuccessAction, FailureAction } from './types';

import { ApiConfig } from '../types';

/**
 * Function which creates a thunk action for given api config and associated action types
 * @param {string|Record<string, unknown>} apiConfig if string it is interprated as path in api endpoint
 * @param {string[]} actionTypes action types for start, success and failure
 */
export const createApiAction = (apiConfig: ApiConfig, actionTypes: string[]) => async (dispatch: any) => {
  const apiCall = createApiCall(apiConfig);

  const startAction = (): StartAction => ({
    type: actionTypes[0],
    payload: null,
  });

  const successAction = (payload: any): SuccessAction => ({
    type: actionTypes[1],
    payload,
  });

  const failureAction = (error: string): FailureAction => ({
    type: actionTypes[2],
    payload: { error },
  });

  dispatch(startAction());

  try {
    const response = await apiCall();

    let res;
    const additionalData = apiConfig.additionalData || {};

    if (!_.isEmpty(additionalData)) {
      res = await dispatch(successAction({ ...response.data, ...additionalData }));
    } else {
      res = await dispatch(successAction(response.data));
    }

    return { ok: true, res };
  } catch (e) {
    const error = _.get(e, 'response.data', {
      errorMessage: e.message || 'Undefined error, please try again.',
    });

    await dispatch(failureAction(error));

    return {
      ok: false,
      errorMessage: error.message,
      status: e?.response?.status,
    };
  }
};

/**
 * Creates opinionated CRUD actions
 */
export function createCrudApiActions<T>(
  basePath: string,
  startActionType: string,
  failureActionType: string,
  fetchActionType: string,
  addActionType: string,
  updateActionType: string,
  deleteActionType: string,
  transformItemForApi: (data: T) => any,
  pathTemplates: { load?: string; add?: string; change?: string } = {
    load: '/',
    add: '/',
    change: '/:id',
  },
): any {
  const { load, add, change } = pathTemplates;
  const fetchItems = (
    pageNumber?: number,
    pageSize?: number,
    params: Record<string, unknown> = {},
    template: string = load!,
  ) =>
    createApiAction(
      {
        path: urlcat(basePath, template, { pageNumber, pageSize, ...params }),
        method: 'GET',
      },
      [startActionType, fetchActionType, failureActionType],
    );

  const addItem = (item: T, params: Record<string, unknown> = {}) =>
    createApiAction(
      {
        path: urlcat(basePath, add!, { ...params }),
        method: 'POST',
        body: transformItemForApi(item),
      },
      [startActionType, addActionType, failureActionType],
    );

  const updateItem = (item: T, params: Record<string, unknown> = {}) =>
    createApiAction(
      {
        // @ts-ignore
        path: urlcat(basePath, change!, { id: item.id, ...params }),
        method: 'PUT',
        body: transformItemForApi(item),
      },
      [startActionType, updateActionType, failureActionType],
    );

  const deleteItem = (itemId: number, params: Record<string, unknown> = {}) =>
    createApiAction(
      {
        path: urlcat(basePath, change!, { id: itemId, ...params }),
        method: 'DELETE',
      },
      [startActionType, deleteActionType, failureActionType],
    );

  return [fetchItems, addItem, updateItem, deleteItem];
}

/**
 * Creates opinionated CRUD actions
 */
export function createCrudApiActionsWithoutPaging<T extends { id?: number | string | null }>(
  basePath: string,
  startActionType: string,
  failureActionType: string,
  fetchActionType: string,
  addActionType: string,
  updateActionType: string,
  deleteActionType: string,
  transformItemForApi: (data: T) => any,
  pathTemplates: { load?: string; add?: string; change?: string } = {
    load: '/',
    add: '/',
    change: '/:id',
  },
): any {
  const { load, add, change } = pathTemplates;
  const fetchItems = (params: Record<string, unknown> = {}, template: string = load!) =>
    createApiAction(
      {
        path: urlcat(basePath, template, params),
        method: 'GET',
      },
      [startActionType, fetchActionType, failureActionType],
    );

  const addItem = (item: T, params: Record<string, unknown> = {}) =>
    createApiAction(
      {
        path: urlcat(basePath, add!, { ...params }),
        method: 'POST',
        body: transformItemForApi(item),
      },
      [startActionType, addActionType, failureActionType],
    );

  const updateItem = (item: T, params: Record<string, unknown> = {}) =>
    createApiAction(
      {
        path: urlcat(basePath, change!, { id: item.id, ...params }),
        method: 'PUT',
        body: transformItemForApi(item),
      },
      [startActionType, updateActionType, failureActionType],
    );

  const deleteItem = (itemId: number, params: Record<string, unknown> = {}) =>
    createApiAction(
      {
        path: urlcat(basePath, change!, { id: itemId, ...params }),
        method: 'DELETE',
      },
      [startActionType, deleteActionType, failureActionType],
    );

  return [fetchItems, addItem, updateItem, deleteItem];
}
