import AdyenStrategy from './strategy/adyen';
import BraintreeStrategy from './strategy/braintree';
import CybersourceStrategy from './strategy/cybersource';
import errors from '../../errors';
import find from 'component-find';
import Promise from 'promise';
import RiskConcern from '../risk-concern';
import SagePayStrategy from './strategy/sage-pay';
import StripeStrategy from './strategy/stripe';
import TestStrategy from './strategy/test';
import WirecardStrategy from './strategy/wirecard';
import WorldpayStrategy from './strategy/worldpay';
import OrbitalStrategy from './strategy/orbital';

const debug = require('debug')('recurly:risk:three-d-secure');

export function factory (options) {
  return new ThreeDSecure({ risk: this, ...options });
}

/**
 * 3D Secure 2.0
 *
 * Usage
 *
 * ```
 * const risk = recurly.Risk();
 * const threeDSecure = risk.ThreeDSecure({ actionTokenId: 'my-action-token-id' });
 *
 * threeDSecure.on('token', token => token);
 * threeDSecure.on('error', err => err);
 *
 * threeDSecure.attach(document.querySelector('#my-checkout-3ds-container'));
 * ```
 *
 * @param {Risk} risk
 * @param {String} [actionTokenId] when given, the concern will parse any auth actions
 *                                 necessary to fulfull the action
 */
export class ThreeDSecure extends RiskConcern {
  concernName = 'three-d-secure';

  static STRATEGIES = [
    AdyenStrategy,
    BraintreeStrategy,
    CybersourceStrategy,
    SagePayStrategy,
    StripeStrategy,
    TestStrategy,
    WirecardStrategy,
    WorldpayStrategy,
    OrbitalStrategy,
  ];

  /**
   * Returns a strateggy for a given gateway type
   *
   * @param  {String} type
   * @return {ThreeDSecureStrategy}
   */
  static getStrategyForGatewayType (type) {
    return find(ThreeDSecure.STRATEGIES, s => s.strategyName === type);
  }

  /**
   * Performs preflight procedures per strategy
   *
   * @typedef  {Object} Preflights
   * @property {Object} gateway
   * @property {String} gateway.type  e.g. 'worldpay'
   * @property {Object} params        params required by the strategy
   *
   * @param {Recurly}    options.recurly
   * @param {String}     options.bin         credit card BIN
   * @param {Preflights} options.preflights
   * @return {Promise}
   */
  static preflight ({ recurly, number, month, year, preflights }) {
    return Promise.all(preflights.map(preflight => {
      const { type } = preflight.gateway;
      const strategy = ThreeDSecure.getStrategyForGatewayType(type);
      return strategy.preflight({ recurly, number, month, year, ...preflight.params })
        .then(results => ({ processor: type, results }));
    }));
  }

  constructor ({ risk, actionTokenId }) {
    super({ risk });

    this.actionTokenId = actionTokenId;

    if (!actionTokenId) {
      throw errors('invalid-option', { name: 'actionTokenId', expect: 'a three_d_secure_action_token_id' });
    }

    this.recurly.request.get({ route: `/tokens/${actionTokenId}` })
      .catch(err => this.error(err))
      .then(token => {
        assertIsActionToken(token);
        this.strategy = this.getStrategyForActionToken(token);
        this.strategy.on('done', (...args) => this.onStrategyDone(...args));
        this.markReady();
      })
      .catch(err => this.error(err));

    this.report('create', { actionTokenId });
    this.whenReady(() => this.report('ready', { strategy: this.strategy.strategyName }));
  }

  /**
   * Instructs the strategy to attach, awaiting readiness of the strategy
   *
   * @param {HTMLElement} container
   */
  attach (container) {
    this.whenReady(() => {
      this.strategy.attach(container);
      this.report('attach');
    });
  }

  remove () {
    this.whenReady(() => {
      this.strategy.remove();
      this.report('remove');
    });
  }

  /**
   * Determines which strategy to employ based on an ThreeDSecureActionToken
   *
   * @private
   * @param {Object} actionToken
   */
  getStrategyForActionToken (actionToken) {
    const strategy = ThreeDSecure.getStrategyForGatewayType(actionToken.gateway.type);
    return new strategy({ threeDSecure: this, actionToken });
  }

  /**
   * Creates a ThreeDSecureActionResultToken from action results
   *
   * @private
   * @param {Object} results
   * @param {Function} done callback
   * @return {Promise} to create the token
   */
  createResultToken (results) {
    const data = {
      type: 'three_d_secure_action_result',
      three_d_secure_action_token_id: this.actionTokenId,
      results
    };
    debug('submitting results for tokenization', data);
    return this.recurly.request.post({ route: '/tokens', data });
  }

  // Listeners

  /**
   * Called when a strategy has produced results
   *
   * @param {Object} results
   */
  onStrategyDone (results) {
    this.createResultToken(results)
      .catch(cause => this.error('3ds-result-tokenization-error', { cause }))
      .then(token => {
        debug('Result token', token);
        this.emit('token', token);
        this.report('done', { actionResultTokenId: token.id });
      })
      .done();
  }
}

function assertIsActionToken (token) {
  if (token && token.type === 'three_d_secure_action') return;
  throw errors('invalid-option', { name: 'actionTokenId', expect: 'a three_d_secure_action_token_id' });
}

@@ -2331,42 +2331,106 @@
     
-return Promise.all(preflights.map(
+const finished_preflights = %5B%5D%0A    return preflights.reduce((preflight, result) =%3E %7B%0A      return 
 pref
@@ -2434,22 +2434,32 @@
 reflight
+.then(()
  =%3E %7B%0A
+  
       co
@@ -2473,24 +2473,21 @@
 ype %7D = 
-p
 re
-fligh
+sul
 t.gatewa
@@ -2489,16 +2489,66 @@
 ateway;%0A
+        const %7B gateway_code %7D = result.params;%0A  
       co
@@ -2604,24 +2604,26 @@
 Type(type);%0A
+  
       return
@@ -2677,24 +2677,21 @@
 ear, ...
-p
 re
-fligh
+sul
 t.params
@@ -2690,24 +2690,26 @@
 t.params %7D)%0A
+  
         .the
@@ -2720,20 +2720,56 @@
 sults =%3E
- 
+%7B%0A            finished_preflights.push
 (%7B
- 
 processo
@@ -2776,16 +2776,31 @@
 r: type,
+ gateway_code, 
  results
@@ -2806,22 +2806,77 @@
 s %7D)
-);
 %0A    
-%7D));%0A  
+       return finished_preflights%7D)%7D)%0A    %7D, Promise.resolve())%0A
 %7D%0A%0A 

modify 3ds preflights to process sequentially