// package cluster provides services and proxies related to the Kubernetes cluster.

package cluster

//nolint:goimports
import (
	"context"
	"fmt"
	"net/http"
	"strings"
	"time"

	"github.com/tidbcloud/clusterproxy/resolve"
	"github.com/tidbcloud/clusterproxy/route"
	"github.com/tidbcloud/clusterproxy/route/router"
	"github.com/tidbcloud/clusterproxy/util"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const (
	cdcServiceFormat = "%s-ticdc-peer"
	cdcPortName      = "ticdc"
	cdcOpenapiPrefix = "api"
)

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

var cdcRoute = &route.Route{
	Name:    "ticdc",
	Path:    "ticdc",
	Version: route.V1,
	Schema: &route.Schema{
		PathRegex:       `.*`,
		RequiredHeaders: []string{},
		JSONBodyModel:   nil,
	},
	GetHandler: cdcGetHandler,
}

/*
proxy request url: proxy/cluster/[tidb-cluster-id]/ticdc/[cdc-cluster-id]/api/v1...
cdcGetHandler request url: [cdc-cluster-id]/api/v1... or api/v1...
if url contains [cdc-cluster-id], should dispatch to right cdc service, and TrimPrefix [cdc-cluster-id]
*/
func cdcGetHandler(c *route.Context, cl resolve.Cluster) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		namespace := cl.Namespace
		var cdcServiceName string
		if !strings.HasPrefix(r.URL.Path, cdcOpenapiPrefix) {
			// mutil cdc clusters url dispatch
			paths := strings.Split(r.URL.Path, "/")
			if len(paths) <= 1 {
				http.Error(w, "wrong cdc url request", http.StatusInternalServerError)
				return
			}
			cl.Name = paths[0]
			r.URL.Path = strings.TrimPrefix(r.URL.Path, paths[0])
			r.URL.Path = strings.TrimPrefix(r.URL.Path, "/")
		}
		cdcServiceName = fmt.Sprintf(cdcServiceFormat, cl.Name)
		tlsClientSecretName := fmt.Sprintf(tlsClientSecretFormat, cl.Name)
		config, err := util.GetTLSConfig(c.Cli, namespace, cl.Name, tlsClientSecretName)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		port, err := getCDCServicePort(c.Cli, namespace, cdcServiceName)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		proxy := route.GetKubernetesServiceProxy("https", namespace, cdcServiceName, port, &http.Transport{
			TLSClientConfig: config,
			MaxIdleConns:    1,
			IdleConnTimeout: 60 * time.Second,
		})
		proxy.ServeHTTP(w, r)
	})
}

func getCDCServicePort(kubeCli kubernetes.Interface, namespace string, cdcServiceName string) (string, error) {
	svc, err := kubeCli.CoreV1().Services(namespace).Get(context.TODO(), cdcServiceName, metav1.GetOptions{})
	if err != nil {
		return "", err
	}
	for _, port := range svc.Spec.Ports {
		if port.Name == cdcPortName {
			return fmt.Sprintf("%d", port.Port), nil
		}
	}
	return "", fmt.Errorf("no ticdc port found: %s:%s", namespace, cdcServiceName)
}
