package service

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/pingcap/tidb-operator/pkg/apis/label"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/samber/lo"
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-agent/v1"
	apicommon "github.com/tidbcloud/infra-provider/api/tidbcloud/common"
)

const (
	CoreGroup   = "core.pingcap.com"
	CoreVersion = "v1alpha1"

	K8sComponentLabelKey = "app.kubernetes.io/component"
	K8sInstanceLabelKey  = "app.kubernetes.io/instance"

	PingcapComponentLabelKey = "pingcap.com/component"
	PingcapGroupLabelKey     = "pingcap.com/group"
)

var (
	// Notice: when you add a new component here, you need add coresponding "delete" verb for the cluster-agent Role.
	DeleteGroupInstancesApi_AllowComponents = []string{"ticdc"}

	ComponentGroupKindMap = map[string]string{
		"pd":      "PDGroup",
		"tikv":    "TiKVGroup",
		"tidb":    "TiDBGroup",
		"tiflash": "TiFlashGroup",
		"tiproxy": "TiProxyGroup",
		"ticdc":   "TiCDCGroup",
	}
	ComponentGroupInstanceKindMap = lo.MapValues(ComponentGroupKindMap, func(v string, k string) string {
		return strings.TrimSuffix(v, "Group")
	})

	ComponentGroupInstanceListKindMap = lo.MapValues(ComponentGroupInstanceKindMap, func(v string, k string) string {
		return v + "List"
	})
)

var (
	member2AnnoKeyToDeleteSlot = map[v1alpha1.MemberType]string{
		v1alpha1.PDMemberType:      label.AnnPDDeleteSlots,
		v1alpha1.TiKVMemberType:    label.AnnTiKVDeleteSlots,
		v1alpha1.TiDBMemberType:    label.AnnTiDBDeleteSlots,
		v1alpha1.TiFlashMemberType: label.AnnTiFlashDeleteSlots,
		v1alpha1.TiProxyMemberType: label.AnnTiProxyDeleteSlots,
	}
)

func (s *AgentService) getTCsByClusterId(ctx context.Context, clusterId string) (map[string]*v1alpha1.TidbCluster, error) {
	ns := buildNamespace(clusterId)

	var tcs v1alpha1.TidbClusterList
	labelFilters := client.MatchingLabels{
		cloudv1.LabelUsedFor: cloudv1.UsedForCluster,
	}
	if err := s.dataplaneCli.List(ctx, &tcs, client.InNamespace(ns), labelFilters); err != nil {
		return nil, fmt.Errorf("failed to list tidbcluster CR: %w", err)
	}
	out := lo.SliceToMap(tcs.Items, func(item v1alpha1.TidbCluster) (string, *v1alpha1.TidbCluster) {
		return item.Name, &item
	})
	return out, nil
}

func (s *AgentService) getTCByClusterId(ctx context.Context, clusterId, tcName string) (*v1alpha1.TidbCluster, error) {
	ns := buildNamespace(clusterId)

	var tc v1alpha1.TidbCluster
	if err := s.dataplaneCli.Get(ctx, client.ObjectKey{Namespace: ns, Name: tcName}, &tc); err != nil {
		return nil, fmt.Errorf("failed to get tidbcluster CR(%s): %w", tcName, err)
	}
	return &tc, nil
}

func buildNamespace(clusterId string) string {
	return fmt.Sprintf("%s%s", tcNamespacePrefix, clusterId)
}

func buildK8sMetadata(obj client.Object) *apiv1.K8SMetadata {
	out := &apiv1.K8SMetadata{
		Name:        obj.GetName(),
		Namespace:   obj.GetNamespace(),
		Labels:      obj.GetLabels(),
		Annotations: obj.GetAnnotations(),
	}
	return out
}

func convertComponentTypeToAPI(tp string) apicommon.ComponentType {
	return apicommon.ComponentType(apicommon.ComponentType_value[strings.ToUpper(tp)])
}

// nolint: unused
func convertComponentTypeFromAPI(tp apicommon.ComponentType) string {
	return strings.ToLower(apicommon.ComponentType_name[int32(tp)])
}

// getOrdinalIdForPod return ordinal id of this pod.
// pod.GenerateName like 'db-1802962504435896320-c7082f84-tidb-'
// pod.Name like 'db-1802962504435896320-c7082f84-tidb-0'
func getOrdinalIdForPod(pod *corev1.Pod) (int32, error) {
	ordinalStr := strings.TrimPrefix(pod.Name, pod.GenerateName)
	ordinal, err := strconv.ParseInt(ordinalStr, 10, 32)
	if err != nil {
		return 0, fmt.Errorf("failed to parse pod ordinal(%s): %w", ordinalStr, err)
	}
	return int32(ordinal), nil
}

func calcNextOrdinal(replicas int32, deletedSlots []int32) (currentOrdinals []int32, nextOrdinal int32) {
	for ; int32(len(currentOrdinals)) < replicas; nextOrdinal++ {
		if !lo.Contains(deletedSlots, nextOrdinal) {
			currentOrdinals = append(currentOrdinals, nextOrdinal)
		}
		continue
	}
	for lo.Contains(deletedSlots, nextOrdinal) {
		nextOrdinal++
	}
	return currentOrdinals, nextOrdinal
}

func isTCReady(tc *v1alpha1.TidbCluster) bool {
	ready := false
	for _, c := range tc.Status.Conditions {
		if c.Type == v1alpha1.TidbClusterReady && c.Status == v1.ConditionTrue {
			ready = true
			break
		}
	}
	return ready
}
