import Ember from 'ember';
import { Machine } from 'xstate';

const { Service, inject } = Ember;

import getStorage from 'vault/lib/token-storage';

import TutorialMachineConfig from 'vault/machines/tutorial-machine';
import SecretsMachineConfig from 'vault/machines/secrets-machine';
import PoliciesMachineConfig from 'vault/machines/policies-machine';
import ReplicationMachineConfig from 'vault/machines/replication-machine';
import ToolsMachineConfig from 'vault/machines/tools-machine';
import AuthMachineConfig from 'vault/machines/auth-machine';

const TutorialMachine = Machine(TutorialMachineConfig);
let FeatureMachine = null;
const TUTORIAL_STATE = 'vault:ui-tutorial-state';
const FEATURE_LIST = 'vault:ui-feature-list';
const FEATURE_STATE = 'vault:ui-feature-state';
const COMPLETED_FEATURES = 'vault:ui-completed-list';
const COMPONENT_STATE = 'vault:ui-component-state';
const RESUME_URL = 'vault:ui-tutorial-resume-url';
const RESUME_ROUTE = 'vault:ui-tutorial-resume-route';
const MACHINES = {
  secrets: SecretsMachineConfig,
  policies: PoliciesMachineConfig,
  replication: ReplicationMachineConfig,
  tools: ToolsMachineConfig,
  authentication: AuthMachineConfig,
};

const DEFAULTS = {
  currentState: null,
  featureList: null,
  featureState: null,
  currentMachine: null,
  tutorialComponent: null,
  featureComponent: null,
  stepComponent: null,
  detailsComponent: null,
  componentState: null,
  nextFeature: null,
  nextStep: null,
};

export default Service.extend(DEFAULTS, {
  router: inject.service(),
  showWhenUnauthenticated: false,

  init() {
    this._super(...arguments);
    this.initializeMachines();
  },

  initializeMachines() {
    if (!this.storageHasKey(TUTORIAL_STATE)) {
      let state = TutorialMachine.initialState;
      this.saveState('currentState', state.value);
      this.saveExtState(TUTORIAL_STATE, state.value);
    }
    this.saveState('currentState', this.getExtState(TUTORIAL_STATE));
    if (this.storageHasKey(COMPONENT_STATE)) {
      this.set('componentState', this.getExtState(COMPONENT_STATE));
    }
    let stateNodes = TutorialMachine.getStateNodes(this.get('currentState'));
    this.executeActions(stateNodes.reduce((acc, node) => acc.concat(node.onEntry), []), null, 'tutorial');
    if (this.storageHasKey(FEATURE_LIST)) {
      this.set('featureList', this.getExtState(FEATURE_LIST));
      if (this.storageHasKey(FEATURE_STATE)) {
        this.saveState('featureState', this.getExtState(FEATURE_STATE));
      } else {
        if (FeatureMachine != null) {
          this.saveState('featureState', FeatureMachine.initialState);
          this.saveExtState(FEATURE_STATE, this.get('featureState'));
        }
      }
      this.buildFeatureMachine();
    }
  },

  restartGuide() {
    let storage = this.storage();
    // empty storage
    [
      TUTORIAL_STATE,
      FEATURE_LIST,
      FEATURE_STATE,
      COMPLETED_FEATURES,
      COMPONENT_STATE,
      RESUME_URL,
      RESUME_ROUTE,
    ].forEach(key => storage.removeItem(key));
    // reset wizard state
    this.setProperties(DEFAULTS);
    // restart machines from blank state
    this.initializeMachines();
    // progress machine to 'active.select'
    this.transitionTutorialMachine('idle', 'AUTH');
  },

  saveState(stateType, state) {
    if (state.value) {
      state = state.value;
    }
    let stateKey = '';
    while (Ember.typeOf(state) === 'object') {
      let newState = Object.keys(state);
      stateKey += newState + '.';
      state = state[newState];
    }
    stateKey += state;
    this.set(stateType, stateKey);
  },

  transitionTutorialMachine(currentState, event, extendedState) {
    if (extendedState) {
      this.set('componentState', extendedState);
      this.saveExtState(COMPONENT_STATE, extendedState);
    }
    let { actions, value } = TutorialMachine.transition(currentState, event);
    this.saveState('currentState', value);
    this.saveExtState(TUTORIAL_STATE, this.get('currentState'));
    this.executeActions(actions, event, 'tutorial');
  },

  transitionFeatureMachine(currentState, event, extendedState) {
    if (!FeatureMachine || !this.get('currentState').includes('active')) {
      return;
    }
    if (extendedState) {
      this.set('componentState', extendedState);
      this.saveExtState(COMPONENT_STATE, extendedState);
    }

    let { actions, value } = FeatureMachine.transition(currentState, event, this.get('componentState'));
    this.saveState('featureState', value);
    this.saveExtState(FEATURE_STATE, value);
    this.executeActions(actions, event, 'feature');
    // if all features were completed, the FeatureMachine gets nulled
    // out and won't exist here as there is no next step
    if (FeatureMachine) {
      let next;
      if (this.get('currentMachine') === 'secrets' && value === 'display') {
        next = FeatureMachine.transition(value, 'REPEAT', this.get('componentState'));
      } else {
        next = FeatureMachine.transition(value, 'CONTINUE', this.get('componentState'));
      }
      this.saveState('nextStep', next.value);
    }
  },

  saveExtState(key, value) {
    this.storage().setItem(key, value);
  },

  getExtState(key) {
    return this.storage().getItem(key);
  },

  storageHasKey(key) {
    return Boolean(this.getExtState(key));
  },

  executeActions(actions, event, machineType) {
    let transitionURL;
    let expectedRouteName;
    let router = this.get('router');

    for (let action of actions) {
      let type = action;
      if (action.type) {
        type = action.type;
      }
      switch (type) {
        case 'render':
          this.set(`${action.level}Component`, action.component);
          break;
        case 'routeTransition':
          expectedRouteName = action.params[0];
          transitionURL = router.urlFor(...action.params).replace(/^\/ui/, '');
          Ember.run.next(() => {
            router.transitionTo(...action.params);
          });
          break;
        case 'saveFeatures':
          this.saveFeatures(event.features);
          break;
        case 'completeFeature':
          this.completeFeature();
          break;
        case 'handleDismissed':
          this.handleDismissed();
          break;
        case 'handlePaused':
          this.handlePaused();
          return;
        case 'handleResume':
          this.handleResume();
          break;
        case 'showTutorialWhenAuthenticated':
          this.set('showWhenUnauthenticated', false);
          break;
        case 'showTutorialAlways':
          this.set('showWhenUnauthenticated', true);
          break;
        case 'continueFeature':
          this.transitionFeatureMachine(this.get('featureState'), 'CONTINUE', this.get('componentState'));
          break;
        default:
          break;
      }
    }
    if (machineType === 'tutorial') {
      return;
    }
    // if we're transitioning in the actions, we want that url,
    // else we want the URL we land on in didTransition in the
    // application route - we'll notify the application route to
    // update the route
    if (transitionURL) {
      this.set('expectedURL', transitionURL);
      this.set('expectedRouteName', expectedRouteName);
      this.set('setURLAfterTransition', false);
    } else {
      this.set('setURLAfterTransition', true);
    }
  },

  handlePaused() {
    let expected = this.get('expectedURL');
    if (expected) {
      this.saveExtState(RESUME_URL, this.get('expectedURL'));
      this.saveExtState(RESUME_ROUTE, this.get('expectedRouteName'));
    }
  },

  handleResume() {
    let resumeURL = this.storage().getItem(RESUME_URL);
    if (!resumeURL) {
      return;
    }
    this.get('router').transitionTo(resumeURL).followRedirects().then(() => {
      this.set('expectedRouteName', this.storage().getItem(RESUME_ROUTE));
      this.set('expectedURL', resumeURL);
      this.initializeMachines();
      this.storage().removeItem(RESUME_URL);
    });
  },

  handleDismissed() {
    this.storage().removeItem(FEATURE_STATE);
    this.storage().removeItem(FEATURE_LIST);
    this.storage().removeItem(COMPONENT_STATE);
  },

  saveFeatures(features) {
    this.set('featureList', features);
    this.saveExtState(FEATURE_LIST, this.get('featureList'));
    this.buildFeatureMachine();
  },

  buildFeatureMachine() {
    if (this.get('featureList') === null) {
      return;
    }
    this.startFeature();
    if (this.storageHasKey(FEATURE_STATE)) {
      this.saveState('featureState', this.getExtState(FEATURE_STATE));
    }
    this.saveExtState(FEATURE_STATE, this.get('featureState'));
    let nextFeature =
      this.get('featureList').length > 1 ? this.get('featureList').objectAt(1).capitalize() : 'Finish';
    this.set('nextFeature', nextFeature);
    let next;
    if (this.get('currentMachine') === 'secrets' && this.get('featureState') === 'display') {
      next = FeatureMachine.transition(this.get('featureState'), 'REPEAT', this.get('componentState'));
    } else {
      next = FeatureMachine.transition(this.get('featureState'), 'CONTINUE', this.get('componentState'));
    }
    this.saveState('nextStep', next.value);
    let stateNodes = FeatureMachine.getStateNodes(this.get('featureState'));
    this.executeActions(stateNodes.reduce((acc, node) => acc.concat(node.onEntry), []), null, 'feature');
  },

  startFeature() {
    const FeatureMachineConfig = MACHINES[this.get('featureList').objectAt(0)];
    FeatureMachine = Machine(FeatureMachineConfig);
    this.set('currentMachine', this.get('featureList').objectAt(0));
    this.saveState('featureState', FeatureMachine.initialState);
  },

  completeFeature() {
    let features = this.get('featureList');
    let done = features.shift();
    if (!this.getExtState(COMPLETED_FEATURES)) {
      let completed = [];
      completed.push(done);
      this.saveExtState(COMPLETED_FEATURES, completed);
    } else {
      this.saveExtState(COMPLETED_FEATURES, this.getExtState(COMPLETED_FEATURES).toArray().addObject(done));
    }

    this.saveExtState(FEATURE_LIST, features.length ? features : null);
    this.storage().removeItem(FEATURE_STATE);
    if (features.length > 0) {
      this.buildFeatureMachine();
    } else {
      this.storage().removeItem(FEATURE_LIST);
      FeatureMachine = null;
      this.transitionTutorialMachine(this.get('currentState'), 'DONE');
    }
  },

  storage() {
    return getStorage();
  },
});
