package feature

import (
	"sort"
	"strconv"
	"strings"

	"github.com/Masterminds/semver"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/internal/config"
	"github.com/tidbcloud/dataflow-service/internal/utils"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
)

const (
	EBS                        = "ebs"                   // ebs snapshot backup
	PiTR                       = "featurepitr"           // PiTR feature
	DualRegionBR               = "dualregionbr"          // dual region br feature
	DM                         = "featuredm"             // data migration
	SkipImportVildation        = "skipimportvalidation"  // skip import validation
	MaintainingDMJobs          = "maintainingdmjobs"     // disable DM jobs: CreateJob/OperateJob/UpdateJob/ListJobs/GetJob/DeleteJob
	RestoreWithWorkFlow        = "restore-with-workflow" // restore with workflow
	AllowMultiImportJobs       = "allowMultiImportJobs"
	SkipCdcPitrCheckWhenImport = "skip-cdc-pitr-check-when-import"

	defaultJobQuota = 200 // dm default quota
)

var TikvLessThanV610, _ = semver.NewConstraint("<v6.1.0-0")

// // IsFeatureEnabledByClusterChecker returns whether the feature is in the allowlist and passed the checks.
// func IsFeatureEnabledByClusterChecker(feature string, cluster *mgmt.Cluster, versionChecker func(string) bool) bool {
// 	if !versionChecker(cluster.GetDedicatedCluster().GetVersion()) {
// 		return false
// 	}
// 	features := GetEnabledFeatures(cluster.OrganizationId, cluster.ProjectId, cluster.ClusterId, cluster.Region, domain.CloudProviderToInfraType(cluster.CloudProvider))
// 	return utils.IsContain(features, feature)
// }

// IsFeatureEnabledByCluster returns whether the feature is in the allowlist.
func IsFeatureEnabledByCluster(feature string, cluster *core.Cluster) bool {
	features := GetEnabledFeatures(cluster.TenantId, cluster.ProjectId, cluster.Id, cluster.RegionName, cluster.InfraProviderType)
	return utils.IsContain(features, feature)
}

// IsFeatureEnabled returns whether the feature is in the allowlist.
func IsFeatureEnabled(feature string, tenantID, projectID, clusterID uint64, region, infraProvider string) bool {
	features := GetEnabledFeatures(tenantID, projectID, clusterID, region, infraProvider)
	return utils.IsContain(features, strings.ToLower(feature))
}

func GetEnabledFeaturesByCluster(cluster *mgmt.Cluster) []string {
	return GetEnabledFeatures(cluster.OrganizationId, cluster.ProjectId, cluster.ClusterId, cluster.Region, string(domain.CloudProviderToInfraType(cluster.CloudProvider)))
}

func GetEnabledFeatures(tenantID, projectID, clusterID uint64, region, infraProvider string) []string {
	flags := map[string]bool{}
	features := []string{}

	addFeature := func(feature string, fs ...func() bool) {
		// All the keys in FeaturesAllowList will be converted to lowercase by one config
		// we need convert all the features to lowercase in case features in PublicFeatures have uppercase
		feature = strings.ToLower(feature)
		_, ok := flags[feature]
		if ok {
			return
		}
		for _, f := range fs {
			// needs to be judged by the callback function
			if !f() {
				return
			}
		}
		flags[feature] = true
		features = append(features, feature)
	}

	switchers := config.Global().ServerConfig.FeaturesSwitchers

	// public features
	for _, feature := range switchers.PublicFeatures {
		addFeature(feature)
	}
	// feature enabled by how it's deployed
	for feature, matcher := range switchers.EnabledFeatureByDeployment {
		addFeature(feature, func() bool {
			return matcher.Match(region, infraProvider)
		})
	}

	// allow list features
	for feature, matcher := range switchers.FeaturesAllowList {
		addFeature(feature, func() bool {
			return matcher.Match(tenantID, projectID, clusterID, region, infraProvider)
		})
	}
	sort.Strings(features)
	return features
}

func GetTenantQuota(tid uint64) int {
	tidStr := strconv.FormatUint(tid, 10)
	quotas := config.Global().ServerConfig.DMTenantQuota
	if val, ok := quotas[tidStr]; ok {
		return val
	}
	return defaultJobQuota
}
