package featuregate

import (
	"context"
	"sync"
	"time"

	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

//go:generate go run go.uber.org/mock/mockgen@latest -source=$GOFILE -package=mock_$GOPACKAGE -destination=./mock_$GOPACKAGE/mock_$GOFILE
type Service interface {
	CheckFeatureEnabled(featureName string, tenantID, projectID uint64, cloudProvider, region string) bool
	// CheckFeatureEnabledV2 is similar to CheckFeatureEnabled, but it supports check organization create time.
	CheckFeatureEnabledV2(
		ctx context.Context,
		featureName string,
		tenantID, projectID uint64,
		cloudProvider, region string,
	) (bool, bizerr.BizError)
}

// Feature names.
const (
	// Mgmt service.
	EnableModifyRiskChecks = "enable_check_modify_risk" // whether to enable modify risk checks, see https://pingcap.feishu.cn/wiki/JZpKwtmuFi0k4YkqqQgcugZ8nWe.

	// Dedicated service.
	PreferArm                                = "prefer_arm"      // whether to prefer arm arch tier
	AwsCmek                                  = "aws_cmek"        // whether to enable aws cmek
	CidrBlockSize                            = "cidr_block_size" // whether to enable cidr block size, see https://pingcap.feishu.cn/wiki/LHjXw9SzpiIvRAkQpPpcD5ddngf
	EnableStorageScaleDown                   = "enable_storage_scale_down"
	ClusterMetering                          = "cluster_metering"
	Multi4cTikv                              = "multi_4c_tikv"                                  // allow 4c tikv node number greater than 3
	NormalPause                              = "normal_pause"                                   // whether to enable normal pause
	BlockPdScaleDown                         = "block_pd_scale_down"                            // whether to block pd scale down, refer to spec https://pingcap.feishu.cn/wiki/JZpKwtmuFi0k4YkqqQgcugZ8nWe.
	AuditLogV2                               = "audit_log_v2"                                   // whether to enable audit log v2, refer to design doc https://pingcap.feishu.cn/wiki/Zu1fwmAq8ivEXfkDfJhcWskcnHe
	AllowHeterogeneousCpuGeneration          = "allow_heterogeneous_cpu_generation"             // whether to allow heterogeneous cpu generation in a cluster, refer to design doc https://pingcap.feishu.cn/wiki/EpFnwq95qi5fAJkRQ1dc9Pj4nmf#share-FEsVdiBNYon6NkxIG4oc7bRQnEJ
	AllowCpuArchOrGenerationChangeCompletely = "allow_cpu_arch_or_generation_change_completely" // whether to allow cpu arch or generation change completely for all components in a cluster.
	GKEHeterogeneousDeploy                   = "gke_heterogeneous_deploy"                       // whether to enable GKE heterogeneous deploy fallback capacity.
	// PreserveTiKVProvisionedIOPS controls the IOPS behavior of data disk of TiKV Plus storage type when scaling up|down
	// TiKV. When this gate is enabled, scaling keeps max(current IOPS, new SKU default IOPS) so the previously provisioned
	// IOPS is not lowered. When it is disabled, IOPS is reset to the new SKU default.
	PreserveTiKVProvisionedIOPS = "preserve_tikv_provisioned_iops"
	PrivateLinkIPv6             = "private_link_ipv6"

	// Serverless service.
	AuditLog = "audit_log"
)

var (
	orgInfoCache = make(map[uint64]time.Time)
	orgInfoMutex sync.RWMutex
)

type featureGateImp struct {
	featureMap           map[string]FeatureGate
	orgCreateTimeFetcher func(ctx context.Context, orgId uint64) (time.Time, bizerr.BizError)
}

func NewFeatureGate(
	featureMap map[string]FeatureGate,
	getTenantCreateTime func(ctx context.Context, orgId uint64) (time.Time, bizerr.BizError),
) Service {
	return &featureGateImp{
		featureMap:           featureMap,
		orgCreateTimeFetcher: getTenantCreateTime,
	}
}

func (i *featureGateImp) CheckFeatureEnabled(
	featureName string,
	tenantID, projectID uint64,
	cloudProvider, region string,
) bool {
	gates, ok := i.featureMap[featureName]
	if !ok {
		return false
	}

	return gates.CheckWithAll(tenantID, projectID, cloudProvider, region)
}

func (i *featureGateImp) CheckFeatureEnabledV2(
	ctx context.Context,
	featureName string,
	tenantID, projectID uint64,
	cloudProvider, region string,
) (bool, bizerr.BizError) {
	gates, ok := i.featureMap[featureName]
	if !ok {
		return false, nil
	}

	if enabled, determined := gates.Check(tenantID, projectID, cloudProvider, region); determined {
		return enabled, nil
	}

	if gates.AffectOrgCreatedAfter != "" {
		if i.orgCreateTimeFetcher == nil {
			return false, bizerr.New(bizerr.ErrCodeUnknown, "orgCreateTimeFetcher is not defined")
		}
		t, err := time.Parse(time.RFC3339, gates.AffectOrgCreatedAfter)
		if err != nil {
			return false, bizerr.New(
				bizerr.ErrCodeUnknown,
				"failed to check feature gate, err=%w, time=%s",
				err,
				gates.AffectOrgCreatedAfter,
			)
		}
		if t.After(time.Now()) {
			return false, nil
		}
		orgCreateTime, berr := i.getOrgInfo(ctx, tenantID)
		if berr != nil {
			return false, berr
		}
		if orgCreateTime.After(t) {
			return true, nil
		}
	}

	return gates.All, nil
}

func (i *featureGateImp) getOrgInfo(ctx context.Context, orgId uint64) (time.Time, bizerr.BizError) {
	orgInfoMutex.RLock()
	orgInfo, ok := orgInfoCache[orgId]
	orgInfoMutex.RUnlock()
	if ok {
		return orgInfo, nil
	}
	orgInfo, err := i.orgCreateTimeFetcher(ctx, orgId)
	if err != nil {
		return time.Time{}, err
	}
	orgInfoMutex.Lock()
	orgInfoCache[orgId] = orgInfo
	orgInfoMutex.Unlock()
	return orgInfo, nil
}
