package main

import (
	"code.fbi.h-da.de/cocsn/gosdn/nucleus"
	"context"
	appv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"k8s.io/client-go/util/homedir"
	"log"
	"os"
	"path/filepath"
)

func main() {
	kubeconfig, err := clientcmd.BuildConfigFromFlags("", filepath.Join(homedir.HomeDir(), ".kube", "config"))
	if err != nil {
		log.Fatal(err)
	}
	clientset, err := kubernetes.NewForConfig(kubeconfig)
	if err != nil {
		log.Fatal(err)
	}
	var tag string
	switch os.Getenv("CI_COMMIT_BRANCH") {
	case "master":
		tag = "latest"
	case "develop":
		tag = "develop"
	default:
		tag = os.Getenv("CI_COMMIT_SHA")
	}
	env := "gosdn-" + tag
	service := createService(env)
	config := createConfigMap(env)
	deployment := createDeployment(env, tag)
	switch os.Getenv("K8S_OP") {
	case "create":
		if err := create(clientset, service, config, deployment, env); err != nil {
			log.Println(err)
		}
	case "delete":
		if err := remove(clientset, env); err != nil {
			log.Println(err)
		}
	case "update":
		if err := update(clientset, service, env); err != nil {
			log.Println(err)
		}
		if err := update(clientset, config, env); err != nil {
			log.Println(err)
		}
		if err := update(clientset, deployment, env); err != nil {
			log.Println(err)
		}
	default:
		log.Fatal("invalid option")
	}
}

func create(clientset *kubernetes.Clientset, service *corev1.Service, config *corev1.ConfigMap, deployment *appv1.Deployment, env string) error {
	opts := metav1.CreateOptions{}
	ctx := context.Background()
	_, err := clientset.CoreV1().Services("cocsn").Create(ctx, service, opts)
	if err != nil {
		switch err.(type) {
		case *errors.StatusError:
			if err.(*errors.StatusError).ErrStatus.Code == 409 {
				err = update(clientset, service, env)
			}
		default:
			return err
		}
	}
	_, err = clientset.CoreV1().ConfigMaps("cocsn").Create(ctx, config, opts)
	if err != nil {
		switch err.(type) {
		case *errors.StatusError:
			if err.(*errors.StatusError).ErrStatus.Code == 409 {
				err = update(clientset, config, env)
			}
		default:
			return err
		}
	}
	_, err = clientset.AppsV1().Deployments("cocsn").Create(ctx, deployment, opts)
	if err != nil {
		switch err.(type) {
		case *errors.StatusError:
			if err.(*errors.StatusError).ErrStatus.Code == 409 {
				err = update(clientset, deployment, env)
			}
		default:
			return err
		}
	}
	return err
}

func update(clientset *kubernetes.Clientset, resource metav1.Common, env string) error {
	opts := metav1.UpdateOptions{}
	getOpts := metav1.GetOptions{}
	ctx := context.Background()
	switch resource.(type) {
	case *corev1.Service:
		service := resource.(*corev1.Service)
		s, err := clientset.CoreV1().Services("cocsn").Get(ctx, env, getOpts)
		if err != nil {
			return err
		}
		s.DeepCopyInto(service)
		_, err = clientset.CoreV1().Services("cocsn").Update(ctx, service, opts)
		if err != nil {
			return err
		}
	case *corev1.ConfigMap:
		config := resource.(*corev1.ConfigMap)
		c, err := clientset.CoreV1().ConfigMaps("cocsn").Get(ctx, env+"-config", getOpts)
		if err != nil {
			return err
		}
		c.DeepCopyInto(config)
		_, err = clientset.CoreV1().ConfigMaps("cocsn").Update(ctx, config, opts)
		if err != nil {
			return err
		}
	case *appv1.Deployment:
		deployment := resource.(*appv1.Deployment)
		d, err := clientset.AppsV1().Deployments("cocsn").Get(ctx, env, getOpts)
		if err != nil {
			return err
		}
		d.DeepCopyInto(deployment)
		_, err = clientset.AppsV1().Deployments("cocsn").Update(ctx, deployment, opts)
		if err != nil {
			return err
		}
	default:
		return &nucleus.ErrInvalidParameters{}
	}
	return nil
}

func remove(clientset *kubernetes.Clientset, env string) error {
	opts := metav1.DeleteOptions{}
	ctx := context.Background()
	err := clientset.CoreV1().Services("cocsn").Delete(ctx, env, opts)
	if err != nil {
		log.Println(err)
	}
	err = clientset.CoreV1().ConfigMaps("cocsn").Delete(ctx, env+"-config", opts)
	if err != nil {
		log.Println(err)
	}
	err = clientset.AppsV1().Deployments("cocsn").Delete(ctx, env, opts)
	if err != nil {
		log.Println(err)
	}
	return err
}

func createService(environment string) *corev1.Service {
	return &corev1.Service{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Service",
			APIVersion: "v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      environment,
			Namespace: "cocsn",
			Labels:    map[string]string{"run": environment},
		},
		Spec: corev1.ServiceSpec{
			Ports: []corev1.ServicePort{
				{
					Name:       "http",
					Port:       8080,
					TargetPort: intstr.IntOrString{IntVal: 8080},
				},
				{
					Name:       "grpc",
					Port:       55055,
					TargetPort: intstr.IntOrString{IntVal: 55055},
				},
			},
			Selector:     map[string]string{"run": environment},
			Type:         "NodePort",
			ExternalName: environment + ".apps.ocp.fbi.h-da.de",
		},
	}
}

func createDeployment(environment, hash string) *appv1.Deployment {
	return &appv1.Deployment{
		TypeMeta: metav1.TypeMeta{
			Kind:       "Deployment",
			APIVersion: "apps/v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: environment,
		},
		Spec: appv1.DeploymentSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{"run": environment},
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{"run": environment},
				},
				Spec: corev1.PodSpec{
					Volumes: []corev1.Volume{
						{
							Name: "gosdn-config-volume",
							VolumeSource: corev1.VolumeSource{
								ConfigMap: &corev1.ConfigMapVolumeSource{
									LocalObjectReference: corev1.LocalObjectReference{
										Name: "gosdn-develop-config",
									},
								},
							},
						},
					},
					Containers: []corev1.Container{
						{
							Name:       "gosdn",
							Image:      "registry.code.fbi.h-da.de/cocsn/gosdn:" + hash,
							Command:    nil,
							Args:       nil,
							WorkingDir: "",
							Ports: []corev1.ContainerPort{
								{
									Name:          "grpc",
									ContainerPort: 55055,
								},
								{
									Name:          "http",
									ContainerPort: 8080,
								},
							},
							VolumeMounts: []corev1.VolumeMount{
								{
									Name:      "gosdn-config-volume",
									MountPath: "/usr/local/etc/gosdn/gosdn.toml",
								},
							},
							LivenessProbe: &corev1.Probe{
								Handler: corev1.Handler{
									HTTPGet: &corev1.HTTPGetAction{
										Path: "/livez",
										Port: intstr.IntOrString{IntVal: 8080},
									},
								},
								InitialDelaySeconds: 20,
								PeriodSeconds:       2,
							},
							ReadinessProbe: &corev1.Probe{
								Handler: corev1.Handler{
									HTTPGet: &corev1.HTTPGetAction{
										Path: "/readyz",
										Port: intstr.IntOrString{IntVal: 8080},
									},
								},
								InitialDelaySeconds: 120,
								PeriodSeconds:       2,
							},
							ImagePullPolicy: "Always",
						},
					},
					ImagePullSecrets: []corev1.LocalObjectReference{
						{Name: "k8s-gosdn-test"},
					},
				},
			},
			Strategy: appv1.DeploymentStrategy{
				Type:          "RollingUpdate",
				RollingUpdate: &appv1.RollingUpdateDeployment{},
			},
		},
	}
}

func createConfigMap(env string) *corev1.ConfigMap {
	return &corev1.ConfigMap{
		TypeMeta: metav1.TypeMeta{
			Kind:       "ConfigMap",
			APIVersion: "v1",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: env + "-config",
		},
		Data: map[string]string{"gosdn.toml": "#empty"},
	}
}