package central

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"

	certmanagerv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	versioned2 "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	"github.com/pingcap/log"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/pingcap/tidb-operator/pkg/client/clientset/versioned"
	v1 "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/selection"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/clusterproxy/resolve"
	"github.com/tidbcloud/clusterproxy/route"
	"github.com/tidbcloud/clusterproxy/route/router"
)

const (
	operateTypeCreateTiCDC             = "createTiCDC"
	operateTypeUpdateTiCDC             = "updateTiCDC"
	operateTypeDeleteTiCDC             = "deleteTiCDC"
	operateTypeQueryTiCDC              = "queryTiCDC"
	operateTypeListTiCDCPod            = "listTiCDCPod"
	operateTypeListTiCDCNode           = "listTiCDCNode"
	operateTypeDeleteTiCDCPvc          = "deleteTiCDCPvc"
	operateTypeListTiCDCPvc            = "listTiCDCPvc"
	operateTypeCreateTiCDCCertV1alpha2 = "createTiCDCCertV1alpha2"
	operateTypeDeleteTiCDCCertV1alpha2 = "deleteTiCDCCertV1alpha2"
	operateTypeQueryTiCDCCertV1alpha2  = "queryTiCDCCertV1alpha2"
	operateTypeCreateTiCDCCertV1       = "createTiCDCCertV1"
	operateTypeDeleteTiCDCCertV1       = "deleteTiCDCCertV1"
	operateTypeQueryTiCDCCertV1        = "queryTiCDCCertV1"
)

func init() {
	router.RegisterRoute(k8sClientRoute)
}

var k8sClientRoute = &route.Route{
	Name:    "k8s-cli",
	Path:    "k8s-cli",
	Version: route.V1,
	Schema: &route.Schema{
		PathRegex:       "^$",
		RequiredHeaders: []string{},
		JSONBodyModel:   &k8sClientModel{},
	},
	GetHandler: k8sClientGetHandler,
}

type k8sClientModel struct {
	OperateType string
	Cluster     v1alpha1.TidbCluster
	Cert        v1alpha2.Certificate
	CertV1      certmanagerv1.Certificate
	Name        string
	NameSpace   string
	Label       map[string]string
}

type k8sClientResponse struct {
	Cluster  *v1alpha1.TidbCluster
	Cert     *v1alpha2.Certificate
	CertV1   *certmanagerv1.Certificate
	NodeList *v1.NodeList
	PodList  *v1.PodList
	PvcList  *v1.PersistentVolumeClaimList
}

func k8sClientGetHandler(c *route.Context, cl resolve.Cluster) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, err := io.ReadAll(r.Body)
		defer r.Body.Close() // nolint
		if err != nil {
			http.Error(w, "failed to read body contents", http.StatusInternalServerError)
			return
		}

		model := &k8sClientModel{}
		err = json.Unmarshal(body, model)
		if err != nil {
			// Surface non-internal errors to the response.
			http.Error(w, fmt.Sprintf("failed to parse the body contents: %v", err), http.StatusBadRequest)
			return
		}
		log.Info(fmt.Sprintf("%v", model))

		// ctx for dial kafka/mysql
		ctx, cancel := context.WithTimeout(context.TODO(), 3*time.Second)
		defer cancel()
		switch model.OperateType {
		case operateTypeCreateTiCDC:
			if err := createTiCDC(ctx, c.OpCli, &model.Cluster); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeUpdateTiCDC:
			if err := updateTiCDC(ctx, c.OpCli, &model.Cluster); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeDeleteTiCDC:
			if err := deleteTiCDC(ctx, c.OpCli, model.Name, model.NameSpace); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeQueryTiCDC:
			cr, err := queryTiCDC(ctx, c.OpCli, model.Name, model.NameSpace)
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
			if err = writeResponse(w, &k8sClientResponse{
				Cluster: cr,
			}); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeListTiCDCPod:
			pods, err := listTiCDCPod(ctx, c.K8sCli, model.Label)
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
			if err = writeResponse(w, &k8sClientResponse{
				PodList: pods,
			}); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeListTiCDCNode:
			nodes, err := listTiCDCNode(ctx, c.K8sCli, model.Label)
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
			if err = writeResponse(w, &k8sClientResponse{
				NodeList: nodes,
			}); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeDeleteTiCDCPvc:
			if err := deleteTiCDCPvc(ctx, c.K8sCli, model.Name, model.NameSpace); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeListTiCDCPvc:
			pvcs, err := listTiCDCPvc(ctx, c.K8sCli, model.Name, model.NameSpace)
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
			if err = writeResponse(w, &k8sClientResponse{
				PvcList: pvcs,
			}); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeCreateTiCDCCertV1alpha2:
			if err := createTiCDCCertV1alpha2(ctx, c.CertCli, &model.Cert); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeDeleteTiCDCCertV1alpha2:
			if err := deleteTiCDCCertV1alpha2(ctx, c.CertCli, model.Name, model.NameSpace); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeQueryTiCDCCertV1alpha2:
			cert, err := queryTiCDCCertV1alpha2(ctx, c.CertCli, model.Name, model.NameSpace)
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
			if err = writeResponse(w, &k8sClientResponse{
				Cert: cert,
			}); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeCreateTiCDCCertV1:
			if err := createTiCDCCertV1(ctx, c.CertCli, &model.CertV1); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeDeleteTiCDCCertV1:
			if err := deleteTiCDCCertV1(ctx, c.CertCli, model.Name, model.NameSpace); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		case operateTypeQueryTiCDCCertV1:
			cert, err := queryTiCDCCertV1(ctx, c.CertCli, model.Name, model.NameSpace)
			if err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
				return
			}
			if err = writeResponse(w, &k8sClientResponse{
				CertV1: cert,
			}); err != nil {
				http.Error(w, err.Error(), http.StatusBadRequest)
			}
			return
		default:
			http.Error(w, fmt.Sprintf("Unknown operator type type %s", model.OperateType), http.StatusBadRequest)
			return
		}
	})
}

func writeResponse(w http.ResponseWriter, resp *k8sClientResponse) error {
	data, err := json.Marshal(resp)
	if err != nil {
		return err
	}
	_, err = w.Write(data)
	return err
}

func createTiCDC(ctx context.Context, cli *versioned.Clientset, cr *v1alpha1.TidbCluster) error {
	_, err := cli.PingcapV1alpha1().TidbClusters(cr.Namespace).Create(ctx, cr, metav1.CreateOptions{})
	if !apierrors.IsAlreadyExists(err) {
		return err
	}
	return nil
}

func updateTiCDC(ctx context.Context, cli *versioned.Clientset, cr *v1alpha1.TidbCluster) error {
	_, err := cli.PingcapV1alpha1().TidbClusters(cr.Namespace).Update(ctx, cr, metav1.UpdateOptions{})
	return err
}

func deleteTiCDC(ctx context.Context, cli *versioned.Clientset, name, namespace string) error {
	return client.IgnoreNotFound(cli.PingcapV1alpha1().TidbClusters(namespace).Delete(ctx, name, metav1.DeleteOptions{}))
}

func queryTiCDC(ctx context.Context, cli *versioned.Clientset, name, namespace string) (*v1alpha1.TidbCluster, error) {
	cr, err := cli.PingcapV1alpha1().TidbClusters(namespace).Get(ctx, name, metav1.GetOptions{})
	if client.IgnoreNotFound(err) != nil {
		return nil, err
	}
	return cr, nil
}

func listTiCDCPod(ctx context.Context, cli client.Client, label map[string]string) (*v1.PodList, error) {
	var podList v1.PodList
	selector := labels.NewSelector()
	for k, v := range label {
		specify, err := labels.NewRequirement(k, selection.Equals, []string{v})
		if err != nil {
			return nil, err
		}
		selector = selector.Add(*specify)
	}
	err := cli.List(ctx, &podList, &client.MatchingLabelsSelector{Selector: selector})
	if err != nil {
		return nil, err
	}
	return &podList, nil
}

func listTiCDCNode(ctx context.Context, cli client.Client, label map[string]string) (*v1.NodeList, error) {
	var nodeList v1.NodeList
	selector := labels.NewSelector()
	for k, v := range label {
		specify, err := labels.NewRequirement(k, selection.Equals, []string{v})
		if err != nil {
			return nil, err
		}
		selector = selector.Add(*specify)
	}
	err := cli.List(ctx, &nodeList, &client.MatchingLabelsSelector{Selector: selector})
	if err != nil {
		return nil, err
	}
	return &nodeList, nil
}

func deleteTiCDCPvc(ctx context.Context, cli client.Client, name string, namespace string) error {
	pvcs := new(v1.PersistentVolumeClaimList)
	cdcLabelSelector, err := labels.Parse(fmt.Sprintf("app.kubernetes.io/component=ticdc,app.kubernetes.io/instance=%s", name))
	if err != nil {
		return err
	}
	err = cli.List(ctx, pvcs, &client.ListOptions{Namespace: namespace, LabelSelector: cdcLabelSelector})
	if err != nil {
		return err
	}
	for _, pvc := range pvcs.Items {
		if pvc.Spec.StorageClassName != nil {
			err = cli.Delete(ctx, &pvc)
			if client.IgnoreNotFound(err) != nil {
				return err
			}
		}
	}
	return nil
}

func listTiCDCPvc(ctx context.Context, cli client.Client, name string, namespace string) (*v1.PersistentVolumeClaimList, error) {
	pvcs := new(v1.PersistentVolumeClaimList)
	cdcLabelSelector, err := labels.Parse(fmt.Sprintf("app.kubernetes.io/component=ticdc,app.kubernetes.io/instance=%s", name))
	if err != nil {
		return nil, err
	}
	err = cli.List(ctx, pvcs, &client.ListOptions{Namespace: namespace, LabelSelector: cdcLabelSelector})
	if err != nil {
		return nil, err
	}
	return pvcs, nil
}

func createTiCDCCertV1alpha2(ctx context.Context, cli *versioned2.Clientset, cert *v1alpha2.Certificate) error {
	_, err := cli.CertmanagerV1alpha2().Certificates(cert.Namespace).Create(ctx, cert, metav1.CreateOptions{})
	if !apierrors.IsAlreadyExists(err) {
		return err
	}
	return nil
}

func deleteTiCDCCertV1alpha2(ctx context.Context, cli *versioned2.Clientset, name string, namespace string) error {
	return client.IgnoreNotFound(cli.CertmanagerV1alpha2().Certificates(namespace).Delete(ctx, name, metav1.DeleteOptions{}))
}

func queryTiCDCCertV1alpha2(ctx context.Context, cli *versioned2.Clientset, name string, namespace string) (*v1alpha2.Certificate, error) {
	cert, err := cli.CertmanagerV1alpha2().Certificates(namespace).Get(ctx, name, metav1.GetOptions{})
	if client.IgnoreNotFound(err) != nil {
		return nil, err
	}
	return cert, nil
}

func createTiCDCCertV1(ctx context.Context, cli *versioned2.Clientset, cert *certmanagerv1.Certificate) error {
	_, err := cli.CertmanagerV1().Certificates(cert.Namespace).Create(ctx, cert, metav1.CreateOptions{})
	if !apierrors.IsAlreadyExists(err) {
		return err
	}
	return nil
}

func deleteTiCDCCertV1(ctx context.Context, cli *versioned2.Clientset, name string, namespace string) error {
	return client.IgnoreNotFound(cli.CertmanagerV1().Certificates(namespace).Delete(ctx, name, metav1.DeleteOptions{}))
}

func queryTiCDCCertV1(ctx context.Context, cli *versioned2.Clientset, name string, namespace string) (*certmanagerv1.Certificate, error) {
	cert, err := cli.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{})
	if client.IgnoreNotFound(err) != nil {
		return nil, err
	}
	return cert, nil
}
