import transitionApplicationState from '~/clusters/services/application_state_machine';
import {
  APPLICATION_STATUS,
  UNINSTALL_EVENT,
  UPDATE_EVENT,
  INSTALL_EVENT,
} from '~/clusters/constants';

const {
  NO_STATUS,
  SCHEDULED,
  NOT_INSTALLABLE,
  INSTALLABLE,
  INSTALLING,
  INSTALLED,
  ERROR,
  UPDATING,
  UPDATED,
  UPDATE_ERRORED,
  UNINSTALLING,
  UNINSTALL_ERRORED,
} = APPLICATION_STATUS;

const NO_EFFECTS = 'no effects';

describe('applicationStateMachine', () => {
  const noEffectsToEmptyObject = effects => (typeof effects === 'string' ? {} : effects);

  describe(`current state is ${NO_STATUS}`, () => {
    it.each`
      expectedState      | event                | effects
      ${INSTALLING}      | ${SCHEDULED}         | ${NO_EFFECTS}
      ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE}   | ${NO_EFFECTS}
      ${INSTALLABLE}     | ${INSTALLABLE}       | ${NO_EFFECTS}
      ${INSTALLING}      | ${INSTALLING}        | ${NO_EFFECTS}
      ${INSTALLED}       | ${INSTALLED}         | ${NO_EFFECTS}
      ${INSTALLABLE}     | ${ERROR}             | ${{ installFailed: true }}
      ${UPDATING}        | ${UPDATING}          | ${NO_EFFECTS}
      ${INSTALLED}       | ${UPDATED}           | ${NO_EFFECTS}
      ${INSTALLED}       | ${UPDATE_ERRORED}    | ${{ updateFailed: true }}
      ${UNINSTALLING}    | ${UNINSTALLING}      | ${NO_EFFECTS}
      ${INSTALLED}       | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
    `(`transitions to $expectedState on $event event and applies $effects`, data => {
      const { expectedState, event, effects } = data;
      const currentAppState = {
        status: NO_STATUS,
      };

      expect(transitionApplicationState(currentAppState, event)).toEqual({
        status: expectedState,
        ...noEffectsToEmptyObject(effects),
      });
    });
  });

  describe(`current state is ${NOT_INSTALLABLE}`, () => {
    it.each`
      expectedState  | event          | effects
      ${INSTALLABLE} | ${INSTALLABLE} | ${NO_EFFECTS}
    `(`transitions to $expectedState on $event event and applies $effects`, data => {
      const { expectedState, event, effects } = data;
      const currentAppState = {
        status: NOT_INSTALLABLE,
      };

      expect(transitionApplicationState(currentAppState, event)).toEqual({
        status: expectedState,
        ...noEffectsToEmptyObject(effects),
      });
    });
  });

  describe(`current state is ${INSTALLABLE}`, () => {
    it.each`
      expectedState      | event              | effects
      ${INSTALLING}      | ${INSTALL_EVENT}   | ${{ installFailed: false }}
      ${INSTALLED}       | ${INSTALLED}       | ${NO_EFFECTS}
      ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
    `(`transitions to $expectedState on $event event and applies $effects`, data => {
      const { expectedState, event, effects } = data;
      const currentAppState = {
        status: INSTALLABLE,
      };

      expect(transitionApplicationState(currentAppState, event)).toEqual({
        status: expectedState,
        ...noEffectsToEmptyObject(effects),
      });
    });
  });

  describe(`current state is ${INSTALLING}`, () => {
    it.each`
      expectedState  | event        | effects
      ${INSTALLED}   | ${INSTALLED} | ${NO_EFFECTS}
      ${INSTALLABLE} | ${ERROR}     | ${{ installFailed: true }}
    `(`transitions to $expectedState on $event event and applies $effects`, data => {
      const { expectedState, event, effects } = data;
      const currentAppState = {
        status: INSTALLING,
      };

      expect(transitionApplicationState(currentAppState, event)).toEqual({
        status: expectedState,
        ...noEffectsToEmptyObject(effects),
      });
    });
  });

  describe(`current state is ${INSTALLED}`, () => {
    it.each`
      expectedState      | event              | effects
      ${UPDATING}        | ${UPDATE_EVENT}    | ${{ updateFailed: false, updateSuccessful: false }}
      ${UNINSTALLING}    | ${UNINSTALL_EVENT} | ${{ uninstallFailed: false, uninstallSuccessful: false }}
      ${NOT_INSTALLABLE} | ${NOT_INSTALLABLE} | ${NO_EFFECTS}
    `(`transitions to $expectedState on $event event and applies $effects`, data => {
      const { expectedState, event, effects } = data;
      const currentAppState = {
        status: INSTALLED,
      };

      expect(transitionApplicationState(currentAppState, event)).toEqual({
        status: expectedState,
        ...noEffectsToEmptyObject(effects),
      });
    });
  });

  describe(`current state is ${UPDATING}`, () => {
    it.each`
      expectedState | event             | effects
      ${INSTALLED}  | ${UPDATED}        | ${{ updateSuccessful: true }}
      ${INSTALLED}  | ${UPDATE_ERRORED} | ${{ updateFailed: true }}
    `(`transitions to $expectedState on $event event and applies $effects`, data => {
      const { expectedState, event, effects } = data;
      const currentAppState = {
        status: UPDATING,
      };

      expect(transitionApplicationState(currentAppState, event)).toEqual({
        status: expectedState,
        ...effects,
      });
    });
  });

  describe(`current state is ${UNINSTALLING}`, () => {
    it.each`
      expectedState  | event                | effects
      ${INSTALLABLE} | ${INSTALLABLE}       | ${{ uninstallSuccessful: true }}
      ${INSTALLED}   | ${UNINSTALL_ERRORED} | ${{ uninstallFailed: true }}
    `(`transitions to $expectedState on $event event and applies $effects`, data => {
      const { expectedState, event, effects } = data;
      const currentAppState = {
        status: UNINSTALLING,
      };

      expect(transitionApplicationState(currentAppState, event)).toEqual({
        status: expectedState,
        ...effects,
      });
    });
  });

  describe('current state is undefined', () => {
    it('returns the current state without having any effects', () => {
      const currentAppState = {};
      expect(transitionApplicationState(currentAppState, INSTALLABLE)).toEqual(currentAppState);
    });
  });

  describe('with event is undefined', () => {
    it('returns the current state without having any effects', () => {
      const currentAppState = {
        status: NO_STATUS,
      };
      expect(transitionApplicationState(currentAppState, undefined)).toEqual(currentAppState);
    });
  });
});
