package configs

import (
	"github.com/tidbcloud/observability/pkg/common/constants"
	"github.com/tidbcloud/observability/pkg/common/errors"
	"github.com/tidbcloud/observability/pkg/common/structs"
	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
)

var serviceInfo *structs.ServiceInfo
var specContainer map[string]k8sv1.ResourceRequirements

func Init(s structs.ServiceInfo) {
	serviceInfo = &s
	if len(serviceInfo.MonitorSpec) > 0 {
		specContainer = LoadSpecResourceRequirements()
	}
}

func GetServiceInfo() structs.ServiceInfo {
	if serviceInfo == nil {
		panic("service info is empty")
	}
	return *serviceInfo
}

func GetSpecContainer() map[string]k8sv1.ResourceRequirements {
	if len(specContainer) == 0 && len(GetServiceInfo().MonitorSpec) == 0 {
		panic("no available monitor spec")
	}
	return specContainer
}

func LoadSpecResourceRequirements() map[string]k8sv1.ResourceRequirements {
	rr := map[string]k8sv1.ResourceRequirements{}
	for k, v := range serviceInfo.MonitorSpec {
		rr[k] = k8sv1.ResourceRequirements{
			Requests: parseSpecQuantities(v.Cpu, v.Memory, v.InitialStorage),
			Limits:   buildResourceLimits(v),
		}
	}
	return rr
}

func GetOidcArn() string {
	return serviceInfo.AccountInfo.OidcARN
}

func GetOidc() string {
	return serviceInfo.AccountInfo.Oidc
}

func GetAccountID() string {
	return serviceInfo.AccountInfo.Account
}

func GetSusbcriptionID() string {
	return serviceInfo.AccountInfo.SubscriptionID
}

func GetResourceGroup() string {
	return serviceInfo.AccountInfo.ResourceGroup
}

func GetStorageResourceGroup() string {
	return serviceInfo.AccountInfo.BlobStorageResourceGroup
}

func GetAzureStorageAccount() string {
	return serviceInfo.AccountInfo.StorageAccount
}

func GetAlertManagerNameSpace() string {
	return serviceInfo.AlertManagerInfo.NameSpace
}
func GetAlertManagerName() string {
	return serviceInfo.AlertManagerInfo.Name
}
func GetAlertManagerPort() string {
	return serviceInfo.AlertManagerInfo.Port
}

func GetAgentManagerARNByBizType(bizType string) (string, error) {
	if config, ok := serviceInfo.BizTypeConfigs[bizType]; ok {
		return config.AgentManagerARN, nil
	} else {
		return "", errors.NewErrorf(errors.UnsupportedBizType, "biz_type %s is not supported", bizType)
	}
}

func GetMonitorReplicasByBizType(bizType string) (int32, error) {
	config, ok := serviceInfo.BizTypeConfigs[bizType]
	if !ok {
		return -1, errors.NewErrorf(errors.UnsupportedBizType, "biz_type %s is not supported", bizType)
	}

	if config.MonitorReplicas < 1 {
		return -1, errors.NewErrorf(errors.ConfigInvalid, "monitorReplicas of biz_type %s is invalid", bizType)
	}
	//nolint:gosec
	return int32(serviceInfo.BizTypeConfigs[bizType].MonitorReplicas), nil
}

func parseSpecQuantities(cpu, memory, storage string) (quantities map[k8sv1.ResourceName]resource.Quantity) {
	quantities = make(map[k8sv1.ResourceName]resource.Quantity)
	var err error
	errors.OfNullable(nil).BreakIf(func() error {
		quantities[k8sv1.ResourceCPU], err = resource.ParseQuantity(cpu)
		return err
	}).BreakIf(func() error {
		quantities[k8sv1.ResourceMemory], err = resource.ParseQuantity(memory)
		return err
	}).BreakIf(func() error {
		if len(storage) > 0 {
			quantities[k8sv1.ResourceStorage], err = resource.ParseQuantity(storage)
		}
		return err
	}).If(func(err error) {
		panic("parse quantity panic" + err.Error())
	})

	return quantities
}

func buildResourceLimits(spec structs.MonitorSpec) (limits map[k8sv1.ResourceName]resource.Quantity) {
	oversell := spec.OverSell
	if oversell == float64(0) {
		oversell = GetServiceInfo().DefaultOversell
	}
	quantity := parseSpecQuantities(spec.Cpu, spec.Memory, spec.InitialStorage)

	// build Cpu limit
	quantityCpu := quantity[k8sv1.ResourceCPU]
	cpu := quantityCpu.AsApproximateFloat64() * oversell
	// use NewScaledQuantity to reduce precision loss
	incCpu := resource.NewScaledQuantity(int64(cpu*10000), -4)
	quantityCpu.Add(*incCpu)

	// build Memory limit
	quantityMem := quantity[k8sv1.ResourceMemory]
	mem := quantityMem.AsApproximateFloat64() * oversell
	// use NewScaledQuantity to reduce precision loss
	incMem := resource.NewScaledQuantity(int64(mem*10000), -4)
	quantityMem.Add(*incMem)

	limits = map[k8sv1.ResourceName]resource.Quantity{
		k8sv1.ResourceCPU:    quantityCpu,
		k8sv1.ResourceMemory: quantityMem,
	}
	return
}

func MultiTenancyRuleEnabled(bizType, appID string) bool {
	return GetServiceInfo().BizTypeConfigs[bizType].MultiTenancyRule &&
		appID != constants.ApplicationIDBase &&
		appID != constants.ApplicationIDSeed &&
		appID != constants.ApplicationIDServerless
}
