import { Machine, assign } from 'xstate';
import { isPast } from 'date-fns';

type periodInput = {
  variables: {
    input: {
      subscriptionId: string,
      periodEnd: Date | null,
    },
  },
};

interface IPeriodMachineContext {
  subscriptionId: string;
  selectedDay: Date | null;
  nextDueDate: Date | null;
  calculatedPeriods: [any] | [];
  getPeriods: () => any;
  newPeriod: (({ }: periodInput) => any);
  calculatePeriods: ({ }: periodInput) => any;
  error: string | null;
}

const periodMachine = Machine<IPeriodMachineContext, any>(
  {
    id: 'periodMachine',
    initial: 'initialize',
    states: {
      initialize: {
        invoke: {
          id: 'invokeGetPeriods',
          src: (context, _) => {
            return context.getPeriods();
          },
        },
        on: {
          SUCCESS: {
            actions: 'setNextDueDate',
            target: 'editing',
          },
          ERROR: {
            actions: 'setError',
            target: 'error',
          },
        },
      },
      editing: {
        on: {
          CONFIRM: {
            target: 'setNewPeriod',
            cond: 'canConfirm',
          },
          'selectedDay.UPDATE': [
            {
              actions: 'updateSelectedDay',
              target: 'calculate',
              cond: 'validEdit',
            },
            {
              actions: 'updateSelectedDay',
            },
          ],
        },
      },
      calculate: {
        initial: 'calculating',
        states: {
          calculating: {
            invoke: {
              id: 'calculatePeriods',
              src: (context, _) => {
                return context.calculatePeriods({
                  variables: {
                    input: {
                      subscriptionId: context.subscriptionId,
                      periodEnd: context.selectedDay,
                    },
                  },
                });
              },
              onDone: {
                actions: 'calculatePeriodsResult',
                target: 'done',
              },
              onError: {
                actions: 'setError',
                target: '#periodMachine.error',
              },
            },
          },
          done: {
            always: [
              { target: '#periodMachine.editing', cond: 'notError' },
              { target: '#periodMachine.error', cond: 'isError' },
            ],
          },
        },
      },
      setNewPeriod: {
        initial: 'settingNewPeriod',
        states: {
          settingNewPeriod: {
            invoke: {
              id: 'setNewPeriodInvoke',
              src: (context, _) => {
                return context.newPeriod({
                  variables: {
                    input: {
                      subscriptionId: context.subscriptionId,
                      periodEnd: context.selectedDay,
                    },
                  },
                });
              },
              onDone: {
                actions: 'setNewPeriodResult',
                target: 'done',
              },
              onError: {
                actions: 'setError',
                target: '#periodMachine.error',
              },
            },
          },
          done: {
            always: [
              { target: '#periodMachine.success', cond: 'notError' },
              { target: '#periodMachine.error', cond: 'isError' },
            ],
          },
        },
      },
      success: {
        on: {
          RESET: {
            actions: 'reset',
            target: 'initialize',
          },
        },
      },
      error: {
        on: {
          RESET: {
            actions: 'reset',
            target: 'initialize',
          },
        },
      },
    },
  },
  {
    actions: {
      updateSelectedDay: assign({
        selectedDay: (_, event) => {
          return event.value ? event.value : null;
        },
      }),
      setNextDueDate: assign(
        (_context, event) => {

          const res = ((periods) => {
            const newestPeriod = periods?.[0];

            if (!newestPeriod || isPast?.(new Date(newestPeriod?.periodEnd))) {
              return null;
            }

            if (newestPeriod?.status === 'Paid') {
              return new Date(newestPeriod?.periodEnd);
            }

            return new Date(newestPeriod?.periodStart);

          })(event.value);

          return {
            nextDueDate: res,
          };
        },
      ),
      calculatePeriodsResult: assign(
        (_context, event) => {
          if (event?.data?.data?.calculatePeriods?.data) {
            return {
              calculatedPeriods: event?.data?.data?.calculatePeriods?.data ?? [],
              error: null,
            };
          }

          return {
            calculatedPeriods: [],
            error: event?.data?.data?.calculatePeriods?.error.message,
          };
        },
      ),
      setNewPeriodResult: assign(
        (_context, event) => {
          if (event?.data?.data?.changeSubscriptionPeriod?.error) {
            return {
              error: event?.data?.data?.changeSubscriptionPeriod?.error?.message,
            };
          }

          return {
            error: null,
          };
        },
      ),
      setError: assign(
        (_context, event) => {
          const error = event?.data?.message ?? event.value;
          return {
            error,
          };
        },
      ),
      reset: assign(
        (_context, _event) => {
          return {
            selectedDay: null,
            error: null,
            nextDueDate: null,
            calculatedPeriods: [],
          };
        },
      ),
    },
    guards: {
      validEdit: (_context, event) => {
        return !!event.value;
      },
      canConfirm: (context, _event) => {
        return !!context.selectedDay;
      },
      isError: (context, _event) => {
        return !!context.error;
      },
      notError: (context, _event) => {
        return !context.error;
      },
    },
  });

export { periodMachine };
