package util

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"k8s.io/api/core/v1"
	"k8s.io/api/extensions/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

// CreateK8sNamespace creates namespaces
func CreateK8sNamespace(namespace string, k8sCli kubernetes.Interface) error {
	namespace = strings.ToLower(FormatSnakeCase(namespace))

	ns := &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: namespace,
		},
	}

	_, err := k8sCli.CoreV1().Namespaces().Create(ns)
	return errors.Trace(err)
}

// DeleteK8sNamespace deletes namespaces
func DeleteK8sNamespace(namespace string, k8sCli kubernetes.Interface) error {
	return errors.Trace(k8sCli.CoreV1().Namespaces().Delete(namespace, &metav1.DeleteOptions{}))
}

// CheckNamespaceIsExist checks whether namespace exists
func CheckNamespaceIsExist(namespace string, k8sCli kubernetes.Interface) (bool, error) {
	_, err := k8sCli.CoreV1().Namespaces().Get(namespace, metav1.GetOptions{})
	if err == nil {
		return true, err
	}
	if strings.Contains(err.Error(), "not found") {
		return false, nil
	}

	return false, err
}

// GetPod returns pod
func GetPod(namespace, name string, k8sCli kubernetes.Interface) (*v1.Pod, error) {
	return k8sCli.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
}

// CreatePod creates pod
func CreatePod(namespace string, k8sCli kubernetes.Interface, pod *v1.Pod) (*v1.Pod, error) {
	pod, err := k8sCli.CoreV1().Pods(namespace).Create(pod)
	if err != nil {
		return nil, errors.Trace(err)
	}
	return pod, nil
}

// DeletePod deletes pod
func DeletePod(namespace string, name string, k8sCli kubernetes.Interface) error {
	return errors.Trace(k8sCli.CoreV1().Pods(namespace).Delete(FormatSnakeCase(name), &metav1.DeleteOptions{}))
}

// CheckPodIsRunning waits util pod running
func CheckPodIsRunning(ctx context.Context, namespace, name string, k8sCli kubernetes.Interface) bool {
	timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Minute)
	defer cancel()

	for {
		select {
		case <-timeoutCtx.Done():
			return false
		default:
		}
		pod, err := k8sCli.CoreV1().Pods(namespace).Get(FormatSnakeCase(name), metav1.GetOptions{})
		if err != nil {
			log.Errorf("get [namespace: %s][pod: %s] failed: %v", namespace, name, err)
		} else {
			log.Infof("[namespace: %s][pod: %s] state is: %s", namespace, name, pod.Status.String())
			if pod.Status.Phase == v1.PodRunning {
				return true
			}
		}
		time.Sleep(10 * time.Second)
	}
}

// CreateDaemonSet creates daemonset
func CreateDaemonSet(namespace string, cli kubernetes.Interface, set *v1beta1.DaemonSet) error {
	_, err := cli.ExtensionsV1beta1().DaemonSets(namespace).Create(set)
	return errors.Trace(err)
}

// DeleteDaemonSet deletes daemonset
func DeleteDaemonSet(namespace, name string, cli kubernetes.Interface) error {
	return errors.Trace(cli.Apps().DaemonSets(namespace).Delete(name, &metav1.DeleteOptions{}))
}

// ListPods lists all pods according to listOptions
func ListPods(namespace string, cli kubernetes.Interface, option metav1.ListOptions) (*v1.PodList, error) {
	return cli.CoreV1().Pods(namespace).List(option)
}

// GetPodLog logs return last logs for pods
func GetPodLog(namespace string, cli kubernetes.Interface, name string, opt *v1.PodLogOptions) ([]byte, error) {
	return cli.CoreV1().Pods(namespace).GetLogs(name, opt).Do().Raw()
}

const (
	// AnnProvisionerPodName is the annotation key of PVC stands for podName
	AnnProvisionerPodName = "volume.pingcap.com/podName"

	// AnnProvisionerHostPath is the annotation key of PVC stands for hostPath
	AnnProvisionerHostPath = "volume-provisioner.pingcap.com/hostpath"
)

// GetPodDataDir returns dataDir of pod in k8s
func GetPodDataDir(namespace, podName string, cli kubernetes.Interface) (string, error) {
	pvcList, err := cli.CoreV1().PersistentVolumeClaims(namespace).List(metav1.ListOptions{})
	if err != nil {
		log.Errorf("failed to list PVC: %v", err)
		return "", errors.Trace(err)
	}

	for _, pvc := range pvcList.Items {
		pvcPodName, ok := pvc.Annotations[AnnProvisionerPodName]
		if !ok {
			return "", errors.NotValidf("AnnProvisionerPodName :%s", AnnProvisionerPodName)
		}

		if pvcPodName != "" && pvcPodName != podName {
			continue
		}

		dataDir, ok := pvc.Annotations[AnnProvisionerHostPath]
		if !ok {
			return "", errors.NotValidf("AnnProvisionerHostPath :%s", AnnProvisionerHostPath)
		}

		return dataDir, nil
	}

	return "", nil
}

// GetServiceAddr returns service addr
func GetServiceAddr(namespace, name string, cli kubernetes.Interface) (string, error) {
	svc, err := cli.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
	if err != nil {
		return "", errors.Trace(err)
	}
	if len(svc.Spec.Ports) < 1 {
		return "", errors.NotValidf("service %v", svc)
	}

	addr := fmt.Sprintf("%s:%d", svc.Spec.ClusterIP, svc.Spec.Ports[0].Port)
	return addr, nil
}
