import _ from 'lodash-es';

import * as JsonPatch from 'fast-json-patch';
import { KubernetesNode, KubernetesNodeDetails, KubernetesNodeTaint, KubernetesNodeAvailabilities, KubernetesPortainerNodeDrainLabel } from 'Kubernetes/node/models';
import KubernetesResourceReservationHelper from 'Kubernetes/helpers/resourceReservationHelper';
import { KubernetesNodeFormValues, KubernetesNodeTaintFormValues, KubernetesNodeLabelFormValues } from 'Kubernetes/node/formValues';
import { KubernetesNodeCreatePayload, KubernetesNodeTaintPayload } from 'Kubernetes/node/payload';

class KubernetesNodeConverter {
  static apiToNode(data, res) {
    if (!res) {
      res = new KubernetesNode();
    }
    res.Id = data.metadata.uid;
    const hostName = _.find(data.status.addresses, { type: 'Hostname' });
    res.Name = data.metadata.name ? data.metadata.name : hostName.address;
    res.Labels = data.metadata.labels;
    // most kube clusters set control-plane label, older clusters set master, microk8s doesn't have either but instead sets microk8s-controlplane
    let masters = ['node-role.kubernetes.io/control-plane', 'node-role.kubernetes.io/master', 'node.kubernetes.io/microk8s-controlplane'];
    res.Role = _.some(masters, (master) => _.has(data.metadata.labels, master)) ? 'Master' : 'Worker';

    const ready = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.READY });
    const memoryPressure = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.MEMORY_PRESSURE });
    const PIDPressure = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.PID_PRESSURE });
    const diskPressure = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.DISK_PRESSURE });
    const networkUnavailable = _.find(data.status.conditions, { type: KubernetesNodeConditionTypes.NETWORK_UNAVAILABLE });

    res.Conditions = {
      MemoryPressure: memoryPressure && memoryPressure.status === 'True',
      PIDPressure: PIDPressure && PIDPressure.status === 'True',
      DiskPressure: diskPressure && diskPressure.status === 'True',
      NetworkUnavailable: networkUnavailable && networkUnavailable.status === 'True',
    };

    res.Availability = KubernetesNodeAvailabilities.ACTIVE;
    if (data.spec.unschedulable === true) {
      res.Availability = _.has(data.metadata.labels, KubernetesPortainerNodeDrainLabel) ? KubernetesNodeAvailabilities.DRAIN : KubernetesNodeAvailabilities.PAUSE;
    }

    if (ready.status === 'False') {
      res.Status = 'Unhealthy';
    } else if (ready.status === 'Unknown' || res.Conditions.MemoryPressure || res.Conditions.PIDPressure || res.Conditions.DiskPressure || res.Conditions.NetworkUnavailable) {
      res.Status = 'Warning';
    } else {
      res.Status = 'Ready';
    }

    res.CPU = KubernetesResourceReservationHelper.parseCPU(data.status.allocatable.cpu);
    res.Memory = data.status.allocatable.memory;
    res.Version = data.status.nodeInfo.kubeletVersion;
    const internalIP = _.find(data.status.addresses, { type: 'InternalIP' });
    res.IPAddress = internalIP ? internalIP.address : '-';
    res.Taints = _.map(data.spec.taints, (taint) => {
      const res = new KubernetesNodeTaint();
      res.Key = taint.key;
      res.Value = taint.value;
      res.Effect = taint.effect;
      return res;
    });
    return res;
  }

  static apiToNodeDetails(data, yaml) {
    let res = new KubernetesNodeDetails();
    res = KubernetesNodeConverter.apiToNode(data, res);
    res.CreationDate = data.metadata.creationTimestamp;
    res.OS.Architecture = data.status.nodeInfo.architecture;
    res.OS.Platform = data.status.nodeInfo.operatingSystem;
    res.OS.Image = data.status.nodeInfo.osImage;
    res.Yaml = yaml ? yaml.data : '';
    return res;
  }

  static nodeToFormValues(node) {
    const res = new KubernetesNodeFormValues();

    res.Availability = node.Availability;

    res.Taints = _.map(node.Taints, (taint) => {
      const res = new KubernetesNodeTaintFormValues();
      res.Key = taint.Key;
      res.Value = taint.Value;
      res.Effect = taint.Effect;
      res.NeedsDeletion = false;
      res.IsNew = false;
      return res;
    });

    res.Labels = _.map(node.Labels, (value, key) => {
      const res = new KubernetesNodeLabelFormValues();
      res.Key = key;
      res.Value = value;
      res.NeedsDeletion = false;
      res.IsNew = false;
      return res;
    });

    return res;
  }

  static formValuesToNode(node, formValues) {
    const res = angular.copy(node);

    res.Availability = formValues.Availability;

    const filteredTaints = _.filter(formValues.Taints, (taint) => !taint.NeedsDeletion);
    res.Taints = _.map(filteredTaints, (item) => {
      const taint = new KubernetesNodeTaint();
      taint.Key = item.Key;
      taint.Value = item.Value;
      taint.Effect = item.Effect;
      return taint;
    });

    const filteredLabels = _.filter(formValues.Labels, (label) => !label.NeedsDeletion);
    res.Labels = _.reduce(
      filteredLabels,
      (acc, item) => {
        acc[item.Key] = item.Value ? item.Value : '';
        return acc;
      },
      {}
    );

    return res;
  }

  static createPayload(node) {
    const payload = new KubernetesNodeCreatePayload();
    payload.metadata.name = node.Name;

    const taints = _.map(node.Taints, (taint) => {
      const res = new KubernetesNodeTaintPayload();
      res.key = taint.Key;
      res.value = taint.Value;
      res.effect = taint.Effect;
      return res;
    });

    payload.spec.taints = taints.length ? taints : undefined;

    payload.metadata.labels = node.Labels;

    if (node.Availability !== KubernetesNodeAvailabilities.ACTIVE) {
      payload.spec.unschedulable = true;
      if (node.Availability === KubernetesNodeAvailabilities.DRAIN) {
        payload.metadata.labels[KubernetesPortainerNodeDrainLabel] = '';
      } else {
        delete payload.metadata.labels[KubernetesPortainerNodeDrainLabel];
      }
    }

    return payload;
  }

  static patchPayload(oldNode, newNode) {
    const oldPayload = KubernetesNodeConverter.createPayload(oldNode);
    const newPayload = KubernetesNodeConverter.createPayload(newNode);
    const payload = JsonPatch.compare(oldPayload, newPayload);
    return payload;
  }
}

export const KubernetesNodeConditionTypes = Object.freeze({
  READY: 'Ready',
  MEMORY_PRESSURE: 'MemoryPressure',
  PID_PRESSURE: 'PIDPressure',
  DISK_PRESSURE: 'DiskPressure',
  NETWORK_UNAVAILABLE: 'NetworkUnavailable',
});

export default KubernetesNodeConverter;
