/*
Copyright (C) 2020  Cloudbase Solutions SRL
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import { action, observable, runInAction, computed } from "mobx";
import { MinionPool, MinionPoolDetails } from "@src/@types/MinionPool";
import MinionPoolSource from "@src/sources/MinionPoolSource";
import { Field, isEnumSeparator } from "@src/@types/Field";
import { Providers, ProviderTypes } from "@src/@types/Providers";
import { OptionsSchemaPlugin } from "@src/plugins";
import { providerTypes } from "@src/constants";
import apiCaller from "@src/utils/ApiCaller";
import { Endpoint, OptionValues } from "@src/@types/Endpoint";

export type MinionPoolAction = "allocate" | "deallocate" | "refresh";

export const MinionPoolStoreUtils = {
  isActive: (minionPool: MinionPool) =>
    minionPool.status === "ALLOCATED" ||
    minionPool.status === "SCALING" ||
    minionPool.status === "RESCALING",
};

class MinionPoolStore {
  @observable
  loadingMinionPools = false;

  @observable
  minionPools: MinionPool[] = [];

  @observable
  loadingMinionPoolDetails = false;

  @observable
  minionPoolDetails: MinionPoolDetails | null = null;

  @observable
  loadingMinionPoolSchema = false;

  @observable
  minionPoolDefaultSchema: Field[] = [];

  @observable
  minionPoolEnvSchema: Field[] = [];

  @observable
  optionsPrimaryLoading = false;

  @observable
  optionsSecondaryLoading = false;

  @computed
  get minionPoolCombinedSchema() {
    return this.minionPoolDefaultSchema.concat(this.minionPoolEnvSchema);
  }

  @action
  async loadMinionPools(options?: {
    showLoading?: boolean;
    skipLog?: boolean;
  }) {
    if (options?.showLoading) {
      this.loadingMinionPools = true;
    }

    try {
      const minionPools = await MinionPoolSource.loadMinionPools({
        skipLog: options?.skipLog,
      });

      runInAction(() => {
        this.minionPools = minionPools;
      });
    } finally {
      runInAction(() => {
        this.loadingMinionPools = false;
      });
    }
  }

  @action
  async loadMinionPoolDetails(
    id: string,
    options?: { showLoading?: boolean; skipLog?: boolean },
  ) {
    if (options?.showLoading) {
      this.loadingMinionPoolDetails = true;
    }

    try {
      const minionPool = await MinionPoolSource.loadMinionPoolDetails(id, {
        skipLog: options?.skipLog,
      });

      runInAction(() => {
        this.minionPoolDetails = minionPool;
      });
    } finally {
      runInAction(() => {
        this.loadingMinionPoolDetails = false;
      });
    }
  }

  @action clearMinionPoolDetails() {
    this.minionPoolDetails = null;
  }

  @action
  async loadMinionPoolSchema(
    provider: ProviderTypes,
    platform: "source" | "destination",
  ) {
    this.loadingMinionPoolSchema = true;

    this.minionPoolDefaultSchema =
      MinionPoolSource.getMinionPoolDefaultSchema();

    try {
      const schema = await MinionPoolSource.loadMinionPoolSchema(
        provider,
        platform,
      );

      runInAction(() => {
        this.minionPoolEnvSchema = schema;
      });
    } finally {
      runInAction(() => {
        this.loadingMinionPoolSchema = false;
      });
    }
  }

  private getOptionsValuesLastReqId = "";

  async loadOptions(config: {
    endpoint: Endpoint;
    providers: Providers;
    optionsType: "source" | "destination";
    envData?: { [prop: string]: any } | null;
    useCache?: boolean;
  }) {
    const { optionsType, endpoint, envData, useCache, providers } = config;
    const providerType =
      optionsType === "source"
        ? providerTypes.SOURCE_OPTIONS
        : providerTypes.DESTINATION_OPTIONS;

    const providerWithExtraOptions = providers[endpoint.type].types.find(
      t => t === providerType,
    );
    if (!providerWithExtraOptions) {
      return;
    }

    let canceled = false;
    apiCaller.cancelRequests(endpoint.id);

    this.optionsPrimaryLoading = !envData;
    this.optionsSecondaryLoading = !!envData;

    const reqId = `${endpoint.id}-${providerType}`;
    this.getOptionsValuesLastReqId = reqId;

    try {
      const options = await MinionPoolSource.loadOptions({
        optionsType,
        endpoint,
        envData,
        useCache,
      });
      this.getOptionsValuesSuccess(
        endpoint.type,
        options,
        this.getOptionsValuesLastReqId === reqId,
      );
    } catch (err) {
      canceled = err ? err.canceled : false;
      throw err;
    } finally {
      if (!canceled && this.getOptionsValuesLastReqId === reqId) {
        this.optionsPrimaryLoading = false;
        this.optionsSecondaryLoading = false;
      }
    }
  }

  @action getOptionsValuesSuccess(
    provider: ProviderTypes,
    options: OptionValues[],
    isValid: boolean,
  ) {
    if (!isValid) {
      return;
    }
    this.minionPoolEnvSchema.forEach(field => {
      const parser = OptionsSchemaPlugin.for(provider);
      parser.fillFieldValues({ field, options, requiresWindowsImage: false });
    });
    this.minionPoolEnvSchema = [...this.minionPoolEnvSchema];
  }

  @action
  async update(provider: ProviderTypes, minionPoolData: any) {
    return MinionPoolSource.update({
      data: minionPoolData,
      defaultSchema: this.minionPoolDefaultSchema,
      envSchema: this.minionPoolEnvSchema,
      provider,
    });
  }

  @action
  async add(provider: ProviderTypes, endpointId: string, minionPoolData: any) {
    return MinionPoolSource.add({
      endpointId,
      data: minionPoolData,
      defaultSchema: this.minionPoolDefaultSchema,
      envSchema: this.minionPoolEnvSchema,
      provider,
    });
  }

  @action
  async runAction(
    minionPoolId: string,
    minionPoolAction: MinionPoolAction,
    actionOptions?: any,
  ) {
    return MinionPoolSource.runAction(
      minionPoolId,
      minionPoolAction,
      actionOptions,
    );
  }

  async deleteMinionPool(minionPoolId: string) {
    return MinionPoolSource.deleteMinionPool(minionPoolId);
  }

  @action
  sortMigrImages(osType: "linux" | "windows") {
    const migrImageField = this.minionPoolEnvSchema.find(
      field => field.name === "migr_image",
    );
    if (!migrImageField || !migrImageField.enum) {
      return;
    }
    migrImageField.enum = migrImageField.enum
      .slice()
      .filter(f => !isEnumSeparator(f))
      .sort((a, b) => {
        const anyA = a as any;
        const anyB = b as any;
        if (anyA.os_type === osType && anyB.os_type !== osType) {
          return -1;
        } else if (anyB.os_type === osType && anyA.os_type !== osType) {
          return 1;
        } else {
          return 0;
        }
      });
    const osIndex = migrImageField.enum.findIndex(
      server => (server as any).os_type !== osType,
    );
    if (osIndex > -1) {
      migrImageField.enum.splice(osIndex, 0, { separator: true });
    }
    this.minionPoolEnvSchema = [...this.minionPoolEnvSchema];
  }
}

export default new MinionPoolStore();
