import { dump } from 'js-yaml';
import { HostStaticNetworkConfig, InfraEnv } from '../../../../../common';
import {
  ProtocolVersion,
  FormViewHost,
  FormViewNetworkWideValues,
  StaticFormData,
  HostIps,
} from './dataTypes';
import {
  FORM_VIEW_PREFIX,
  getDnsSection,
  getEthernetInterface,
  getMachineNetworkFieldName,
  getNmstateProtocolConfig,
  getRouteConfig,
  getVlanInterface,
  YAML_COMMENT_CHAR,
} from './nmstateYaml';
import { getShownProtocolVersions } from './protocolVersion';
import {
  Nmstate,
  NmstateInterface,
  NmstateProtocolConfigs,
  NmstateRoutesConfig,
} from './nmstateTypes';
import { formDataFromInfraEnvField } from './formDataFromInfraEnvField';
import { getStaticNetworkConfig } from './fromInfraEnv';
import { DUMMY_NMSTATE_ADDRESSES, getDummyMacInterfaceMap, getDummyNicName } from './dummyData';
import { getMachineNetworkCidr } from './machineNetwork';
import { getEmptyHostIps } from './emptyData';

const REAL_NIC_NAME = 'eth0';

const getPrefixLength = (
  networkWide: FormViewNetworkWideValues,
  protocolVersion: ProtocolVersion,
): number => {
  const machineNetwork = networkWide.ipConfigs[protocolVersion].machineNetwork;
  if (machineNetwork.prefixLength === '') {
    throw 'Machine network prefix length not configured';
  }
  return machineNetwork.prefixLength;
};

const toYamlWithComments = (json: object, comments: string[]) => {
  const yamlComments = comments.map((comment) => `${YAML_COMMENT_CHAR}${comment}`);
  return `${yamlComments.join('\n')}\n${dump(json)}`;
};

const getNmstateObject = (
  networkWide: FormViewNetworkWideValues,
  hostIps: Partial<HostIps>,
): Nmstate => {
  const interfaces: NmstateInterface[] = [];
  const routeConfigs: NmstateRoutesConfig[] = [];
  const dns = getDnsSection(networkWide.ipConfigs, networkWide.protocolType);
  const realProtocolConfigs: NmstateProtocolConfigs = {};
  for (const protocolVersion of getShownProtocolVersions(networkWide.protocolType)) {
    const hostIp = hostIps[protocolVersion];
    let nicName = '';
    if (hostIp) {
      nicName = REAL_NIC_NAME;
      realProtocolConfigs[protocolVersion] = getNmstateProtocolConfig(
        hostIp,
        getPrefixLength(networkWide, protocolVersion),
      );
    } else {
      //this happens when a host was particall configured, or not configured at all
      const protocolConfigs = {
        [protocolVersion]: getNmstateProtocolConfig(
          DUMMY_NMSTATE_ADDRESSES[protocolVersion].ip,
          DUMMY_NMSTATE_ADDRESSES[protocolVersion].prefixLength,
        ),
      };
      nicName = getDummyNicName(protocolVersion);
      interfaces.push(getEthernetInterface(nicName, protocolConfigs));
    }
    routeConfigs.push(
      getRouteConfig(protocolVersion, networkWide.ipConfigs[protocolVersion].gateway, nicName),
    );
  }
  if (Object.keys(realProtocolConfigs).length > 0) {
    interfaces.unshift(getEthernetInterface(REAL_NIC_NAME, realProtocolConfigs));
  }
  if (networkWide.useVlan && networkWide.vlanId) {
    interfaces.push(getVlanInterface(interfaces[0].name, networkWide.vlanId));
  }
  const nmstate = {
    interfaces,
    'dns-resolver': dns,
    routes: { config: routeConfigs },
  };
  return nmstate;
};

const toYaml = (
  networkWideConfiguration: FormViewNetworkWideValues,
  formHostData?: FormViewHost,
): string => {
  const comments = [FORM_VIEW_PREFIX];
  for (const protocolVersion of getShownProtocolVersions(networkWideConfiguration.protocolType)) {
    comments.push(
      `${getMachineNetworkFieldName(protocolVersion)} ${getMachineNetworkCidr(
        networkWideConfiguration.ipConfigs[protocolVersion].machineNetwork,
      )}`,
    );
  }
  const hostIps = formHostData ? formHostData.ips : getEmptyHostIps();
  const nmstate = getNmstateObject(networkWideConfiguration, hostIps);
  return toYamlWithComments(nmstate, comments);
};

const formDataToInfraEnvField = (formData: StaticFormData): HostStaticNetworkConfig[] => {
  let ret: HostStaticNetworkConfig[] = [];
  if (formData.hosts.length === 0) {
    //user only filled in network wide configuration and didn't fill in host specific configurations
    ret = [
      {
        networkYaml: toYaml(formData.networkWide),
        macInterfaceMap: getDummyMacInterfaceMap(),
      },
    ];
  } else {
    for (const host of formData.hosts) {
      ret.push({
        networkYaml: toYaml(formData.networkWide, host),
        macInterfaceMap: [
          {
            macAddress: host.macAddress,
            logicalNicName: REAL_NIC_NAME,
          },
        ],
      });
    }
  }
  return ret;
};

export const formViewHostsToInfraEnvField = (
  currentInfraEnv: InfraEnv,
  formViewHosts: FormViewHost[],
): HostStaticNetworkConfig[] => {
  const staticNetworkConfig = getStaticNetworkConfig(currentInfraEnv);
  if (!staticNetworkConfig) {
    throw `Infra env doesn't contain static ip values`;
  }
  const currentFormData = formDataFromInfraEnvField(staticNetworkConfig);
  const networkWide = currentFormData.networkWide;
  return formDataToInfraEnvField({
    networkWide: networkWide,
    hosts: formViewHosts,
  });
};

const fixHostIps = (networkWide: FormViewNetworkWideValues, hosts: FormViewHost[]) => {
  //fix host ips following changes in network wide configuration protocol type
  for (const host of hosts) {
    const newHostIps = getEmptyHostIps();
    for (const protocolVersion of getShownProtocolVersions(networkWide.protocolType)) {
      newHostIps[protocolVersion] = host.ips[protocolVersion];
    }
    host.ips = newHostIps;
  }
};

export const networkWideToInfraEnvField = (
  currentInfraEnv: InfraEnv,
  networkWide: FormViewNetworkWideValues,
): HostStaticNetworkConfig[] => {
  const staticNetworkConfig = getStaticNetworkConfig(currentInfraEnv);
  if (!staticNetworkConfig) {
    throw `Infra env doesn't contain static ip values`;
  }
  const currentFormData = formDataFromInfraEnvField(staticNetworkConfig);
  fixHostIps(networkWide, currentFormData.hosts);
  return formDataToInfraEnvField({ networkWide, hosts: currentFormData.hosts });
};