<script>
import {
  GlBadge,
  GlLink,
  GlLoadingIcon,
  GlPagination,
  GlDeprecatedSkeletonLoading as GlSkeletonLoading,
  GlSprintf,
  GlTable,
  GlTooltipDirective,
} from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { __, sprintf } from '~/locale';
import { CLUSTER_TYPES, STATUSES } from '../constants';
import AncestorNotice from './ancestor_notice.vue';
import NodeErrorHelpText from './node_error_help_text.vue';
import ClustersEmptyState from './clusters_empty_state.vue';

export default {
  nodeMemoryText: __('%{totalMemory} (%{freeSpacePercentage}%{percentSymbol} free)'),
  nodeCpuText: __('%{totalCpu} (%{freeSpacePercentage}%{percentSymbol} free)'),
  components: {
    AncestorNotice,
    GlBadge,
    GlLink,
    GlLoadingIcon,
    GlPagination,
    GlSkeletonLoading,
    GlSprintf,
    GlTable,
    NodeErrorHelpText,
    ClustersEmptyState,
  },
  directives: {
    GlTooltip: GlTooltipDirective,
  },
  props: {
    isChildComponent: {
      default: false,
      required: false,
      type: Boolean,
    },
    limit: {
      default: null,
      required: false,
      type: Number,
    },
  },
  computed: {
    ...mapState([
      'clusters',
      'clustersPerPage',
      'loadingClusters',
      'loadingNodes',
      'page',
      'providers',
      'totalClusters',
    ]),
    contentAlignClasses() {
      return 'gl-display-flex gl-align-items-center gl-justify-content-end gl-md-justify-content-start';
    },
    currentPage: {
      get() {
        return this.page;
      },
      set(newVal) {
        this.setPage(newVal);
        this.fetchClusters();
      },
    },
    fields() {
      const tdClass = 'gl-py-5!';
      return [
        {
          key: 'name',
          label: __('Kubernetes cluster'),
          tdClass,
        },
        {
          key: 'environment_scope',
          label: __('Environment scope'),
          tdClass,
        },
        {
          key: 'node_size',
          label: __('Nodes'),
          tdClass,
        },
        {
          key: 'total_cpu',
          label: __('Total cores (CPUs)'),
          tdClass,
        },
        {
          key: 'total_memory',
          label: __('Total memory (GB)'),
          tdClass,
        },
        {
          key: 'cluster_type',
          label: __('Cluster level'),
          tdClass,
          formatter: (value) => CLUSTER_TYPES[value],
        },
      ];
    },
    hasClustersPerPage() {
      return this.clustersPerPage > 0;
    },
    hasClusters() {
      return this.totalClusters > 0;
    },
  },
  mounted() {
    if (this.limit) {
      this.setClustersPerPage(this.limit);
    }

    this.fetchClusters();
  },
  methods: {
    ...mapActions(['fetchClusters', 'reportSentryError', 'setPage', 'setClustersPerPage']),
    k8sQuantityToGb(quantity) {
      if (!quantity) {
        return 0;
      } else if (quantity.endsWith(__('Ki'))) {
        return parseInt(quantity.substr(0, quantity.length - 2), 10) * 0.000001024;
      } else if (quantity.endsWith(__('Mi'))) {
        return parseInt(quantity.substr(0, quantity.length - 2), 10) * 0.001048576;
      }

      // We are trying to track quantity types coming from Kubernetes.
      // Sentry will notify us if we are missing types.
      throw new Error(`UnknownK8sMemoryQuantity:${quantity}`);
    },
    k8sQuantityToCpu(quantity) {
      if (!quantity) {
        return 0;
      } else if (quantity.endsWith('m')) {
        return parseInt(quantity.substr(0, quantity.length - 1), 10) / 1000.0;
      } else if (quantity.endsWith('n')) {
        return parseInt(quantity.substr(0, quantity.length - 1), 10) / 1000000000.0;
      }

      // We are trying to track quantity types coming from Kubernetes.
      // Sentry will notify us if we are missing types.
      throw new Error(`UnknownK8sCpuQuantity:${quantity}`);
    },
    selectedProvider(provider) {
      return this.providers[provider] || this.providers.default;
    },
    statusTitle(status) {
      const iconTitle = STATUSES[status] || STATUSES.default;
      return sprintf(__('Status: %{title}'), { title: iconTitle.title }, false);
    },
    totalMemoryAndUsage(nodes) {
      try {
        // For EKS node.usage will not be present unless the user manually
        // install the metrics server
        if (nodes && nodes[0].usage) {
          let totalAllocatableMemory = 0;
          let totalUsedMemory = 0;

          nodes.reduce((total, node) => {
            const allocatableMemoryQuantity = node.status.allocatable.memory;
            const allocatableMemoryGb = this.k8sQuantityToGb(allocatableMemoryQuantity);
            totalAllocatableMemory += allocatableMemoryGb;

            const usedMemoryQuantity = node.usage.memory;
            const usedMemoryGb = this.k8sQuantityToGb(usedMemoryQuantity);
            totalUsedMemory += usedMemoryGb;

            return null;
          }, 0);

          const freeSpacePercentage = (1 - totalUsedMemory / totalAllocatableMemory) * 100;

          return {
            totalMemory: totalAllocatableMemory.toFixed(2),
            freeSpacePercentage: Math.round(freeSpacePercentage),
          };
        }
      } catch (error) {
        this.reportSentryError({ error, tag: 'totalMemoryAndUsageError' });
      }

      return { totalMemory: null, freeSpacePercentage: null };
    },
    totalCpuAndUsage(nodes) {
      try {
        // For EKS node.usage will not be present unless the user manually
        // install the metrics server
        if (nodes && nodes[0].usage) {
          let totalAllocatableCpu = 0;
          let totalUsedCpu = 0;

          nodes.reduce((total, node) => {
            const allocatableCpuQuantity = node.status.allocatable.cpu;
            const allocatableCpu = this.k8sQuantityToCpu(allocatableCpuQuantity);
            totalAllocatableCpu += allocatableCpu;

            const usedCpuQuantity = node.usage.cpu;
            const usedCpuGb = this.k8sQuantityToCpu(usedCpuQuantity);
            totalUsedCpu += usedCpuGb;

            return null;
          }, 0);

          const freeSpacePercentage = (1 - totalUsedCpu / totalAllocatableCpu) * 100;

          return {
            totalCpu: totalAllocatableCpu.toFixed(2),
            freeSpacePercentage: Math.round(freeSpacePercentage),
          };
        }
      } catch (error) {
        this.reportSentryError({ error, tag: 'totalCpuAndUsageError' });
      }

      return { totalCpu: null, freeSpacePercentage: null };
    },
  },
};
</script>

<template>
  <gl-loading-icon v-if="loadingClusters" size="md" />

  <section v-else>
    <ancestor-notice />

    <gl-table
      v-if="hasClusters"
      :items="clusters"
      :fields="fields"
      fixed
      stacked="md"
      head-variant="white"
      thead-class="gl-border-b-solid gl-border-b-2 gl-border-b-gray-100"
      class="qa-clusters-table gl-mb-4!"
      data-testid="cluster_list_table"
    >
      <template #cell(name)="{ item }">
        <div :class="[contentAlignClasses, 'js-status']">
          <img
            :src="selectedProvider(item.provider_type).path"
            :alt="selectedProvider(item.provider_type).text"
            class="gl-w-6 gl-h-6 gl-display-flex gl-align-items-center"
          />

          <gl-link
            data-qa-selector="cluster"
            :data-qa-cluster-name="item.name"
            :href="item.path"
            class="gl-px-3"
          >
            {{ item.name }}
          </gl-link>

          <gl-loading-icon
            v-if="item.status === 'deleting' || item.status === 'creating'"
            v-gl-tooltip
            :title="statusTitle(item.status)"
            size="sm"
          />
        </div>
      </template>

      <template #cell(node_size)="{ item }">
        <span v-if="item.nodes">{{ item.nodes.length }}</span>

        <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />

        <node-error-help-text
          v-else-if="item.kubernetes_errors"
          :class="contentAlignClasses"
          :error-type="item.kubernetes_errors.connection_error"
          :popover-id="`nodeSizeError${item.id}`"
        />
      </template>

      <template #cell(total_cpu)="{ item }">
        <span v-if="item.nodes">
          <gl-sprintf :message="$options.nodeCpuText">
            <template #totalCpu>{{ totalCpuAndUsage(item.nodes).totalCpu }}</template>
            <template #freeSpacePercentage>{{
              totalCpuAndUsage(item.nodes).freeSpacePercentage
            }}</template>
            <template #percentSymbol>%</template>
          </gl-sprintf>
        </span>

        <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />

        <node-error-help-text
          v-else-if="item.kubernetes_errors"
          :class="contentAlignClasses"
          :error-type="item.kubernetes_errors.node_connection_error"
          :popover-id="`nodeCpuError${item.id}`"
        />
      </template>

      <template #cell(total_memory)="{ item }">
        <span v-if="item.nodes">
          <gl-sprintf :message="$options.nodeMemoryText">
            <template #totalMemory>{{ totalMemoryAndUsage(item.nodes).totalMemory }}</template>
            <template #freeSpacePercentage>{{
              totalMemoryAndUsage(item.nodes).freeSpacePercentage
            }}</template>
            <template #percentSymbol>%</template>
          </gl-sprintf>
        </span>

        <gl-skeleton-loading v-else-if="loadingNodes" :lines="1" :class="contentAlignClasses" />

        <node-error-help-text
          v-else-if="item.kubernetes_errors"
          :class="contentAlignClasses"
          :error-type="item.kubernetes_errors.metrics_connection_error"
          :popover-id="`nodeMemoryError${item.id}`"
        />
      </template>

      <template #cell(cluster_type)="{ value }">
        <gl-badge variant="muted">
          {{ value }}
        </gl-badge>
      </template>
    </gl-table>

    <clusters-empty-state v-else :is-child-component="isChildComponent" />

    <gl-pagination
      v-if="hasClustersPerPage && !limit"
      v-model="currentPage"
      :per-page="clustersPerPage"
      :total-items="totalClusters"
      :prev-text="__('Prev')"
      :next-text="__('Next')"
      align="center"
    />
  </section>
</template>
