import Errors from 'ui/utils/errors';
import {
  get, set, computed, setProperties, observer
} from '@ember/object';
import { scheduleOnce } from '@ember/runloop';
import { alias, notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import NewOrEdit from 'shared/mixins/new-or-edit';
import C from 'ui/utils/constants';
import Util from 'ui/utils/util';
import { task } from 'ember-concurrency';
import YAML from 'yamljs';
import layout from './template';
import { stringifyAnswer } from 'shared/utils/evaluate';
import { isEmpty } from '@ember/utils';
import CatalogApp from 'shared/mixins/catalog-app';
import { all } from 'rsvp';

const OVERRIDE_HEADERS = [
  {
    translationKey: 'newMultiClusterApp.overrides.table.scope',
    name:           'scope',
    sort:           ['scope'],
  },
  {
    translationKey: 'newMultiClusterApp.overrides.table.question',
    name:           'question',
    sort:           ['question'],
  },
  {
    translationKey: 'newMultiClusterApp.overrides.table.answer',
    name:           'answer',
    sort:           ['answer'],
  },
];

export default Component.extend(NewOrEdit, CatalogApp, {
  catalog:                   service(),
  intl:                      service(),
  scope:                     service(),
  router:                    service(),
  settings:                  service(),
  globalStore:               service(),

  layout,
  allTemplates:              null,
  templateResource:          null,
  versionsArray:             null,
  versionsLinks:             null,
  actuallySave:              true,
  showHeader:                true,
  showPreview:               true,
  decoding:                  false,
  upgradeStrategy:           false,
  titleAdd:                  'newCatalog.titleAdd',
  titleUpgrade:              'newCatalog.titleUpgrade',
  selectVersionAdd:          'newCatalog.selectVersionAdd',
  selectVersionUpgrade:      'newCatalog.selectVersionUpgrade',
  saveUpgrade:               'newCatalog.saveUpgrade',
  saveNew:                   'newCatalog.saveNew',
  sectionClass:              'box mb-20',
  showDefaultVersionOption:  false,

  classNames:                ['launch-catalog', 'launch-multicluster-app'],
  multiClusterApp:           null,
  srcSet:                    false,

  detailExpanded:            false,
  previewOpen:               false,
  previewTab:                null,
  questionsArray:            null,
  selectedTemplateUrl:       null,
  selectedTemplateModel:     null,
  readmeContent:             null,
  appReadmeContent:          null,
  pastedAnswers:             null,
  noAppReadme:               null,
  selectedFileContetnt:      null,
  answerOverrides:           null,
  projects:                  null,
  clusters:                  null,
  isClone:                   false,
  projectsToAddOnUpgrade:    null,
  projectsToRemoveOnUpgrade: null,

  overridesHeaders:          OVERRIDE_HEADERS,

  isGKE:                     alias('scope.currentCluster.isGKE'),

  primaryResource:           alias('multiClusterApp'),
  editing:                   notEmpty('primaryResource.id'),

  init() {
    this._super(...arguments);

    this.initAttrs();
    this.initUpgradeStrategy();

    scheduleOnce('afterRender', () => {
      if ( get(this, 'selectedTemplateUrl') ) {
        this.templateChanged();
      } else {
        this.initSelectedTemplateModel();
      }
    });
  },

  didRender() {
    this.initCatalogIcon();
  },

  actions: {
    addTarget(targetIn) {
      if (targetIn && !get(targetIn, 'type')) {
        const {
          multiClusterApp, editing, projectsToAddOnUpgrade, projectsToRemoveOnUpgrade
        }    = this;

        let target = null;
        let toRemoveMatch = (projectsToRemoveOnUpgrade || []).findBy('projectId', get(targetIn, 'value'));

        if (toRemoveMatch) {
          // a project was remove then re-added
          this.projectsToRemoveOnUpgrade.removeObject(targetIn);

          target = toRemoveMatch;
        } else {
          target = this.globalStore.createRecord({
            type:      'target',
            projectId: get(targetIn, 'value'),
          });
        }

        if (editing) {
          projectsToAddOnUpgrade.pushObject(target);
        }

        if (multiClusterApp.targets) {
          multiClusterApp.targets.pushObject(target);
        } else {
          set(multiClusterApp, 'targets', [target]);
        }
      }
    },

    removeTarget(target) {
      const {
        editing, projectsToRemoveOnUpgrade, projectsToAddOnUpgrade
      } = this;

      let targetToAddMatch = (projectsToAddOnUpgrade || []).findBy('projectId', get(target, 'projectId'));

      if (targetToAddMatch) {
        // a project was added then removed
        this.projectsToAddOnUpgrade.removeObject(targetToAddMatch);
      } else {
        if (editing) {
          projectsToRemoveOnUpgrade.pushObject(target);
        }
      }

      get(this, 'multiClusterApp.targets').removeObject(target);
    },

    addRole(roleId, roleToRemove) {
      let { roles } = this.multiClusterApp;

      if (!roles) {
        roles = [];
      }

      if (roles.indexOf(roleToRemove) > -1) {
        roles.removeObject(roleToRemove);
      }

      if (roleId !== '') {
        roles.pushObject(roleId);

        set(this, 'multiClusterApp.roles', roles);
      } else {
        // its possible that the user set extra roles via the api, we shouldn't clobber those roles as well.
        if (roles.length > 1) {
          set(this, 'multiClusterApp.roles', roles);
        } else {
          set(this, 'multiClusterApp.roles', null);
        }
      }
    },

    addAuthorizedPrincipal(principal) {
      if (principal) {
        let { members = [] } = this.multiClusterApp;

        if (!members) {
          members = [];
        }

        members.pushObject(principal);

        set(this, 'multiClusterApp.members', members);
      }
    },

    removeMember(member) {
      let { members } = this.multiClusterApp;

      members.removeObject(member);
    },

    gotError(err) {
      set(this, 'errors', [Errors.stringify(err)]);
    },

    addAnswerOverride() {
      let { answerOverrides } = this;
      let nueOverride = {
        scope:         null,
        question:      null,
        answer:        null,
        isSubQuestion: false,
      }

      if (answerOverrides) {
        answerOverrides.pushObject(nueOverride);
      } else {
        answerOverrides = [nueOverride];
      }

      set(this, 'answerOverrides', answerOverrides);
    },

    removeAnswerOverride(answer) {
      this.answerOverrides.removeObject(answer);
    },

    addDependentSubQuestions(answers) {
      let { answerOverrides } = this;

      answerOverrides.pushObjects(answers);
    },

    removeDependentSubQuestions(answers) {
      let { answerOverrides } = this;

      answerOverrides.removeObjects(answers);
    },

    toogleDetailedDescriptions() {
      set(this, 'detailExpanded', true);
    },

    cancel() {
      if (this.cancel) {
        this.cancel();
      }
    },

    togglePreview() {
      this.toggleProperty('previewOpen');
    },

    selectPreviewTab(tab) {
      set(this, 'previewTab', tab);
    },
  },

  updateAnswerOverrides: observer('selectedTemplateModel', 'multiClusterApp.answers.@each.{values}', function() {
    let { selectedTemplateModel = {} } = this;

    const questions       = get(selectedTemplateModel, 'questions')
    const customAnswers   = questions ? get(selectedTemplateModel, 'customAnswers') : get(this, 'multiClusterApp.answers');
    const answerOverrides = [];

    Object.keys(customAnswers).forEach( (customAnswerKey) => {
      let answer = get(customAnswers, customAnswerKey);

      if (isGlobalAnswersCollection(answer)) {
        if (this.isClone || this.editing) {
          Object.keys(answer.values).forEach((valueKey) => {
            ( questions || []).forEach((q) => {
              if (get(q, 'variable') === valueKey) {
                customAnswers[customAnswerKey];
                let allAnswersFromInput = get(customAnswers, `${ customAnswerKey }.values`);
                let answerFromInput     = allAnswersFromInput ? allAnswersFromInput[valueKey] : null;

                try {
                  answerFromInput = JSON.parse(answerFromInput)
                } catch (e) {
                }

                set(q, 'answer', answerFromInput);
              } else if (get(q, 'subquestions') && get(q, 'subquestions').findBy('variable', valueKey)) {
                let sqMatch             = get(q, 'subquestions').findBy('variable', valueKey);
                let allAnswersFromInput = get(customAnswers, `${ customAnswerKey }.values`);
                let answerFromInput     = allAnswersFromInput ? allAnswersFromInput[valueKey] : null;

                set(sqMatch, 'answer', answerFromInput);
              }
            });
          });
        }
      } else {
        Object.keys(answer.values).forEach((valueKey) => {
          let isSubQuestion = false;

          (questions || []).forEach((q) => {
            if (get(q, 'subquestions') && get(q, 'subquestions').findBy('variable', valueKey)) {
              isSubQuestion  = true;
            }
          });

          let nueOverride = {
            scope:         answer.clusterId || answer.projectId,
            answer:        get(answer.values, valueKey),
            question:      valueKey,
            isSubQuestion,
          }

          answerOverrides.pushObject(nueOverride);
        });
      }
    });

    set(this, 'answerOverrides', answerOverrides);

    function isGlobalAnswersCollection(answer) {
      if (isEmpty(answer.clusterId) && isEmpty(answer.projectId)) {
        return true;
      } else {
        return false;
      }
    }
  }),

  upgradeStrategyChanged: observer('upgradeStrategy', function() {
    const {
      upgradeStrategy, multiClusterApp, globalStore
    } = this;

    if (upgradeStrategy) {
      set(multiClusterApp, 'upgradeStrategy', globalStore.createRecord({
        type:          'upgradeStrategy',
        rollingUpdate: globalStore.createRecord({
          type:      'rollingUpdate',
          batchSize: 1,
          interval:  1,
        })
      }));
    } else {
      set(multiClusterApp, 'upgradeStrategy', null);
    }
  }),

  templateOrHelmChartQuestions: computed('selectedTemplateModel', function() {
    let { selectedTemplateModel, multiClusterApp } = this;
    let nueQuestions = [];

    if (get(selectedTemplateModel, 'questions')) {
      return get(selectedTemplateModel, 'questions');
    } else {
      if (get(multiClusterApp, 'answers.firstObject.values')) {
        let helmQuestions = get(multiClusterApp, 'answers.firstObject.values');

        nueQuestions = Object.keys(helmQuestions).map((qk) => {
          return {
            variable: qk,
            answer:   get(helmQuestions, qk),
          };
        });
      }

      return nueQuestions;
    }
  }),

  answers: computed('templateOrHelmChartQuestions.@each.{variable,answer}', function() {
    const out = {};

    (get(this, 'templateOrHelmChartQuestions') || []).forEach((item) => {
      out[item.variable] = stringifyAnswer(item.answer);
      (get(item, 'subquestions') || []).forEach((sub) => {
        out[sub.variable] = stringifyAnswer(sub.answer);
      });
    });

    return out;
  }),

  answersArray: computed('selectedTemplateModel.questions', 'selectedTemplateModel.customAnswers', 'primaryResource.answers', function() {
    const model = get(this, 'selectedTemplateModel');

    if (get(model, 'questions')) {
      const questions = [];

      (get(this, 'selectedTemplateModel.questions') || []).forEach((q) => {
        questions.push(q);
        const subquestions = get(q, 'subquestions');

        if ( subquestions ) {
          questions.pushObjects(subquestions);
        }
      });

      const customAnswers = get(this, 'selectedTemplateModel.customAnswers') || {};

      Object.keys(customAnswers).forEach((key) => {
        questions.push({
          variable: key,
          answer:   customAnswers[key],
        });
      });

      return questions;
    } else {
      return get(this, 'primaryResource.answers.firstObject.values');
    }
  }),

  answersString: computed('answersArray.@each.{variable,answer}', function() {
    const model = get(this, 'selectedTemplateModel');

    if (get(model, 'questions')) {
      let neu = {};

      (get(this, 'answersArray') || []).filter((a) => typeof a.answer !== 'object' ).forEach((a) => {
        neu[a.variable] = isEmpty(a.answer) ? a.default : a.answer;
      });

      const customAnswers = get(model, 'customAnswers') || {};

      Object.keys(customAnswers).forEach((key) => {
        if ( typeof customAnswers[key] !== 'object' ) {
          neu[key] = customAnswers[key];
        }
      });

      return YAML.stringify(neu);
    } else {
      return JSON.stringify(get(this, 'answersArray'));
    }
  }),

  allProjectsAndClustersUngrouped: computed('projects.[]', 'primaryResource.targets.@each.projectId', function() {
    let out = [];

    get(this, 'clusters').forEach( (c) => {
      out.pushObject({
        name:      get(c, 'name'),
        value:     get(c, 'id'),
        isCluster: true,
      });

      c.get('projects').forEach( (p) => {
        out.pushObject({
          name:      get(p, 'name'),
          value:     get(p, 'id'),
          isProject: true,
        });
      });
    });

    return out;
  }),


  getTemplate: task(function * () {
    let url = get(this, 'selectedTemplateUrl');

    if ( url === 'default' ) {
      let defaultUrl = get(this, 'defaultUrl');

      if ( defaultUrl ) {
        url = defaultUrl;
      } else {
        url = null;
      }
    }

    if (url) {
      let version = get(this, 'settings.rancherVersion');

      if ( version ) {
        url = Util.addQueryParam(url, 'rancherVersion', version);
      }

      let current = get(this, 'primaryResource.answers');

      if ( !current ) {
        current = {};
        set(this, 'primaryResource.answers', current);
      }

      var selectedTemplateModel = yield get(this, 'catalog').fetchByUrl(url)
        .then((response) => {
          if (response.questions) {
            const questions = [];
            const customAnswers = {};

            response.questions.forEach((q) => {
              questions.push(q);
              const subquestions = get(q, 'subquestions');

              if ( subquestions ) {
                questions.pushObjects(subquestions);
              }
            });
            questions.forEach((item) => {
            // This will be the component that is rendered to edit this answer
              item.inputComponent = `schema/input-${ item.type }`;

              // Only types marked supported will show the component, Ember will explode if the component doesn't exist
              item.supported = C.SUPPORTED_SCHEMA_INPUTS.indexOf(item.type) >= 0;

              if (typeof current[item.variable] !== 'undefined') {
              // If there's an existing value, use it (for upgrade)
                item.answer = current[item.variable];
              } else if (item.type === 'service' || item.type === 'certificate') {
              // Loaded async and then the component picks the default
              } else if ( item.type === 'boolean' ) {
              // Coerce booleans
                item.answer = (item.default === 'true' || item.default === true);
              } else {
              // Everything else
                item.answer = item.default || null;
              }
            });

            Object.keys(current).forEach((key) => {
              const q = questions.findBy('variable', key);

              if ( !q ) {
                customAnswers[key] = current[key];
              }
            });

            response.customAnswers = customAnswers;
          }

          return response;
        });

      setProperties(this, {
        selectedTemplateModel,
        'primaryResource.templateVersionId': selectedTemplateModel.id,
      });

      const files = Object.keys(selectedTemplateModel.get('files')) || [];

      if ( files.length > 0 ) {
        const valuesYaml = files.find((file) => file.endsWith('/values.yaml'));

        set(this, 'previewTab', valuesYaml ? valuesYaml : files[0]);
      }
    } else {
      setProperties(this, {
        selectedTemplateModel: null,
        readmeContent:         null,
        appReadmeContent:      null,
        noAppReadme:           false,
      })
    }

    this.updateReadme();
  }),

  validate() {
    this._super(...arguments);

    let ok       = true;
    const errors = get(this, 'errors') || [];

    errors.pushObjects(get(this, 'selectedTemplateModel').validationErrors() || []);
    errors.pushObjects(this.validateTargetsProjectIds());

    set(this, 'errors', errors.uniq());

    if (errors.length > 1) {
      ok = false;
    }

    return ok;
  },

  validateTargetsProjectIds() {
    let errors = [];
    let targets = get(this, 'multiClusterApp.targets');

    if (targets && targets.length >= 1) {
      targets.forEach((target) => {
        if (!get(target, 'projectId')) {
          errors.push(this.intl.t('validation.targets.missingProjectId'));
        }
      });
    }

    return errors;
  },

  willSave() {
    set(this, 'errors', null);
    const ok                  = this.validate();
    const { primaryResource } = this;

    if (!ok) {
      // Validation failed
      return false;
    }

    if ( get(this, 'actuallySave') ) {
      primaryResource.set('answers', this.buildAnswerMap())

      if (this.editing) {
        return this.doProjectActions();
      } else {
        return true;
      }
    } else {
      return false;
    }
  },

  doProjectActions() {
    const { primaryResource } = this;
    const { projectsToAddOnUpgrade, projectsToRemoveOnUpgrade } = this;
    const promises = [];

    if (projectsToAddOnUpgrade && projectsToAddOnUpgrade.length > 0) {
      promises.push(primaryResource.doAction('addProjects', { projects: projectsToAddOnUpgrade.map((p) => get(p, 'projectId'))  }));
    }

    if (projectsToRemoveOnUpgrade && projectsToRemoveOnUpgrade.length > 0) {
      promises.push(primaryResource.doAction('removeProjects', { projects: projectsToRemoveOnUpgrade.map((p) => get(p, 'projectId'))  }));
    }

    if (promises.length > 0) {
      return all(promises)
        .then(() => {
          return true;
        })
        .catch((/* handled by growl error */) => {
          return false;
        });
    } else {
      return true;
    }
  },

  doneSaving() {
    return this.router.transitionTo('global-admin.multi-cluster-apps');
  },

  buildAnswerMap() {
    const {
      globalStore, answers, answerOverrides
    }          = this;
    let answer = {
      type:      'answer',
      clusterId: null,
      projectId: null,
      values:    null
    };
    let out = [];

    let globalAnswers = answer;

    set(globalAnswers, 'values', answers);

    out.pushObject(globalStore.createRecord(globalAnswers));

    if (answerOverrides && answerOverrides.length > 0) {
      answerOverrides.forEach( (override) => {
        let outMatch         = out.findBy('clusterId', override.scope) || out.findBy('projectId', override.scope);
        let questionVariable = get(override, 'question.variable') ? override.question.variable : override.question;

        if (outMatch) {
          outMatch.values[questionVariable] = stringifyAnswer(override.answer);
        } else {
          let newOverrideAnswer = {
            type:      'answer',
            clusterId: null,
            projectId: null,
            values:    {}
          };
          let overrideScope = get(this, 'allProjectsAndClustersUngrouped').findBy('value', override.scope);

          if (get(overrideScope, 'isProject')) {
            set(newOverrideAnswer, 'projectId', override.scope);
          }

          if (get(overrideScope, 'isCluster')) {
            set(newOverrideAnswer, 'clusterId', override.scope);
          }

          newOverrideAnswer.values[questionVariable] = stringifyAnswer(override.answer);

          out.pushObject(globalStore.createRecord(newOverrideAnswer));
        }
      });
    }

    return out;
  },

  initAttrs() {
    setProperties(this, {
      selectedTemplateModel:     null,
      projectsToAddOnUpgrade:    [],
      projectsToRemoveOnUpgrade: [],
    });
  },

  initCatalogIcon() {
    if (!this.get('srcSet')) {
      set(this, 'srcSet', true);

      const $icon = this.$('img');

      $icon.attr('src', $icon.data('src'));

      this.$('img').on('error', () => {
        $icon.attr('src', `${ this.get('app.baseAssets') }assets/images/generic-catalog.svg`);
      });
    }
  },

  initSelectedTemplateModel() {
    let def     = get(this, 'templateResource.defaultVersion');
    const latest = get(this, 'templateResource.latestVersion');

    const links = get(this, 'versionLinks');
    const app   = get(this, 'primaryResource');

    if (get(app, 'id') && !get(this, 'upgrade')) {
      def = get(app, 'externalIdInfo.version');
    }

    set(this, 'selectedTemplateUrl', links[def] || links[latest] || null)

    return;
  },

  initUpgradeStrategy() {
    const { multiClusterApp } = this;

    if (get(multiClusterApp, 'upgradeStrategy.rollingUpdate')) {
      set(this, 'upgradeStrategy', true);
    }
  },

});
