// package resolve provides methods that resolve the TiDB cluster spec from a request
package resolve

import (
	"context"
	"fmt"
	"net/http"
	"regexp"
	"strings"

	"github.com/pingcap/log"
	"github.com/pingcap/tidb-operator/pkg/client/clientset/versioned"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

const byNamespacedNamePrefix = "/proxy/namespace/"

var (
	byNamespacedNameRegex        = regexp.MustCompile("^/proxy/namespace/([^/]*)/cluster/([^/]*)/")
	byNamespacedNameMonitorRegex = regexp.MustCompile("^/proxy/namespace/([^/]*)/tidbmonitor/([^/]*)/")
)

type ByNamespacedName struct {
	cache cache
	cli   *kubernetes.Clientset
	opcli *versioned.Clientset
}

type cache interface {
	Get(string) (string, bool)
	Set(string, string)
}

func NewByNamespacedName(cli *kubernetes.Clientset, opcli *versioned.Clientset) (*ByNamespacedName, error) {
	cache, err := newCache()
	if err != nil {
		return nil, err
	}
	return &ByNamespacedName{
		cache: cache,
		cli:   cli,
		opcli: opcli,
	}, nil
}

func (r *ByNamespacedName) Prefix() string {
	return byNamespacedNamePrefix
}

func (r *ByNamespacedName) Resolve(req *http.Request) (cl Cluster, route string, err error) {
	match := byNamespacedNameRegex.FindStringSubmatch(req.URL.Path)
	if len(match) < 3 {
		monitorMatch := byNamespacedNameMonitorRegex.FindStringSubmatch(req.URL.Path)
		if len(monitorMatch) < 3 {
			err = fmt.Errorf("unable to locate the cluster in the path %s", req.URL.Path)
			return
		}
		// monitorMatch[0] is the whole matched string
		cl.MonitorNamespace = monitorMatch[1]
		cl.MonitorName = monitorMatch[2]
	} else {
		// match[0] is the whole matched string
		cl.Namespace = match[1]
		cl.Name = match[2]
		// In the case the /proxy/namespace/.../cluster/... path is called for Prometheus
		// The assumption here is that there will be one and only one TiDBMonitor in the
		// same namespace as the TiDBCluster.
		cl.MonitorName = r.getMonitorName(cl.Namespace)
		cl.MonitorNamespace = cl.Namespace
	}
	// /proxy/namespace/ns/name/clustername/url... -> url...
	paths := strings.Split(req.URL.Path, "/")
	route = strings.Join(paths[6:], "/")
	req.URL.Path = route
	return
}

func (r *ByNamespacedName) getMonitorName(namespace string) string {
	key := namespace
	if name, ok := r.cache.Get(key); ok {
		return name
	}

	tms, err := r.opcli.PingcapV1alpha1().TidbMonitors(namespace).List(context.TODO(), metav1.ListOptions{})

	if err != nil || tms == nil || len(tms.Items) == 0 {
		log.Warn("Unable to find the TiDBMonitor", zap.String("namespace", namespace))
		return ""
	}
	if len(tms.Items) != 1 {
		log.Warn("Find more than one TiDB monitor", zap.String("namespace", namespace))
	}
	tm := tms.Items[0]
	name := tm.Name
	r.cache.Set(key, name)
	return name
}
