/*
 * Copyright © 2020 Atomist, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { logger } from "@atomist/automation-client/lib/util/logger";
import * as k8s from "@kubernetes/client-node";
import * as _ from "lodash";
import { k8sErrMsg } from "../support/error";
import { logRetry } from "../support/retry";
import { applicationLabels } from "./labels";
import { metadataTemplate } from "./metadata";
import { patchHeaders } from "./patch";
import {
    appName,
    KubernetesApplication,
    KubernetesResourceRequest,
    KubernetesSdm,
} from "./request";
import { logObject } from "./resource";

/**
 * If `req.port` and `req.path` are truthy, create or patch an ingress
 * for a Kubernetes application.  Any provided `req.ingressSpec` is
 * merged using [[ingressTemplate]] before creating/patching.
 *
 * @param req Kuberenetes resource request
 * @return Kubernetes spec used to create/patch resource
 */
export async function upsertIngress(req: KubernetesResourceRequest): Promise<k8s.NetworkingV1beta1Ingress | undefined> {
    const slug = appName(req);
    if (!req.port) {
        logger.debug(`Port not provided, will not create ingress ${slug}`);
        return undefined;
    }
    if (!req.path) {
        logger.debug(`Path not provided, will not upsert ingress ${slug}`);
        return undefined;
    }
    const spec = await ingressTemplate(req);
    try {
        await req.clients.net.readNamespacedIngress(spec.metadata.name, spec.metadata.namespace);
    } catch (e) {
        logger.debug(`Failed to read ingress ${slug}, creating: ${k8sErrMsg(e)}`);
        logger.info(`Creating ingress ${slug} using '${logObject(spec)}'`);
        await logRetry(
            () => req.clients.net.createNamespacedIngress(spec.metadata.namespace, spec),
            `create ingress ${slug}`,
        );
        return spec;
    }
    logger.info(`Ingress ${slug} exists, patching using '${logObject(spec)}'`);
    await logRetry(
        () =>
            req.clients.net.patchNamespacedIngress(
                spec.metadata.name,
                spec.metadata.namespace,
                spec,
                undefined,
                undefined,
                undefined,
                undefined,
                patchHeaders(req),
            ),
        `patch ingress ${slug}`,
    );
    return spec;
}

/**
 * Create an ingress HTTP path.
 *
 * @param req ingress request
 * @return ingress HTTP path
 */
function httpIngressPath(req: KubernetesApplication): k8s.NetworkingV1beta1HTTPIngressPath {
    const httpPath: k8s.NetworkingV1beta1HTTPIngressPath = {
        path: req.path,
        backend: {
            serviceName: req.name,
            servicePort: ("http" as any) as object,
        },
    };
    return httpPath;
}

/**
 * Create the ingress for a deployment namespace.  If the
 * request has an `ingressSpec`, it is merged into the spec created
 * by this function using `lodash.merge(default, req.ingressSpec)`.
 *
 * It is possible to override the ingress name using the
 * [[KubernetesApplication.ingressSpec]].  If you do this, make sure
 * you know what you are doing.
 *
 * @param req Kubernestes application
 * @return ingress spec with single rule
 */
export async function ingressTemplate(
    req: KubernetesApplication & KubernetesSdm,
): Promise<k8s.NetworkingV1beta1Ingress> {
    const labels = applicationLabels(req);
    const metadata = metadataTemplate({
        name: req.name,
        namespace: req.ns,
        labels,
    });
    const httpPath = httpIngressPath(req);
    const rule: k8s.NetworkingV1beta1IngressRule = {
        http: {
            paths: [httpPath],
        },
    } as any;
    const apiVersion = "networking.k8s.io/v1beta1";
    const kind = "Ingress";
    const i: k8s.NetworkingV1beta1Ingress = {
        apiVersion,
        kind,
        metadata,
        spec: {
            rules: [rule],
        },
    };
    if (req.ingressSpec) {
        _.merge(i, req.ingressSpec, { apiVersion, kind });
        i.metadata.namespace = req.ns;
    }
    return i;
}
