package metrics

import (
	"context"
	"errors"
	"fmt"
	"github.com/pingcap/log"
	"github.com/pingcap/tep/pkg/apis/apiserver"
	"github.com/pingcap/tep/pkg/metrics/provider"
	store "github.com/pingcap/tep/pkg/registry/apiserver"
	"go.uber.org/zap"
	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apiserver/pkg/endpoints/request"
	"strings"
)

var (
	TiDBClusterCPUQuery = `
avg(sum(rate(container_cpu_usage_seconds_total{namespace="$$REPLACE_NAMESPACE$$", container_name=~"tidb|tikv|pd"}[5m])) by (pod_name, container_name) /
sum(container_spec_cpu_quota{namespace="$$REPLACE_NAMESPACE$$", container_name=~"tidb|tikv|pd"}/container_spec_cpu_period{namespace="$$REPLACE_NAMESPACE$$", container_name=~"tidb|tikv|pd"}) by (pod_name, container_name))
    `
	TiDBClusterMemQuery      = "sum(container_memory_rss{namespace=~\"$$REPLACE_NAMESPACE$$\", container_name=~\"tidb|tikv|pd\"})"
	TiDBClusterDiskQuery     = "sum(tikv_engine_size_bytes)"
	TiDBClusterResourceUsage = map[string]func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string){
		"CPU": func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string) {
			return s.K8SMetricsProvider, TiDBClusterCPUQuery
		},
		"MEMORY": func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string) {
			return s.K8SMetricsProvider, TiDBClusterMemQuery
		},
		"DISK": func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string) {
			p, err := store.NewMetricsProvider(provider.GetTiDBPrometheusDNSName(namespace, name))
			if err != nil {
				log.Error("init prometheus provider failed", zap.Error(err))
				return nil, ""
			}

			return p, TiDBClusterDiskQuery
		},
	}

	TiDBClusterCPULimit      = "sum(kube_pod_container_resource_requests_cpu_cores{namespace=\"$$REPLACE_NAMESPACE$$\", pod =~\"$$REPLACE_NAMESPACE_NAME$$-tidb.*|$$REPLACE_NAMESPACE_NAME$$-tikv.*|$$REPLACE_NAMESPACE_NAME$$-pd.*\"})"
	TiDBClusterMemLimit      = "sum(container_spec_memory_limit_bytes{namespace=\"$$REPLACE_NAMESPACE$$\" , container_name=~\"tidb|tikv|pd\"})"
	TiDBClusterDiskLimit     = "sum(tikv_store_size_bytes{type=\"capacity\"})"
	TiDBClusterResourceLimit = map[string]func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string){
		"CPU": func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string) {
			return s.K8SMetricsProvider, TiDBClusterCPULimit
		},
		"MEMORY": func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string) {
			return s.K8SMetricsProvider, TiDBClusterMemLimit
		},
		"DISK": func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string) {
			p, err := store.NewMetricsProvider(provider.GetTiDBPrometheusDNSName(namespace, name))
			if err != nil {
				log.Error("init prometheus provider failed", zap.Error(err))
				return nil, ""
			}

			return p, TiDBClusterDiskLimit
		},
	}
)

type TiDBClusterStorage struct {
	*store.Store
}

func NewTiDBClusterStorage(store *store.Store) *TiDBClusterStorage {
	return &TiDBClusterStorage{
		Store: store,
	}
}

func (s *TiDBClusterStorage) Kind() string {
	return "TiDBClusterMetrics"
}

func (s *TiDBClusterStorage) New() runtime.Object {
	return &apiserver.MetricValue{}
}

func (s *TiDBClusterStorage) NewList() runtime.Object {
	return &apiserver.MetricValueList{}
}

func (s *TiDBClusterStorage) NamespaceScoped() bool {
	return true
}

func (s *TiDBClusterStorage) NewExtraListOptions() runtime.Object {
	return &apiserver.TiDBClusterMetricsListOptions{}
}

func (s *TiDBClusterStorage) List(ctx context.Context, options *metainternalversion.ListOptions, extraOptions runtime.Object) (runtime.Object, error) {
	o := extraOptions.(*apiserver.TiDBClusterMetricsListOptions)
	tidbClusterID := request.NamespaceValue(ctx)
	queryType := strings.ToUpper(o.Type)
	if o.Name == "" {
		o.Name = tidbClusterID
	}

	r, err := s.query(TiDBClusterResourceUsage, queryType, tidbClusterID, o.Name)
	if err != nil {
		return nil, err
	}

	values := &apiserver.MetricValueList{
		TypeMeta: r.TypeMeta,
		ListMeta: r.ListMeta,
	}

	s.append(values, r.Items, queryType, "Used", tidbClusterID)

	r, err = s.query(TiDBClusterResourceLimit, queryType, tidbClusterID, o.Name)
	if err != nil {
		log.Error("query limit resource failed", zap.Error(err))
		return r, nil
	}
	s.append(values, r.Items, queryType, "Limit", tidbClusterID)

	return values, nil
}

func (s *TiDBClusterStorage) append(list *apiserver.MetricValueList, items []apiserver.MetricValue, queryType string, metric string, instanceID string) {
	if len(items) == 0 {
		return
	}

	item := items[0]
	item.MetricLabels["__name__"] = fmt.Sprintf("tidbcluster_%s_%s", strings.ToLower(queryType), strings.ToLower(metric))
	item.MetricLabels["instance"] = instanceID
	list.Items = append(list.Items, item)
}

func (s *TiDBClusterStorage) query(resources map[string]func(s *store.Store, namespace string, name string) (*provider.PrometheusProvider, string), queryType string, namespace string, name string) (*apiserver.MetricValueList, error) {
	fun, exist := resources[queryType]
	if !exist {
		return nil, k8serrors.NewBadRequest("Unknown type - " + queryType)
	}

	p, q := fun(s.Store, namespace, name)
	if p == nil {
		return nil, k8serrors.NewInternalError(errors.New("Init prometheus client failed"))
	}

	return p.GetMetric(strings.ReplaceAll(strings.ReplaceAll(q, "$$REPLACE_NAMESPACE$$", name), "$$REPLACE_NAMESPACE_NAME$$", name), provider.MetricInfo{Metric: "query"})
}

func (s *TiDBClusterStorage) NeedParameter() bool {
	return true
}
