package k8s

import (
	"context"
	"fmt"
	"strings"
	"text/template"

	"github.com/juju/errors"
	"github.com/ngaut/log"
	"github.com/pingcap/schrodinger/pkg/types"
	myutil "github.com/pingcap/schrodinger/util"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap.com/v1alpha1"
	"github.com/pingcap/tidb-operator/pkg/label"
	corev1 "k8s.io/api/core/v1"
	apierr "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	configMapTemplate = "tidb-cluster-config-template"
	alertManagerURL   = ""
	tidbLease         = 10

	metaInstance  = "{{ $labels.instance }}"
	metaType      = "{{ $labels.type }}"
	metaValue     = "{{ $value }}"
	leftDelim     = "<%"
	rightDelim    = "%>"
	runModeShell  = "/home/pingcap/runmode.sh -n %s -m %s -a tikv -t tidbcluster tidb-cluster"
	backupSuffix  = "backup"
	tidbCluster   = "tidb-cluster"
	pvcPodName    = "volume.pingcap.com/podName"
	grafanaSecret = "tidb-cluster-secret"
)

const (
	// TiKVStoreDir is the sub directory under hostPath for tikv
	TiKVStoreDir = "data"
	// PDStoreDir is the sub directory under hostPath of pd
	PDStoreDir = "data"
	// PDDataMountPath is the directory for the pd data.
	PDDataMountPath = "/var/lib/pd"
	// TiKVDataMountPath is the directory for the tikv data.
	TiKVDataMountPath = "/var/lib/tikv"
	// BackupStoreDir is the backup directory for storing backup data.
	BackupStoreDir = "backup"
	// HostPathLabelKey is hostpath label key
	HostPathLabelKey = "volume-provisioner.pingcap.com/hostpath"
)

func getName(clusterName, app string) string {
	return fmt.Sprintf("%s-%s", clusterName, app)
}

type pdClusterConfig struct {
	MetricsAddr string
	LogLevel    types.LogLevel
}

type tikvClusterConfig struct {
	MetricsAddr             string
	EndpointConcurrency     int32
	DefaultCFBlockCacheSize string
	WriteCFBlockCacheSize   string
	LogLevel                types.LogLevel
}

type tidbClusterConfigTemplate struct {
	MetricsAddr    string
	TiDBLease      int
	SkipGrantTable bool
	LogLevel       types.LogLevel
}

type prometheusConfig struct {
	AlertmanagerURL string
	Namespace       string
	ClusterName     string
}

type alertConfig struct {
	ClusterName  string
	MetaInstance string
	MetaType     string
	MetaValue    string
}

func (c *ClusterController) createGrafanaSecret(cluster *types.Cluster) error {
	secret, err := c.K8sCli.CoreV1().Secrets(c.Namespace).Get(grafanaSecret, metav1.GetOptions{})
	if err != nil {
		if apierr.IsNotFound(err) {
			log.Warnf("namespace %s secret %s is not found", c.Namespace, grafanaSecret)
			return nil
		}

		return errors.Trace(err)
	}
	secret.Namespace = cluster.Namespace
	newSecret := &corev1.Secret{
		Type: secret.Type,
		TypeMeta: metav1.TypeMeta{
			Kind: secret.Kind,
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      secret.Name,
			Namespace: cluster.Namespace,
		},
		Data: secret.Data,
	}
	if _, err := c.K8sCli.CoreV1().Secrets(cluster.Namespace).Create(newSecret); err != nil {
		if apierr.IsAlreadyExists(err) {
			log.Warnf("namespace %s secret %s is already exist", c.Namespace, grafanaSecret)
			return nil
		}
		return errors.Trace(err)
	}

	return nil
}

func (c *ClusterController) getIPsByLabel(ctx context.Context, namespace, labelVar string) ([]string, error) {
	var ips []string

	pods, err := c.getClusterPodsByLabel(ctx, namespace, labelVar)
	if err != nil {
		return nil, errors.Trace(err)
	}

	for _, pod := range pods.Items {
		ips = append(ips, pod.Status.PodIP)
	}

	return ips, nil
}

func getResourceRequirements(cs *v1alpha1.ContainerSpec) *corev1.ResourceRequirements {
	rr := corev1.ResourceRequirements{}
	if cs.Requests != nil {
		rr.Requests = make(map[corev1.ResourceName]resource.Quantity)
		if q, err := resource.ParseQuantity(cs.Requests.CPU); err != nil {
			log.Errorf("failed to parse CPU resource %s to quantity, error: %v", cs.Requests.CPU, err)
		} else {
			rr.Requests[corev1.ResourceCPU] = q
		}
		if q, err := resource.ParseQuantity(cs.Requests.Memory); err != nil {
			log.Errorf("failed to parse memory resource %s to quantity, error: %v", cs.Requests.Memory, err)
		} else {
			rr.Requests[corev1.ResourceMemory] = q
		}
	}
	if cs.Limits != nil {
		rr.Limits = make(map[corev1.ResourceName]resource.Quantity)
		if q, err := resource.ParseQuantity(cs.Limits.CPU); err != nil {
			log.Errorf("failed to parse CPU resource %s to quantity, error: %v", cs.Limits.CPU, err)
		} else {
			rr.Limits[corev1.ResourceCPU] = q
		}
		if q, err := resource.ParseQuantity(cs.Limits.Memory); err != nil {
			log.Errorf("failed to parse memory resource %s to quantity, error: %v", cs.Limits.Memory, err)
		} else {
			rr.Limits[corev1.ResourceMemory] = q
		}
	}
	return &rr
}

var funcMap = template.FuncMap{
	"indent":  indent,
	"replace": replace,
}

func indent(spaces int, v string) string {
	pad := strings.Repeat(" ", spaces)
	return pad + strings.Replace(v, "\n", "\n"+pad, -1)
}

func replace(old, new, src string) string {
	return strings.Replace(src, old, new, -1)
}

func (c *ClusterController) handleConfigSet(cluster *types.Cluster) error {
	_, ok := cluster.ConfigSet["open-general-log"]
	if !ok {
		return nil
	}

	return errors.Trace(c.handlerGeneralLog(cluster))
}

func (c *ClusterController) handlerGeneralLog(cluster *types.Cluster) error {
	ctx := context.Background()

	ips, err := c.getIPsByLabel(ctx, cluster.Namespace, label.TiDBLabelVal)
	if err != nil {
		log.Infof("namespace: %s, get ips failed", cluster.Namespace)
		return errors.Trace(err)
	}

	for _, ip := range ips {
		err = myutil.OpenGeneralLog(ip)
		if err != nil {
			// here ignore error
			log.Errorf("namespace: %s, tidb %s open general log failed", cluster.Namespace, ip)
		}
	}

	return nil
}
