import { CustomObjectsApi, NetworkingV1Api, CoreV1Api, ApiextensionsV1Api } from "@kubernetes/client-node";

import getKubeArguments from "utils/config/kubernetes";
import createLogger from "utils/logger";

const logger = createLogger("service-helpers");

const kubeArguments = getKubeArguments();
const kc = kubeArguments.config;

const apiGroup = "gateway.networking.k8s.io";
const version = "v1";

let crd;
let core;
let networking;
let routingType;
let traefik;

export async function checkCRD(name) {
  const apiExtensions = kc.makeApiClient(ApiextensionsV1Api);
  const exist = await apiExtensions
    .readCustomResourceDefinitionStatus(name)
    .then(() => true)
    .catch(async (error) => {
      if (error.statusCode === 403) {
        logger.error(
          "Error checking if CRD %s exists. Make sure to add the following permission to your RBAC: %d %s %s",
          name,
          error.statusCode,
          error.body.message,
        );
      }
      return false;
    });

  return exist;
}

const getSchemaFromGateway = async (gatewayRef) => {
  const schema = await crd
    .getNamespacedCustomObject(apiGroup, version, gatewayRef.namespace, "gateways", gatewayRef.name)
    .then((response) => {
      const listner = response.body.spec.listeners.filter((listener) => listener.name === gatewayRef.sectionName)[0];
      return listner.protocol.toLowerCase();
    })
    .catch((error) => {
      logger.error("Error getting gateways: %d %s %s", error.statusCode, error.body, error.response);
      logger.debug(error);
      return "";
    });
  return schema;
};

async function getUrlFromHttpRoute(ingress) {
  const urlHost = ingress.spec.hostnames[0];
  const urlPath = ingress.spec.rules[0].matches[0].path.value;
  const urlSchema = (await getSchemaFromGateway(ingress.spec.parentRefs[0])) ? "https" : "http";
  return `${urlSchema}://${urlHost}${urlPath}`;
}

function getUrlFromIngress(ingress) {
  const urlHost = ingress.spec.rules[0].host;
  const urlPath = ingress.spec.rules[0].http.paths[0].path;
  const urlSchema = ingress.spec.tls ? "https" : "http";
  return `${urlSchema}://${urlHost}${urlPath}`;
}

async function getHttpRouteList() {
  // httproutes
  const getHttpRoute = async (namespace) =>
    crd
      .listNamespacedCustomObject(apiGroup, version, namespace, "httproutes")
      .then((response) => {
        const [httpRoute] = response.body.items;
        return httpRoute;
      })
      .catch((error) => {
        logger.error("Error getting httproutes: %d %s %s", error.statusCode, error.body, error.response);
        logger.debug(error);
        return null;
      });

  // namespaces
  const namespaces = await core
    .listNamespace()
    .then((response) => response.body.items.map((ns) => ns.metadata.name))
    .catch((error) => {
      logger.error("Error getting namespaces: %d %s %s", error.statusCode, error.body, error.response);
      logger.debug(error);
      return null;
    });

  let httpRouteList = [];
  if (namespaces) {
    const httpRouteListUnfiltered = await Promise.all(
      namespaces.map(async (namespace) => {
        const httpRoute = await getHttpRoute(namespace);
        return httpRoute;
      }),
    );

    httpRouteList = httpRouteListUnfiltered.filter((httpRoute) => httpRoute !== undefined);
  }
  return httpRouteList;
}

async function getIngressList(ANNOTATION_BASE) {
  const ingressList = await networking
    .listIngressForAllNamespaces(null, null, null, null)
    .then((response) => response.body)
    .catch((error) => {
      logger.error("Error getting ingresses: %d %s %s", error.statusCode, error.body, error.response);
      logger.debug(error);
      return null;
    });

  if (traefik) {
    const traefikContainoExists = await checkCRD("ingressroutes.traefik.containo.us");
    const traefikExists = await checkCRD("ingressroutes.traefik.io");

    const traefikIngressListContaino = await crd
      .listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes")
      .then((response) => response.body)
      .catch(async (error) => {
        if (traefikContainoExists) {
          logger.error(
            "Error getting traefik ingresses from traefik.containo.us: %d %s %s",
            error.statusCode,
            error.body,
            error.response,
          );
          logger.debug(error);
        }

        return [];
      });

    const traefikIngressListIo = await crd
      .listClusterCustomObject("traefik.io", "v1alpha1", "ingressroutes")
      .then((response) => response.body)
      .catch(async (error) => {
        if (traefikExists) {
          logger.error(
            "Error getting traefik ingresses from traefik.io: %d %s %s",
            error.statusCode,
            error.body,
            error.response,
          );
          logger.debug(error);
        }

        return [];
      });

    const traefikIngressList = [...(traefikIngressListContaino?.items ?? []), ...(traefikIngressListIo?.items ?? [])];

    if (traefikIngressList.length > 0) {
      const traefikServices = traefikIngressList.filter(
        (ingress) => ingress.metadata.annotations && ingress.metadata.annotations[`${ANNOTATION_BASE}/href`],
      );
      ingressList.items.push(...traefikServices);
    }
  }

  return ingressList.items;
}

export async function getRouteList(ANNOTATION_BASE) {
  let routeList = [];

  if (!kc) {
    return [];
  }

  crd = kc.makeApiClient(CustomObjectsApi);
  core = kc.makeApiClient(CoreV1Api);
  networking = kc.makeApiClient(NetworkingV1Api);

  routingType = kubeArguments.route;
  traefik = kubeArguments.traefik;

  switch (routingType) {
    case "ingress":
      routeList = await getIngressList(ANNOTATION_BASE);
      break;
    case "gateway":
      routeList = await getHttpRouteList();
      break;
    default:
      routeList = await getIngressList(ANNOTATION_BASE);
  }

  return routeList;
}

export async function getUrlSchema(route) {
  let urlSchema;

  switch (routingType) {
    case "ingress":
      urlSchema = getUrlFromIngress(route);
      break;
    case "gateway":
      urlSchema = await getUrlFromHttpRoute(route);
      break;
    default:
      urlSchema = getUrlFromIngress(route);
  }
  return urlSchema;
}
