package utils

import (
	"fmt"
	"os"
	"path/filepath"
	"strconv"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	reconcileerr "github.com/tidbcloud/infra-api/controllers/common/error"
	"github.com/tidbcloud/infra-api/pkg/consts"
	"github.com/tidbcloud/infra-api/pkg/pingcap/label"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/predicate"
)

const (
	ClusterPrefix             = "db"
	annMigrationSkipReconcile = "tags.tidbcloud.com/migrated-from-central"

	// DryrunAnnotation instruct the controller to dryrun write operations, DryrunAnnotation is
	// not part of the Infra API thus has no behavior guarantee
	DryrunAnnotation = "internal.tidbcloud.com/dryrun"

	KeyDedicated = "dedicated"
)

// GetMemberType returns the v1alpha1.MemberType from the cloudv1.ComponentType
func GetMemberType(t cloudv1.ComponentType) v1alpha1.MemberType {
	switch t {
	case cloudv1.ComponentTiDB, cloudv1.ComponentAdminTiDB:
		return v1alpha1.TiDBMemberType
	case cloudv1.ComponentTiKV:
		return v1alpha1.TiKVMemberType
	case cloudv1.ComponentPD:
		return v1alpha1.PDMemberType
	case cloudv1.ComponentTiFlash:
		return v1alpha1.TiFlashMemberType
	case cloudv1.ComponentTiCDC:
		return v1alpha1.TiCDCMemberType
	default:
		return v1alpha1.UnknownMemberType
	}
}

func ParsingLegacyId(n *cloudv1.Cluster, labelKey string) *uint64 {
	parsed, err := strconv.ParseUint(n.Labels[labelKey], 10, 64)
	if err != nil {
		return nil
	} else {
		return &parsed
	}
}

// CopyLabelsForWorkloads copies label for workloads from infra API CRs.
func CopyLabelsForWorkloads(labels map[string]string) map[string]string {
	keys := []string{
		consts.TenantTagKey,
		consts.ProjectTagKey,
		consts.ClusterTagKey,
	}
	ret := make(map[string]string)
	for _, key := range keys {
		if value, ok := labels[key]; ok {
			ret[key] = value
		}
	}
	return ret
}

// TODO(aylei): remove rule-based cluster access, the cluster status should expose these dynamically
// Deprecated
func GetExtraAccessSvcName(tcName string) string {
	return fmt.Sprintf("%s-tidb-extra-service", tcName)
}

// Deprecated
func GetExtraAccessSecretName(tcName string) string {
	return fmt.Sprintf("%s-tidb-extra-secret", tcName)
}

// Deprecated
func GetPrivilegedTiDBAddress(tcName, ns string) string {
	return fmt.Sprintf("%s.%s", GetExtraAccessSvcName(tcName), ns)
}

func GetMigrationAnnotationSkipPredicator() predicate.Funcs {
	return predicate.NewPredicateFuncs(func(o client.Object) bool {
		if _, found := o.GetAnnotations()[annMigrationSkipReconcile]; found {
			return false
		}

		return true
	})
}

func GetPredicateForProviderTypes(providerTypes ...string) predicate.Funcs {
	return predicate.NewPredicateFuncs(func(o client.Object) bool {
		objPT := o.GetLabels()[cloudv1.ProviderTypeLabel]
		for _, t := range providerTypes {
			if objPT == t {
				return true
			}
		}
		return false
	})
}

func HasDryrunAnnotation(obj client.Object) bool {
	_, ok := obj.GetAnnotations()[DryrunAnnotation]
	return ok
}

func Toleration(comp cloudv1.ComponentType) []corev1.Toleration {
	return []corev1.Toleration{
		{
			Key:      KeyDedicated,
			Operator: corev1.TolerationOpEqual,
			Value:    fmt.Sprintf("tidb-%s", string(comp)),
			Effect:   corev1.TaintEffectNoSchedule,
		},
	}
}

func IsMatchedToleration(tolerations []corev1.Toleration, comp cloudv1.ComponentType) bool {
	for _, toleration := range tolerations {
		if toleration.Key == KeyDedicated &&
			toleration.Operator == corev1.TolerationOpEqual &&
			toleration.Effect == corev1.TaintEffectNoSchedule &&
			toleration.Value == fmt.Sprintf("tidb-%s", string(comp)) {
			return true
		}
	}
	return false
}

func Taint(comp cloudv1.ComponentType) []corev1.Taint {
	return []corev1.Taint{
		{
			Key:    KeyDedicated,
			Value:  fmt.Sprintf("tidb-%s", string(comp)),
			Effect: corev1.TaintEffectNoSchedule,
		},
	}
}

func Affinity(comp cloudv1.ComponentType) *corev1.Affinity {
	return &corev1.Affinity{
		PodAntiAffinity: &corev1.PodAntiAffinity{
			RequiredDuringSchedulingIgnoredDuringExecution: []corev1.PodAffinityTerm{
				{
					LabelSelector: &metav1.LabelSelector{
						MatchLabels: map[string]string{
							label.ComponentLabelKey: string(comp),
						},
					},
					TopologyKey: "kubernetes.io/hostname",
				},
			},
		},
	}
}

// IsRealError checks if the given error is a real error.
// If err is the type of `RequeueAfterError` but its Caused is nil,
// it means that the given error is not an error, actually.
func IsRealError(err error) bool {
	if err == nil {
		return false
	}

	if requeueErr, ok := err.(*reconcileerr.RequeueAfterError); ok && requeueErr.Caused == nil {
		return false
	}

	return true
}

// IsStatusConditionTrue returns true when the conditionType is present and set to `true`
func IsStatusConditionTrue(conditions []appsv1.DeploymentCondition, conditionType appsv1.DeploymentConditionType) bool {
	return IsStatusConditionPresentAndEqual(conditions, conditionType, corev1.ConditionTrue)
}

// IsStatusConditionPresentAndEqual returns true when conditionType is present and equal to status.
func IsStatusConditionPresentAndEqual(conditions []appsv1.DeploymentCondition, conditionType appsv1.DeploymentConditionType, status corev1.ConditionStatus) bool {
	for _, condition := range conditions {
		if condition.Type == conditionType {
			return condition.Status == status
		}
	}
	return false
}

func InIntegrationTest() bool {
	v := os.Getenv("KUBEBUILDER_ASSETS")
	return v != ""
}

// MatchRegion is a glob checker against "provider-region-shootid" string.
// One could use pattern like "*-shootid" to match all shootid, or "aws-us-west-2-*" to match all shootid in aws us-west-2 region.
func MatchRegion(provider, region, shootName string, patterns []string) bool {
	for _, s := range patterns {
		target := fmt.Sprintf("%s-%s-%s", provider, region, shootName)
		if matched, err := filepath.Match(s, target); err == nil && matched {
			// Region is in the whitelist, do not skip.
			return true
		}
	}
	return false
}

// TiCDCNodeGroupNamePrefix returns the ticdc node group name prefix
// deployedInDownstream identify if the ticdc is deployed in downstream
func TiCDCNodeGroupNamePrefix(deployedInDownstream bool,
	upstreamClusterName string,
	destCluster *cloudv1.Cluster) string {
	var prefix string
	if destCluster != nil {
		prefix = destCluster.PrefixedId()
	}
	if deployedInDownstream {
		if prefix != "" {
			prefix = fmt.Sprintf("%s-%s", prefix, "cdc")
		} else {
			// no downstream cluster, use the UpstreamClusterName
			prefix = fmt.Sprintf("%s-%s", cloudv1.ClusterPrefixId(upstreamClusterName), "cdc")
		}
	}
	return prefix
}
