// Copyright 2017 PingCAP, 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,
// See the License for the specific language governing permissions and
// limitations under the License.package spec

package controller

import (
	"fmt"

	"github.com/ngaut/log"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap.com/v1"
	"github.com/pingcap/tidb-operator/pkg/util/errors"
	"github.com/pingcap/tidb-operator/pkg/util/label"
	appsv1beta1 "k8s.io/api/apps/v1beta1"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/intstr"
)

func (tcc *TiDBClusterController) syncPrivilegedTiDBFor(cluster *v1.TidbCluster) error {
	ns := cluster.GetNamespace()
	clusterName := cluster.GetName()

	opts := metav1.ListOptions{
		LabelSelector: labels.SelectorFromSet(
			label.New().Cluster(clusterName).TiDB().Labels(),
		).String(),
	}
	podList, err := tcc.kubeCli.CoreV1().Pods(ns).List(opts)
	if err != nil {
		return errors.Logf(err, "failed to list tidb pods in [%s/%s]", ns, clusterName)
	}
	tidbStarted := false
	for _, pod := range podList.Items {
		if pod.Status.Phase == corev1.PodRunning {
			tidbStarted = true
			break
		}
	}
	if !tidbStarted {
		log.Infof("tidb not started, skip syncing privileged-tidb in [%s/%s]", ns, clusterName)
		return nil
	}
	if err := tcc.syncPrivilegedTiDBDeployment(cluster); err != nil {
		return errors.Logf(err, "failed to sync privileged-tidb deployment %s/%s", ns, clusterName)
	}
	if err := tcc.syncPrivilegedTiDBService(cluster); err != nil {
		return errors.Logf(err, "failed to sync privileged-tidb service %s/%s", ns, clusterName)
	}
	return nil
}

// TODO(tennix): add nodeselector
func (tcc *TiDBClusterController) syncPrivilegedTiDBDeployment(cluster *v1.TidbCluster) error {
	ns := cluster.GetNamespace()
	clusterName := cluster.GetName()
	deployName := fmt.Sprintf("%s-privileged-tidb", clusterName)
	deployments := tcc.kubeCli.AppsV1beta1().Deployments(ns)

	deploy, deployErr := deployments.Get(deployName, metav1.GetOptions{})
	if deployErr != nil {
		if !apierrors.IsNotFound(deployErr) {
			return errors.Logf(deployErr, "failed to get privileged-tidb deployment in [%s/%s]", ns, clusterName)
		}
		deployLabel := label.New().Cluster(clusterName).App("privileged-tidb").Labels()
		cmd := []string{"/entrypoint.sh"}
		args := []string{
			"--run-ddl=false",
			"--store=tikv",
			"--host=0.0.0.0",
			fmt.Sprintf("--path=%s-pd:2379", clusterName),
			"--config=/etc/tidb/tidb.toml",
		}
		deploy = &appsv1beta1.Deployment{
			ObjectMeta: metav1.ObjectMeta{
				Name:            deployName,
				Namespace:       ns,
				Labels:          deployLabel,
				OwnerReferences: []metav1.OwnerReference{getOwnerRef(cluster)},
			},
			Spec: appsv1beta1.DeploymentSpec{
				Replicas: &cluster.Spec.PrivilegedTiDB.Size,
				Template: corev1.PodTemplateSpec{
					ObjectMeta: metav1.ObjectMeta{
						Name:   deployName,
						Labels: deployLabel,
					},
					Spec: corev1.PodSpec{
						Containers: []corev1.Container{
							{
								Name:    "privileged-tidb",
								Image:   cluster.Spec.PrivilegedTiDB.Image,
								Command: cmd,
								Args:    args,
								Ports: []corev1.ContainerPort{
									{
										Name:          "mysql-client",
										ContainerPort: int32(4000),
										Protocol:      corev1.ProtocolTCP,
									},
								},
								VolumeMounts: []corev1.VolumeMount{
									{
										Name:      "timezone",
										ReadOnly:  true,
										MountPath: "/etc/localtime",
									},
									{
										Name:      "config",
										ReadOnly:  true,
										MountPath: "/etc/tidb",
									},
								},
							},
						},
						RestartPolicy: corev1.RestartPolicyAlways,
						Volumes: []corev1.Volume{
							{
								Name:         "timezone",
								VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/etc/localtime"}},
							},
							{
								Name: "config",
								VolumeSource: corev1.VolumeSource{
									ConfigMap: &corev1.ConfigMapVolumeSource{
										LocalObjectReference: corev1.LocalObjectReference{Name: cluster.GetConfigMapName()},
										Items:                []corev1.KeyToPath{{Key: "privileged-tidb-config", Path: "tidb.toml"}},
									},
								},
							},
						},
					},
				},
			},
		}
		if _, err := deployments.Create(deploy); err != nil {
			return errors.Logf(err, "failed to create privileged-tidb deployment %s/%s", ns, deployName)
		}
	}
	if deploy.Spec.Replicas != &cluster.Spec.PrivilegedTiDB.Size ||
		deploy.Spec.Template.Spec.Containers[0].Image != cluster.Spec.PrivilegedTiDB.Image {
		if _, err := deployments.Update(deploy); err != nil {
			return errors.Logf(err, "failed to update privileged-tidb deployment %s/%s", ns, deployName)
		}
		log.Infof("privileged-tidb deployment %s/%s updated successfully", ns, deployName)
		return nil
	}
	return nil
}

func (tcc *TiDBClusterController) syncPrivilegedTiDBService(cluster *v1.TidbCluster) error {
	ns := cluster.GetNamespace()
	clusterName := cluster.GetName()
	svcName := fmt.Sprintf("%s-privileged-tidb", clusterName)
	svcType := getServiceType(cluster.Spec.Services, "privileged-tidb")
	services := tcc.kubeCli.CoreV1().Services(ns)

	svc, svcErr := services.Get(svcName, metav1.GetOptions{})
	if svcErr != nil {
		if !apierrors.IsNotFound(svcErr) {
			return errors.Logf(svcErr, "failed to get privileged-tidb service %s/%s", ns, svcName)
		}
		svcLabel := label.New().Cluster(clusterName).App("privileged-tidb").Labels()
		svc = &corev1.Service{
			ObjectMeta: metav1.ObjectMeta{
				Name:            svcName,
				Namespace:       ns,
				Labels:          svcLabel,
				OwnerReferences: []metav1.OwnerReference{getOwnerRef(cluster)},
			},
			Spec: corev1.ServiceSpec{
				Type: svcType,
				Ports: []corev1.ServicePort{
					{
						Name:       "mysql-client",
						Port:       4000,
						TargetPort: intstr.FromInt(4000),
						Protocol:   corev1.ProtocolTCP,
					},
				},
				Selector: svcLabel,
			},
		}
		if _, err := services.Create(svc); err != nil && !apierrors.IsAlreadyExists(err) {
			return errors.Logf(err, "failed to create service %s/%s", ns, svcName)
		}
	}
	if svc.Spec.Type != svcType {
		svc.Spec.Type = svcType
		_, err := tcc.kubeCli.CoreV1().Services(ns).Update(svc)
		if err != nil {
			return errors.Logf(err, "failed to update Service[%s/%s]", ns, svcName)
		}
		log.Infof("successfully updated Service[%s/%s]", ns, svcName)
	}
	return nil
}
