/* eslint max-params: [2, 11] */
define([
  'underscore',
  'shared/views/BaseView',
  'shared/util/TemplateUtil',
  './AppDescriptionView',
  './tab/ModalTabs',
  './MappingsViewContent',
  'shared/models/ud/ProfileAppInstance',
  'shared/models/ud/AppUserProfile',
  'shared/models/ud/OktaUserProfile',
  'shared/models/ud/ProfilesMapping'
], function (_, BaseView, TemplateUtil, AppDescriptionView, ModalTabs, MappingsViewContent,
             ProfileAppInstance, AppUserProfile, OktaUserProfile, ProfilesMapping) {

  return BaseView.extend({
    className: 'attributes-mapping-modal-content',

    initialize: function () {
      _.bindAll(this, '_setAppProfile', '_setOktaProfile');
      this._updateMappingDirection();

      this.appModel = new ProfileAppInstance.Model();
      this._initModel();

      this.listenTo(this.state, 'change:tabId', function () {
        this._updateMappingDirection();
      });
    },

    _updateMappingDirection: function () {
      /* eslint max-statements: [2, 20] */
      var source,
          sourceId = this.model.get('sourceId'),
          targetId = this.model.get('targetId');
      if (sourceId && targetId && this.sourceTarget) {
        // from changing tab, swap source and target
        source = this.source;
        this.source = this.target;
        this.target = source;

        /**
         * set both sourceId and targetId will trigger retrieve fetch mapping.
         * silence one to avoid duplicated fetching.
         * @see this._initModel
         */
        this.model.set({sourceId: targetId}, {silent: true});
        this.model.set({targetId: sourceId});

        var appRole = this.sourceTarget.app;
        this.sourceTarget = {app: this.sourceTarget.okta, okta: appRole};
      } else {
        // from initialize
        // initialize source and target value base on the mapping direction
        source = 'appProfile';
        var target = 'oktaProfile',
            sourceName = 'sourceId',
            targetName = 'targetId';
        if (this.state.get('tabId') === 'oktaToApp') {
          // should not run into this case, unless we decided to swap the mapping direction (tabs)
          var tempSource = source,
              tempSourceName = sourceName;
          source = target;
          target = tempSource;
          sourceName = targetName;
          targetName = tempSourceName;
        }
        this.source = source;
        this.target = target;
        this.sourceTarget = {app: sourceName, okta: targetName};
      }
    },

    /**
     * Notes to main data flow:
     *
     * 1. appProfile.fetch + oktaProfile.fetch
     * 2. set sourceId and targetId,
     * 3. trigger `this.model.change` event
     * 4. invoke this._retrieveMapping
     * 5. trigger `this.model.reset` event
     * 6. invoke the reset handler (line 98)
     * 7. invoke this._addContentView
     *
     */
    _initModel: function () {
      this.sourceTargetData = {};

      this.sourceTargetData.appProfile = new AppUserProfile.Model({id: 'default'}, {
        instanceId: this.state.get('appInstanceId'),
        expand: ['schema', 'app', 'appLogo']
      });
      this.sourceTargetData.appProfile.fetch().then(this._setAppProfile);

      this.sourceTargetData.oktaProfile = new OktaUserProfile.Model({id: 'default'}, {expand: ['schema']});
      this.sourceTargetData.oktaProfile.fetch().then(this._setOktaProfile);

      this.listenTo(this.model, 'change:sourceId change:targetId', this._retrieveMapping);
      this.listenTo(this.model, 'reset', function () {
        this._updatePreviewModel();
        this._addAppDescriptionView();
        this._addTabs();
        this._addContentView();
      });
    },

    _setAppProfile: function (appProfile) {
      this.model.set(this.sourceTarget.app, appProfile.id);
      this.appModel.set(
        _.extend({}, appProfile['_embedded'].app, {logoHref: appProfile['_embedded'].appLogo.href})
      );
    },

    _setOktaProfile: function (oktaProfile) {
      this.model.set(this.sourceTarget.okta, oktaProfile.id);
    },

    _retrieveMapping: function () {
      if (this.model.get('sourceId') && this.model.get('targetId') &&
          (this.model.get('sourceId') !== this.model.get('targetId'))
         ) {
        var self = this;
        this.model
          .fetch({
            reset: true,
            data: {source: this.model.get('sourceId'), target: this.model.get('targetId')}
          })
          .fail(function () {
            self.model.set('propertyMappings', new ProfilesMapping.ProfileMappingCollection());
          })
          .always(function () {
            self.model.trigger('reset');
          });
      }
    },

    _addAppDescriptionView: function () {
      this.appDescriptionView && this.appDescriptionView.remove();

      this.appDescriptionView = new AppDescriptionView({
        state: this.state,
        appModel: this.appModel
      });
      this.add(this.appDescriptionView);
    },

    _addTabs: function () {
      this.tabs && this.tabs.remove();

      var toOktaTemplate = TemplateUtil.tpl('{{appInstanceName}} to Okta'),
          fromOktaTemplate = TemplateUtil.tpl('Okta to {{appInstanceName}}'),
          appLabel = this.appModel.get('label');

      var tabs = [
        { id: 'appToOkta',
          disp: toOktaTemplate({appInstanceName: appLabel}),
          link: '',
          selected: this.state.get('tabId') === 'appToOkta'
        },
        { id: 'oktaToApp',
          disp: fromOktaTemplate({appInstanceName: appLabel}),
          link: '',
          selected: this.state.get('tabId') === 'oktaToApp'
        }
      ];
      var showOnlyTabId = this.options.showOnlyTabId;
      if (showOnlyTabId) {
        var showOnlyTabIndex = _.findIndex(tabs, function (tab) {
          return tab.id === showOnlyTabId;
        });
        if (showOnlyTabIndex > -1) {
          this.state.set({tabId: showOnlyTabId});
          tabs = [tabs[showOnlyTabIndex]];
        }
      }

      this.tabs = new ModalTabs({
        state: this.state,
        settings: this.settings,
        tabs: tabs
      });
      this.add(this.tabs);
    },

    _addContentView: function () {
      this._initMappingData();
      this._processMappingData();

      this.content && this.content.remove();
      this.content = this.add(
        MappingsViewContent,
        {
          options: {
            systemPropertyMappings: this.systemPropertyMappings,
            previewModel: this.options.previewModel,
            collection: new ProfilesMapping.ProfileMappingCollection(this.mappingList),
            sourceOptions: this.sourceOptions,
            appModel: this.appModel
          },
          bubble: true
        }
      ).last();
      this.state.trigger('mappingsViewReady');
    },

    _initMappingData: function () {
      var source = this.source,
          target = this.target,
          targetAttributes,
          sourceAttributes;

      this.mappingList = [];
      this.sourceOptions = {};

      if (this.sourceTargetData[source] && this.sourceTargetData[target]) {
        // 1. get the original target and source lists
        targetAttributes = this.sourceTargetData[target].get('properties');
        sourceAttributes = this.sourceTargetData[source].get('properties');

        // 2. map source attributes to target attributes
        // put all the attributes from all the schemas to targetList
        var self = this;
        targetAttributes.each(function (targetAttribute) {
          if (!targetAttribute.get('__isReadOnly__')) {
            var name = targetAttribute.get('name');
            self.mappingList.push({
              '__targetAttribute__': targetAttribute.toJSON({verbose: true}),
              'targetField': name,
              'sourceExpression': ''
            });
          }
        });

        // 2.1 preserve SYSTEM mappings
        this.systemPropertyMappings = this._getSystemPropertyMappings(targetAttributes);

        // 3. compose source dropdown list
        sourceAttributes.each(function (sourceAttribute) {
          if (!sourceAttribute.get('__isWriteOnly__')) {
            var newSourceAttr = {};
            newSourceAttr[sourceAttribute.get('name')] = _.omit(sourceAttribute.toJSON({verbose: true}), 'name');
            self.sourceOptions = _.extend(self.sourceOptions, newSourceAttr);
          }
        });

      }
    },

    /*
     * SYSTEM attributes may have mappings but those attributes will not be in the `targetAttributes` list
     * since schema API only fetch attributes having scope NONE and SELF but not SYSTEM.
     * fixed for OKTA-88195
     */
    _getSystemPropertyMappings: function (targetAttributes) {
      if (!targetAttributes) {
        return [];
      }

      var allMappings = [];
      if (this.model.get('propertyMappings') instanceof ProfilesMapping.ProfileMappingCollection) {
        allMappings = this.model.get('propertyMappings').toJSON() || [];
      }

      var allTargetAttributes = targetAttributes.reduce(function (init, m) {
        init[m.get('name')] = true;
        return init;
      }, {});

      return _.reject(allMappings, function (mapping) {
        return allTargetAttributes[mapping.targetField];
      });
    },

    _processMappingData: function () {
      // put existing mappings to the init mapping list
      // example:
      // init mapping: [{targetField: 'login', sourceExpression: ''}, {targetField: 'email', sourceExpression: ''}]
      // existing mapping: [{targetField: 'email', sourceExpression: 'source.email'}]
      // output mapping: [{targetField: 'login', sourceExpression: ''},
      //                  {targetField: 'email', sourceExpression: 'source.email'}]
      if (this.model.get('propertyMappings') instanceof ProfilesMapping.ProfileMappingCollection) {
        var existingMappings = this.model.get('propertyMappings').toJSON({verbose: true}),
            self = this;
        _.each(existingMappings, function (existingMapping) {
          var defaultMapping = _.find(self.mappingList, function (completeMapping) {
            return completeMapping.targetField === existingMapping.targetField;
          });
          if (defaultMapping) {
            defaultMapping.sourceExpression = existingMapping.sourceExpression;
            defaultMapping.pushStatus = existingMapping.pushStatus;
          }
        });
      }
    },

    _updatePreviewModel: function () {
      this.options.previewModel.set({
        sourceId: this.model.get('sourceId'),
        targetId: this.model.get('targetId')
      }).unset('userId').unset('propertyMappings');
    }
  });
});
