package k8s

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/pingcap/errors"
	"go.uber.org/zap"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/kube"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"

	apiresource "github.com/pingcap/test-infra/common/model/resource"
	"github.com/pingcap/test-infra/sdk/pkg/proxy"
	"github.com/pingcap/test-infra/sdk/resource"
)

var _ resource.TiDBDrainer = &TiDBDrainer{}

type TiDBDrainer struct {
	KubeClientSet
	apiresource.TiDBDrainer
}

func (t *TiDBDrainer) Handle() apiresource.ResourceHandle {
	return t.TiDBDrainer.ResourceHandle
}

func (t *TiDBDrainer) Exec(opts resource.TiDBDrainerExecOptions) (stdout string, stderr string, exitCode int, err error) {
	podName := fmt.Sprintf("%s-%d", t.Name, opts.Peer)
	stdout, stderr, ec, err := t.ExecCommandInContainerWithFullOutput(podName, "drainer", "/bin/sh", "-c", opts.Command)
	if err != nil {
		sugar.Error("Exec error", zap.Error(err))
		err = errors.Trace(err)
	}
	if ec != nil {
		exitCode = *ec
	} else {
		sugar.Warnw("cannot get the exit code, return -1")
		exitCode = -1
	}
	return
}

func (t *TiDBDrainer) Ready() bool {
	return t.TiDBDrainer.Status.Phase == apiresource.TiDBDrainerReady
}

func (t *TiDBDrainer) Sync() error {
	actionConfig := new(action.Configuration)

	if err := actionConfig.Init(kube.GetConfig(t.kubeConfig, "", t.Namespace), t.Namespace, "configmap", sugar.Infof); err != nil {
		sugar.Errorf("init the helm action config error: %v", err)
		return err
	}
	if err := t.CheckResourceExist(); err != nil {
		return err
	}
	client := action.NewGetValues(actionConfig)
	vals, err := client.Run(t.Name)
	if err != nil {
		return err
	}
	data, _ := json.Marshal(vals)
	if err := json.Unmarshal(data, &t.Spec); err != nil {
		return err
	}
	sts, err := t.ClientSet.AppsV1().StatefulSets(t.Namespace).Get(context.TODO(), t.Name, metav1.GetOptions{})
	if err != nil {
		return err
	}
	if sts.Status.ReadyReplicas == 1 {
		t.TiDBDrainer.Status.Phase = apiresource.TiDBDrainerReady
		if config.TiDBCluster.ReadyCheck.DialDrainer {
			address := fmt.Sprintf("%s.%s:8249", t.Name, t.Namespace)
			if _, err := proxy.ProxyDialer(5*time.Second).Dial("tcp", address); err != nil {
				sugar.Warnf("dial %s error: %v", address, err)
				t.TiDBDrainer.ResourceHandle.Ready = &[]bool{false}[0]
				return nil
			}
		}
		t.TiDBDrainer.ResourceHandle.Ready = &[]bool{true}[0]
	} else {
		t.TiDBDrainer.Status.Phase = apiresource.TiDBDrainerNotReady
		t.TiDBDrainer.ResourceHandle.Ready = &[]bool{false}[0]
	}
	return nil
}

func (t *TiDBDrainer) CheckResourceExist() error {
	labelSelector := labels.Set{
		"app.kubernetes.io/component": "drainer",
		"app.kubernetes.io/name":      t.Name,
	}.AsSelector().String()

	sts, err := t.ClientSet.AppsV1().StatefulSets(t.Namespace).List(context.TODO(), metav1.ListOptions{
		LabelSelector: labelSelector,
	})
	if err != nil {
		return err
	}
	pods, err := t.ClientSet.CoreV1().Pods(t.Namespace).List(context.TODO(), metav1.ListOptions{
		LabelSelector: labelSelector,
	})
	if err != nil {
		return err
	}
	pvcs, err := t.ClientSet.CoreV1().PersistentVolumeClaims(t.Namespace).List(context.TODO(), metav1.ListOptions{
		LabelSelector: labelSelector,
	})
	if err != nil {
		return err
	}

	if len(sts.Items) == 0 && len(pods.Items) == 0 && len(pvcs.Items) == 0 {
		return resource.ErrorResourceNotFound{ResourceHandle: t.Handle()}
	}
	if len(sts.Items) == 0 {
		return fmt.Errorf("integrityCheck error: the statefulsets of drainer miss")
	}
	return nil
}
