text: import { isArray } from '@ember/array';
  import Component from '@ember/component';
  import {
    computed, get, observer, set, setProperties
  } from '@ember/object';
  import { alias, equal, union } from '@ember/object/computed';
  import { on } from '@ember/object/evented';
  import { inject as service } from '@ember/service';
  import { isEmpty } from '@ember/utils';
  import ipaddr from 'ipaddr.js';
  import { hash } from 'rsvp';
  import Semver from 'semver';
  import ClusterDriver from 'shared/mixins/cluster-driver';
  import C from 'shared/utils/constants';
  import { addQueryParams } from 'shared/utils/util';
  import { DEFAULT_AKS_CONFIG, DEFAULT_AKS_NODE_POOL_CONFIG } from 'ui/models/cluster';
  import { regionsWithAZs } from 'ui/utils/azure-choices';
  import layout from './template';
  
  const NETWORK_POLICY = [
    {
      label: 'None',
      value: null
    },
    {
      label: 'Calico',
      value: 'calico'
    },
    {
      label:    'Azure (requires Azure CNI)',
      value:    'azure',
      disabled: true
    }
  ];
  const CHINA_REGION_API_URL = 'https://management.chinacloudapi.cn/';
  const CHINA_REGION_AUTH_URL = 'https://login.chinacloudapi.cn/';
  const NETWORK_PLUGINS = [
    {
      label: 'Kubenet',
      value: 'kubenet'
    },
    {
      label: 'Azure CNI',
      value: 'azure'
    }
  ];
  const LB_SKUS = [
    {
      label: 'Standard',
      value: 'Standard'
    },
    {
      label: 'Basic',
      value: 'Basic'
    }
  ];
  
  export default Component.extend(ClusterDriver, {
    globalStore:          service(),
    intl:                 service(),
    settings:             service(),
    versionChoiceService: service('version-choices'),
    layout,
  
    configField: 'aksConfig',
  
    clusterAdvanced:           false,
    clusterErrors:             null,
    clusterLocationSaved:      false,
    clusterLocationSaving:     false,
    disableAzs:                false,
    enabledAuthorizedIpRanges: false,
    errors:                    null,
    lbSkus:                    LB_SKUS,
    loadBalancerImmutable:     false,
    monitoringRegionConent:    [],
    networkPlugins:            NETWORK_PLUGINS,
    otherErrors:               null,
    regions:                   null,
    step:                      1,
    versions:                  null,
    vmSizes:                   null,
    originalSecret:            null,
    regionsWithAZs,
  
    defaultNodePoolConfig: DEFAULT_AKS_NODE_POOL_CONFIG,
    defaultAksConfig:      DEFAULT_AKS_CONFIG,
  
    defaultK8sVersionRange: alias(`settings.${ C.SETTING.VERSION_SYSTEM_K8S_DEFAULT_RANGE }`),
    editing:                equal('mode', 'edit'),
    isNew:                  equal('mode', 'new'),
    allErrors:              union('errors', 'otherErrors', 'clusterErrors'),
  
    init() {
      this._super(...arguments);
  
      let config = get(this, 'cluster.aksConfig');
  
      if ( !config ) {
        config = this.globalStore.createRecord(JSON.parse(JSON.stringify(this.defaultAksConfig)));
        const defNodePool = this.globalStore.createRecord(JSON.parse(JSON.stringify(this.defaultNodePoolConfig)))
  
        setProperties(defNodePool, {
          isNew: true,
          name:  'agentpool',
        });
        set(config, 'nodePools', [defNodePool]);
  
        setProperties(this, {
          'cluster.aksConfig': config,
          'loadBalancerSku':   'Standard',
        });
      } else {
        if ( this.editing && this.importedClusterIsPending || (this.clusterIsPending && config?.privateCluster) ) {
          set(this, 'step', 4);
        } else {
          this.syncUpstreamConfig();
  
          const tags = { ...( config.tags || {} ) };
  
          set(this, 'tags', tags);
  
          if (!isEmpty(config?.authorizedIpRanges)) {
            set(this, 'enabledAuthorizedIpRanges', true);
          }
        }
      }
  
      let cur = config?.azureCredentialSecret;
  
      if ( cur && !cur.startsWith('cattle-global-data:') ) {
        cur = `cattle-global-data:${ cur }`;
      }
  
      set(this, 'originalSecret', cur);
    },
  
    actions: {
      addNodePool() {
        const config = get(this, `primaryResource.aksConfig`);
        const kubernetesVersion = get(config, 'kubernetesVersion');
        let nodePools = (get(config, 'nodePools') || []).slice();
        const npConfig = JSON.parse(JSON.stringify(this.defaultNodePoolConfig));
  
        if (!isArray(nodePools)) {
          nodePools = [];
        }
  
        const nodePool = this.globalStore.createRecord(npConfig);
  
        set(nodePool, 'mode', 'User')
  
        setProperties(nodePool, {
          orchestratorVersion: kubernetesVersion,
          isNew:               true,
        });
  
        nodePools.pushObject(nodePool);
  
        set(this, 'config.nodePools', nodePools);
      },
  
      removeNodePool(nodePool) {
        let { config: { nodePools = [] } } = this;
  
        if (!isEmpty(nodePools)) {
          nodePools.removeObject(nodePool);
        }
  
        set(this, 'config.nodePools', nodePools);
      },
      updateAuthorizedIpRanges(ipRanges) {
        set(this, 'config.authorizedIpRanges', ipRanges);
      },
      finishAndSelectCloudCredential(cred) {
        if (cred) {
          set(this, 'config.azureCredentialSecret', get(cred, 'id'));
          this.send('loadRegions', (ok, error) => {
            if (!ok) {
              this.send('errorHandler', error);
            }
          });
        }
      },
      async loadRegions(cb) {
        const store = get(this, 'globalStore')
        const selectedCloudCredential = this.selectedCloudCredential;
        const regionsWithAZs = this.regionsWithAZs
        const data = {
          cloudCredentialId: get(selectedCloudCredential, 'id'),
          // tenantId:          get(this, 'config.tenantId'),
        };
        const url = addQueryParams('/meta/aksLocations', data);
  
        try {
          const regions = await store.rawRequest({
            url,
            method: 'GET',
          });
  
          const filteredRegions = (regions?.body ?? []).map((reg) => {
            if (regionsWithAZs.includes(reg?.displayName || '')) {
              return {
                ...reg,
                ...{ group: 'High Availablity' }
              }
            } else {
              return {
                ...reg,
                ...{ group: 'Other' }
              };
            }
          });
  
          set(this, 'regions', filteredRegions);
  
          cb(true);
  
          set(this, 'step', 2);
  
          if (this.editing) {
            set(this, 'clusterLocationSaving', true);
            this.send('authenticate', (ok, error) => {
              if (!ok) {
                this.send('errorHandler', error);
              }
              set(this, 'clusterLocationSaving', false);
            })
          }
        } catch (error) {
          this.send('errorHandler', error);
          cb(false);
        }
      },
  
      authenticate(cb) {
        const store = get(this, 'globalStore')
        const { selectedCloudCredential } = this;
        const data = {
          cloudCredentialId: get(selectedCloudCredential, 'id'),
          // tenantId:          get(this, 'config.tenantId'),
          region:            get(this, 'config.resourceLocation'),
          clusterId:         get(this, 'cluster.id')
        };
  
        if ( get(this, 'isChinaRegion') ) {
          setProperties(data, {
            baseUrl:     CHINA_REGION_API_URL,
            authBaseUrl: CHINA_REGION_AUTH_URL
          })
        }
  
        const versionsUrl = addQueryParams('/meta/aksVersions', data);
        const upgradesUrl = addQueryParams('/meta/aksUpgrades', data);
        const vNetsUrl = addQueryParams('/meta/aksVirtualNetworks', data);
        const vmSizes = addQueryParams('/meta/aksVMSizes', data);
  
        const aksRequest = {
          versions: store.rawRequest({
            url:    versionsUrl,
            method: 'GET',
          }),
          upgradeVersions: data.clusterId ? store.rawRequest({
            url:    upgradesUrl,
            method: 'GET'
          }) : Promise.resolve({}),
          virtualNetworks: store.rawRequest({
            url:    vNetsUrl,
            method: 'GET',
          }),
          vmSizes: store.rawRequest({
            url:    vmSizes,
            method: 'GET',
          }),
        }
  
        return hash(aksRequest).then((resp) => {
          const { mode }                      = this;
          const {
            versions, upgradeVersions, virtualNetworks, vmSizes
          } = resp;
  
          const isEdit                        = mode === 'edit';
          const versionz                      = (get(versions, 'body') || []);
          const upgradeVersionz               = (get(upgradeVersions, 'body.upgrades') || []);
          const initialVersion                = isEdit ? this.config.kubernetesVersion : Semver.maxSatisfying(versionz, this.defaultK8sVersionRange); // default in azure ui
  
          if (!isEdit && initialVersion) {
            set(this, 'cluster.aksConfig.kubernetesVersion', initialVersion);
          }
  
          const enabledVersions = upgradeVersionz
            .filter((upgrade) => upgrade.enabled)
            .map((upgrade) => upgrade.version);
  
          setProperties(this, {
            step:            3,
            versions:        enabledVersions.length > 0 ? [initialVersion, ...enabledVersions] : versionz,
            virtualNetworks: (get(virtualNetworks, 'body') || []),
            vmSizes:         vmSizes?.body || [],
          });
  
          cb(true);
        }).catch((xhr) => {
          const err = xhr.body.message || xhr.body.code || xhr.body.error;
  
          setProperties(this, { errors: [err], });
  
          cb(false, [err]);
        });
      },
  
      setTags(section) {
        set(this, 'config.tags', section);
      },
    },
  
    resetNetworkPolicy: observer('config.{networkPolicy,networkPlugin}', function() {
      const { networkPolicy, networkPlugin } = this.config;
  
      if (networkPlugin === 'kubenet' && networkPolicy === 'azure') {
        set(this, 'config.networkPolicy', null);
      }
  
      if (this.config.networkPolicy === null) {
        set(this, 'cluster.enableNetworkPolicy', false)
      }
    }),
  
    resetIpRanges: observer('config.privateCluster', 'loadBalancerSku', function() {
      if (( this.config?.privateCluster || this.loadBalancerSku === 'Basic' ) && !isEmpty(this.config?.authorizedIpRanges)) {
        setProperties(this, {
          'config.authorizedIpRanges': [],
          enabledAuthorizedIpRanges:   false,
        });
      }
    }),
  
    resourceLocationChanged: observer('config.resourceLocation', function() {
      const { regions, config: { resourceLocation } } = this;
      const match = regions.findBy('name', resourceLocation);
  
      const regionHasAz = this.regionsWithAZs.includes(match?.displayName || '');
  
      if (regionHasAz) {
        set(this, 'disableAzs', false);
      } else {
        set(this, 'disableAzs', true);
      }
    }),
  
    postSaveChanged: observer('isPostSave', function() {
      const {
        isNew,
        isPostSave,
        config: { privateCluster },
        importedClusterIsPending,
      } = this;
  
      if ((privateCluster || importedClusterIsPending) && isPostSave) {
        if (isNew) {
          set(this, 'step', 4);
        } else {
          this.close();
        }
      } else {
        this.close();
      }
    }),
  
    availablityZonesChanged: on('init', observer('config.nodePools.[]', function() {
      const nodePools = get(this, 'config.nodePools') || [];
      const azs = [];
  
      nodePools.forEach((np) => {
        if (np?.availabilityZones && np.availabilityZones.length > 0) {
          azs.pushObjects(np.availabilityZones);
        }
      });
      const anySet = azs.uniq().any((az) => az);
  
      if (anySet) {
        setProperties(this, {
          'loadBalancerSku':       'Standard',
          'loadBalancerImmutable': true,
        });
      } else {
        set(this, 'loadBalancerImmutable', false);
      }
    })),
  
    resetAdvancedOptions: on('init', observer('config.networkPlugin', function() {
      if (get(this, 'isNew') && get(this, 'config.networkPlugin') === 'azure') {
        const config = get(this, 'config');
        const defaultConfigOptions = {
          dnsServiceIp:                '10.0.0.10',
          dockerBridgeCidr:            '172.17.0.1/16',
          podCidr:                     '172.244.0.0/16',
          serviceCidr:                 '10.0.0.0/16',
          subnet:                      '',
          virtualNetwork:              '',
          virtualNetworkResourceGroup: '',
        };
        const {
          subnet,
          virtualNetwork,
          virtualNetworkResourceGroup,
          serviceCidr,
          dnsServiceIp,
          dockerBridgeCidr
        } = defaultConfigOptions;
  
        setProperties(config, {
          subnet,
          virtualNetwork,
          virtualNetworkResourceGroup,
          serviceCidr,
          dnsServiceIp,
          dockerBridgeCidr
        });
      }
    })),
  
    monitoringConfigDisabled: computed('model.originalCluster.aksStatus.upstreamSpec.monitoring', 'isNewOrEditable', 'editing', function(){
      const { isNewOrEditable, editing } = this;
      const upstreamMonitoringEnabled = get(this, 'model.originalCluster.aksStatus.upstreamSpec.monitoring') ?? false;
  
      if (isNewOrEditable) {
        return false;
      }
  
      if (editing && !upstreamMonitoringEnabled) {
        return false;
      }
  
      return true;
    }),
  
    importedClusterIsPending: computed('clusterIsPending', 'model.originalCluster', function() {
      const { clusterIsPending } = this;
      const originalCluster = get(this, 'model.originalCluster');
      const ourClusterSpec = get(( originalCluster ?? {} ), 'aksConfig');
      const upstreamSpec = get(( originalCluster ?? {} ), 'aksStatus.upstreamSpec');
  
      return clusterIsPending && get(ourClusterSpec, 'imported') && !isEmpty(upstreamSpec);
    }),
  
    clusterIsPending: computed('clusterState', function() {
      const { clusterState } = this;
  
      return ['pending', 'provisioning', 'waiting'].includes(clusterState);
    }),
  
    anyWindowsNodes: computed('config.nodePools.@each.osType', function() {
      const nodePools = this?.config?.nodePools ?? [];
  
      if (isEmpty(nodePools)) {
        return false;
      }
  
      return nodePools.any((np) => np?.osType === 'Windows');
    }),
  
    hasProvisioned: computed('model.cluster', function() {
      const cluster = get(this, 'model.cluster');
      const { state = '', isError = false } = cluster;
      let clusterHasProvisioned = true;
  
      if (isError && state === 'provisioning') {
        if (isEmpty(cluster?.aksStatus?.upstreamSpec)) {
          clusterHasProvisioned = false;
        }
      }
  
      return clusterHasProvisioned;
    }),
  
    isNewOrEditable:   computed('hasProvisioned', 'isNew', 'mode', function() {
      const isNew = get(this, 'isNew');
  
      if (isNew) {
        return true;
      }
  
      return this.mode === 'edit' && !this.hasProvisioned;
    }),
  
    cloudCredentials: computed('model.cloudCredentials', 'originalSecret', function() {
      const out = this.model.cloudCredentials.filter((cc) => {
        const isAzure = Object.prototype.hasOwnProperty.call(cc, 'azurecredentialConfig');
  
        if (isAzure) {
          return true;
        }
  
        return false;
      });
  
      if ( this.originalSecret && !out.find((x) => x.id === this.originalSecret ) ) {
        const obj = this.globalStore.createRecord({
          name:                  `${ this.originalSecret.replace(/^cattle-global-data:/, '') } (current)`,
          id:                    this.originalSecret,
          type:                  'cloudCredential',
          azurecredentialConfig: {},
        });
  
        out.push(obj);
      }
  
      return out;
    }),
  
    selectedCloudCredential: computed('cloudCredentials.@each.id', 'config.azureCredentialSecret', function() {
      const cur = this.config?.azureCredentialSecret;
      const  cloudCredentials = this.cloudCredentials;
  
      if (isEmpty(cloudCredentials) && isEmpty(cur)) {
        return null;
      } else {
        return cloudCredentials.findBy('id', cur.includes('cattle-global-data:') ? cur : `cattle-global-data:${ cur }`);
      }
    }),
  
    versionChoices: computed('versions', function() {
      const {
        versions = [],
        mode,
        config : { kubernetesVersion: initialVersion }
      } = this;
  
      // azure versions come in oldest to newest
      return this.versionChoiceService.parseCloudProviderVersionChoices(( versions || [] ).reverse(), initialVersion, mode);
    }),
  
    networkChoice: computed({
      set( _key, value = '' ) {
        const [subnet, virtualNetwork, virtualNetworkResourceGroup] = value.split(':');
        const config = get(this, 'config');
  
        if (subnet && virtualNetwork && virtualNetworkResourceGroup) {
          setProperties(config, {
            subnet,
            virtualNetwork,
            virtualNetworkResourceGroup
          });
        }
  
        return value;
      }
    }),
  
    filteredVirtualNetworks: computed('config.virtualNetwork', 'virtualNetworks', function() {
      const vnets = get(this, 'virtualNetworks') || [];
      const subNets = [];
  
      vnets.forEach( (vnet) => {
        (get(vnet, 'subnets') || []).forEach( (subnet) => {
          subNets.pushObject({
            name:  `${ get(subnet, 'name') } (${ get(subnet, 'addressRange') })`,
            group: get(vnet, 'name'),
            value: `${ get(subnet, 'name') }:${ get(vnet, 'name') }:${ get(vnet, 'resourceGroup') }`
          })
        });
      });
  
      return subNets;
    }),
  
    networkChoiceDisplay: computed('virtualNetworks', 'config.virtualNetwork', 'config.subnet', function() {
      const selected = (get(this, 'virtualNetworks') || []).findBy('name', get(this, 'config.virtualNetwork')) || {}
      const subnet = (get(selected, 'subnets') || []).findBy('name', get(this, 'config.subnet')) || {}
  
      return `${ get(subnet, 'name') } (${ get(subnet, 'addressRange') })`
    }),
  
    isChinaRegion: computed('config.resourceLocation', function() {
      return get(this, 'config.resourceLocation').startsWith('china');
    }),
  
    networkPolicyContent: computed('config.networkPlugin', function() {
      const netPolicies = [...NETWORK_POLICY];
      const azureNetPolicy = netPolicies.findBy('value', 'azure');
  
      if (this?.config?.networkPlugin === 'azure') {
        set(azureNetPolicy, 'disabled', false);
      } else {
        set(azureNetPolicy, 'disabled', true);
      }
  
      return netPolicies;
    }),
  
    validate() {
      const intl   = get(this, 'intl');
      let model = get(this, 'cluster');
      let clusterErrors = model.validationErrors() || [];
      const nodePoolErrors = this.validateNodePools();
  
      const vnetSet = !!get(this, 'config.virtualNetwork');
  
      if (vnetSet) {
        clusterErrors = clusterErrors.concat(this.validateVnetInputs());
      }
  
      if ( this.isNew && !get(this, 'config.resourceGroup') ) {
        clusterErrors.push(intl.t('validation.required', { key: intl.t('clusterNew.azureaks.resourceGroup.label') }));
      }
  
      if ( this.isNew && !get(this, 'config.dnsPrefix') ) {
        clusterErrors.push(intl.t('validation.required', { key: intl.t('clusterNew.azureaks.dns.label') }));
      }
  
      set(this, 'errors', [...(this.errors ?? []), ...clusterErrors, ...nodePoolErrors]);
  
      return this.errors.length === 0;
    },
  
    validateNodePools() {
      const nodePools = get(this, 'primaryResource.aksConfig.nodePools');
      const errors = [];
  
      if (!isEmpty(nodePools)) {
        const nodePoolErrors = [];
  
        nodePools.forEach((np) => {
          const npErr = np.validationErrors();
          const npOs = np?.osType;
          const npName = np?.name;
  
          // aka.ms/aks-naming-rules
          if (npOs === 'Linux') {
            if (npName.length > 11) {
              errors.push(this.intl.t('clusterNew.azureaks.nodePools.errors.linuxName'));
            }
          } else if (npOs === 'Windows') {
            if (npName.length > 6) {
              errors.push(this.intl.t('clusterNew.azureaks.nodePools.errors.windowsName'));
            }
          }
  
          if (!/^[a-z][a-z0-9]+$/.test(npName)) {
            errors.push(this.intl.t('clusterNew.azureaks.nodePools.errors.nameFormat'));
          }
  
          nodePoolErrors.push(npErr)
        });
  
        if (!isEmpty(nodePoolErrors)) {
          errors.pushObjects(nodePoolErrors.flat());
        }
      }
  
      return errors;
    },
  
    validateVnetInputs() {
      const intl   = get(this, 'intl');
      const errors = [];
      const config = get(this, 'config');
      const vnet   = get(this, 'virtualNetworks').findBy('name', get(config, 'virtualNetwork'));
  
      if (vnet) {
        let subnet = get(vnet, `subnets`).findBy('name', get(config, 'subnet'));
        let vnetRange  = ipaddr.parseCIDR(get(subnet, 'addressRange'));
  
        let {
          serviceCidr, dnsServiceIp, dockerBridgeCidr
        } = config;
  
        let parsedServiceCidr      = null;
        let parsedDnsServiceIp     = null;
        let parsedDockerBridgeCidr = null;
  
        if (!serviceCidr && !dnsServiceIp && !dockerBridgeCidr) {
          errors.pushObject('You must include all required fields when using a Virtual Network');
        }
  
        try {
          parsedServiceCidr = ipaddr.parseCIDR(serviceCidr);
  
          // check if serviceCidr falls within the VNet/Subnet range
          if (parsedServiceCidr && vnetRange[0].match(parsedServiceCidr)) {
            errors.pushObject(intl.t('clusterNew.azureaks.errors.included.parsedServiceCidr'));
          }
        } catch ( err ) {
          errors.pushObject(intl.t('clusterNew.azureaks.errors.included.serviceCidr'));
        }
  
        try {
          parsedDnsServiceIp = ipaddr.parse(dnsServiceIp);
  
          // check if dnsService exists in range
          if (parsedDnsServiceIp && vnetRange[0].match(parsedDnsServiceIp, vnetRange[1])) {
            errors.pushObject(intl.t('clusterNew.azureaks.errors.included.parsedDnsServiceIp'));
          }
        } catch ( err ) {
          errors.pushObject(intl.t('clusterNew.azureaks.errors.included.dnsServiceIp'));
        }
  
        try {
          parsedDockerBridgeCidr = ipaddr.parseCIDR(dockerBridgeCidr);
  
          // check that dockerBridge doesn't overlap
          if (parsedDockerBridgeCidr && ( vnetRange[0].match(parsedDockerBridgeCidr) || parsedServiceCidr[0].match(parsedDockerBridgeCidr) )) {
            errors.pushObject(intl.t('clusterNew.azureaks.errors.included.parsedDockerBridgeCidr'));
          }
        } catch ( err ) {
          errors.pushObject(intl.t('clusterNew.azureaks.errors.included.dockerBridgeCidr'));
        }
      }
  
      return errors;
    },
  
    willSave() {
      const enableMonitoring = get(this, 'config.monitoring')
      const config = get(this, 'config')
  
      if (!enableMonitoring) {
        setProperties(config, {
          logAnalyticsWorkspaceResourceGroup: null,
          logAnalyticsWorkspace:              null,
        })
      }
  
      set(config, 'clusterName', this.cluster.name);
  
      if ( get(this, 'isChinaRegion') ) {
        setProperties(config, {
          baseUrl:     CHINA_REGION_API_URL,
          authBaseUrl: CHINA_REGION_AUTH_URL
        })
      } else {
        delete config['baseUrl'];
        delete config['authBaseUrl'];
      }
  
      Object.keys(config).forEach((k) => {
        const val = get(config, k);
  
        if (isEmpty(val)) {
          delete config[k];
        }
      });
  
      return this._super(...arguments);
    },
  
    syncUpstreamConfig() {
      const originalCluster = get(this, 'model.originalCluster').clone();
      const ourClusterSpec = get(originalCluster, 'aksConfig');
      const upstreamSpec = get(originalCluster, 'aksStatus.upstreamSpec');
  
      if (!isEmpty(upstreamSpec)) {
        Object.keys(upstreamSpec).forEach((k) => {
          if (isEmpty(get(ourClusterSpec, k)) && !isEmpty(get(upstreamSpec, k))) {
            set(this, `config.${ k }`, get(upstreamSpec, k));
          }
        });
      }
    },
  });
  