package entity

import (
	"fmt"
	"path/filepath"

	meteringsdkconfig "github.com/pingcap/metering_sdk/config"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const (
	TiDBGracefulWaitShutdownKey    = "graceful-wait-before-shutdown"
	TiFlashGracefulWaitShutdownKey = "flash.graceful_wait_shutdown_timeout"
)

func ComputePDConfig(sharedPool *SharedPool, pdComp *Component) error {
	if pdComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "%s %s component, no configuration needed", pdComp.GetComponentType(), pdComp.GetId())
	}

	// get pd config
	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(pdComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal %s configuration: %s", GetComponentGroupName(pdComp), err)
	}

	// set pd config
	conf.Set("security.cert-allowed-cn", []string{utils.FnvHash(sharedPool.GetDataPlaneNamespace())})
	for _, comp := range sharedPool.GetComponentGroups() {
		if comp.GetComponentType() == apiv1.ComponentType_TIFLASH_COMPUTE {
			conf.Set("replication.enable-placement-rules", true)
			break
		}
	}

	for _, comp := range sharedPool.GetComponentGroups() {
		if comp.GetComponentType() == apiv1.ComponentType_RESOURCE_MANAGER {
			conf.Set("micro-service.enable-resource-manager-fallback", false)
			break
		}
	}

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal pd-%s configuration: %w", pdComp.GetId(), err)
	}

	pdComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func PatchPDConfigWithMetering(pdComp *Component, data *meteringsdkconfig.MeteringConfig) error {
	// if data is nil, just do nothing and return
	if data == nil {
		return nil
	}
	if pdComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "%s %s component, no configuration needed", pdComp.GetComponentType(), pdComp.GetId())
	}
	// get pd config
	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(pdComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal %s configuration: %s", GetComponentGroupName(pdComp), err)
	}

	// set metering config
	conf.Set("metering", data)

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal pd-%s configuration: %w", pdComp.GetId(), err)
	}
	// write toml back
	pdComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func ComputeTiDBConfig(sharedPool *SharedPool, tidbComp *Component) error {
	if tidbComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "%s %s component, no configuration needed", tidbComp.GetComponentType().String(), tidbComp.GetId())
	}

	// get tidb config
	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tidbComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tidb-%s configuration: %s", tidbComp.GetId(), err)
	}

	conf.Set("security.cluster-verify-cn", []string{utils.FnvHash(sharedPool.GetDataPlaneNamespace())})

	// set tikv worker config
	for _, comp := range sharedPool.GetComponentGroups() {
		if comp.GetComponentType() != apiv1.ComponentType_TIKV_WORKER {
			continue
		}
		if comp.GetNodeQuota().GetNodeCount() == 0 {
			continue
		}
		tikvWorkerAddress := fmt.Sprintf("%s.%s.svc:19000", GetComponentSvcName(sharedPool, comp), sharedPool.GetDataPlaneNamespace())
		conf.Set("tikv-worker-url", fmt.Sprintf("https://%s", tikvWorkerAddress))
		break
	}

	if tidbComp.GetComponentType() == apiv1.ComponentType_TIDB {
		for _, comp := range sharedPool.GetComponentGroups() {
			if comp.GetComponentType() == apiv1.ComponentType_TIFLASH_COMPUTE {
				conf.Set("disaggregated-tiflash", true)
				break
			}
		}
	}

	// columnar architecture: route query dispatch / DDL to the columnar store.
	if UsesTiFlashColumnar(sharedPool) {
		conf.Set("cse.columnar-store-type", "columnar")
	}

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal %s configuration: %w", GetComponentGroupName(tidbComp), err)
	}

	tidbComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func PatchTiDBConfigWithMetering(tidbComp *Component, data *meteringsdkconfig.MeteringConfig) error {
	if data == nil {
		return nil
	}

	if tidbComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "%s %s component, no configuration needed", tidbComp.GetComponentType().String(), tidbComp.GetId())
	}

	// get tidb config
	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tidbComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tidb-%s configuration: %s", tidbComp.GetId(), err)
	}
	// set metering config
	// tidb team has different falvor
	meteringStorageURI := data.ToURI()
	conf.Set("metering-storage-uri", meteringStorageURI)

	// write toml back
	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal %s configuration: %w", GetComponentGroupName(tidbComp), err)
	}

	tidbComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func ComputeTiKVConfig(sharedPool *SharedPool, tikvComp *Component, kmsId string) error {
	if tikvComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "tikv %s component, no configuration needed", tikvComp.GetId())
	}

	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tikvComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tikv-%s configuration: %s", tikvComp.GetId(), err)
	}

	conf.Set("security.cert-allowed-cn", []string{utils.FnvHash(sharedPool.GetDataPlaneNamespace())})

	// check has raftlog storage
	hasRaftlog := false
	for _, storage := range tikvComp.GetStorages() {
		if storage.GetName() == apiv1.Component_Storage_RAFTLOG {
			hasRaftlog = true
			// MUST config for backup, refer to https://github.com/tidbcloud/cloud-storage-engine/blob/cloud-engine/doc/features/lightweight_native_br.md#server-side
			conf.Set("rfengine.wal-sync-dir", filepath.Join(storage.MountPath, "data"))
			conf.Set("raftstore.raftdb-path", storage.MountPath)
		}

		if storage.GetName() == apiv1.Component_Storage_DATA {
			switch {
			case storage.GetSizeGi() >= 1500:
				conf.Set("storage.low-space-threshold", "5%")
			case storage.GetSizeGi() >= 500:
				conf.Set("storage.low-space-threshold", "10%")
			default:
				conf.Set("storage.low-space-threshold", "20%")
			}
		}

		// deprecated raftdb storage
		if storage.GetName() == apiv1.Component_Storage_RAFTDB {
			conf.Set("raftstore.raftdb-path", storage.MountPath)
		}
	}

	if !hasRaftlog {
		return status.Errorf(codes.InvalidArgument, "tikv-%s has no raftlog storage", tikvComp.GetId())
	}

	if kmsId != "" {
		conf.Set("security.encryption.data-encryption-method", "aes256-ctr")
		conf.Set("security.encryption.data-key-rotation-period", "7d")
		conf.Set("security.encryption.master-key.type", "kms")
		conf.Set("security.encryption.master-key.key-id", kmsId)
		conf.Set("security.encryption.master-key.region", sharedPool.GetProvider().GetRegion())
	}

	// columnar architecture: let tikv build columnar / fts index.
	if UsesTiFlashColumnar(sharedPool) {
		conf.Set("kvengine.build-columnar", true)
		conf.Set("kvengine.build-fts-index", true)
	}

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal tikv-%s configuration: %w", tikvComp.GetId(), err)
	}

	tikvComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func ComputeTiflashConfig(sharedPool *SharedPool, tiflashComp *Component) error {
	if tiflashComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "%s %s, no configuration needed", tiflashComp.GetComponentType().String(), tiflashComp.GetId())
	}

	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tiflashComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tiflash-%s configuration: %s", tiflashComp.GetId(), err)
	}

	conf.Set("security.cert_allowed_cn", []string{utils.FnvHash(sharedPool.GetDataPlaneNamespace())})

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal tiflash-%s configuration: %w", tiflashComp.GetId(), err)
	}

	tiflashComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func ComputeTiflashLearnerConfig(sharedPool *SharedPool, tiflashLearnerComp *Component, kmsId string) error {
	if tiflashLearnerComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "tiflash learner %s component, no configuration needed", tiflashLearnerComp.GetId())
	}

	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tiflashLearnerComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tiflashLearner-%s configuration: %s", tiflashLearnerComp.GetId(), err)
	}

	conf.Set("security.cert-allowed-cn", []string{utils.FnvHash(sharedPool.GetDataPlaneNamespace())})

	if kmsId != "" {
		conf.Set("security.encryption.data-encryption-method", "aes256-ctr")
		conf.Set("security.encryption.data-key-rotation-period", "7d")
		conf.Set("security.encryption.master-key.type", "kms")
		conf.Set("security.encryption.master-key.key-id", kmsId)
		conf.Set("security.encryption.master-key.region", sharedPool.GetProvider().GetRegion())
	}

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal tiflashLearner-%s configuration: %w", tiflashLearnerComp.GetId(), err)
	}

	tiflashLearnerComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func ComputeTiflashComputeConfig(sharedPool *SharedPool, tiflashComputeComp *Component, kmsId string) error {
	if tiflashComputeComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "tiflash compute %s component, no configuration needed", tiflashComputeComp.GetId())
	}

	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tiflashComputeComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tiflashCompute-%s configuration: %s", tiflashComputeComp.GetId(), err)
	}

	conf.Set("security.cert_allowed_cn", []string{utils.FnvHash(sharedPool.GetDataPlaneNamespace())})

	// set compute config
	conf.Set("storage.s3.endpoint", GetObjectStorageAddress(sharedPool))
	conf.Set("storage.s3.bucket", sharedPool.GetObjectStorage().GetBucket())
	conf.Set("storage.s3.root", sharedPool.GetTiFlashRootPrefix())

	for _, storage := range tiflashComputeComp.GetStorages() {
		if storage.GetName() == apiv1.Component_Storage_DATA {
			// use int64 to avoid overflow
			conf.Set("storage.remote.cache.capacity", int64(storage.GetSizeGi())*1024*1024*1024)
			break
		}
	}

	// columnar architecture: read from the columnar store.
	if UsesTiFlashColumnar(sharedPool) {
		conf.Set("flash.use_columnar", true)
	}

	if kmsId != "" {
		conf.Set("security.encryption.data_encryption_method", "aes256-ctr")
		conf.Set("security.encryption.data_key_rotation_period", "7d")
		conf.Set("security.encryption.master_key.type", "kms")
		conf.Set("security.encryption.master_key.key_id", kmsId)
		conf.Set("security.encryption.master_key.region", sharedPool.GetProvider().GetRegion())
	}

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal tiflashCompute-%s configuration: %w", tiflashComputeComp.GetId(), err)
	}

	tiflashComputeComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func ComputeTiflashWriteConfig(sharedPool *SharedPool, tiflashWriteComp *Component, kmsId string) error {
	if tiflashWriteComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "tiflash write %s component, no configuration needed", tiflashWriteComp.GetId())
	}

	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tiflashWriteComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tiflashWrite-%s configuration: %s", tiflashWriteComp.GetId(), err)
	}

	conf.Set("security.cert_allowed_cn", []string{utils.FnvHash(sharedPool.GetDataPlaneNamespace())})

	conf.Set("storage.s3.endpoint", GetObjectStorageAddress(sharedPool))
	conf.Set("storage.s3.bucket", sharedPool.GetObjectStorage().GetBucket())
	conf.Set("storage.s3.root", sharedPool.GetTiFlashRootPrefix())

	if kmsId != "" {
		conf.Set("security.encryption.data_encryption_method", "aes256-ctr")
		conf.Set("security.encryption.data_key_rotation_period", "7d")
		conf.Set("security.encryption.master_key.type", "kms")
		conf.Set("security.encryption.master_key.key_id", kmsId)
		conf.Set("security.encryption.master_key.region", sharedPool.GetProvider().GetRegion())
	}

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal tiflashWrite-%s configuration: %w", tiflashWriteComp.GetId(), err)
	}

	tiflashWriteComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

// ComputeSchemaManagerConfig configures the schema-manager component (a tikv-worker
// variant) for the columnar (Essential V2) architecture: it carries the
// [schema-manager] section that enables schema management on the worker.
func ComputeSchemaManagerConfig(sharedPool *SharedPool, schemaManagerComp *Component) error {
	if schemaManagerComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "schema-manager %s component, no configuration needed", schemaManagerComp.GetId())
	}

	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(schemaManagerComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal schema-manager-%s configuration: %s", schemaManagerComp.GetId(), err)
	}

	conf.Set("schema-manager.dir", "/var/lib/tikv-worker/schemas")
	conf.Set("schema-manager.enabled", true)
	conf.Set("schema-manager.keyspace-refresh-interval", "10s")

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal schema-manager-%s configuration: %w", schemaManagerComp.GetId(), err)
	}

	schemaManagerComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func GetObjectStorageAddress(sharedPool *SharedPool) string {
	switch sharedPool.GetProvider().GetType() {
	case apiv1.ProviderType_AWS:
		return fmt.Sprintf("http://s3.%s.amazonaws.com", sharedPool.GetProvider().GetRegion())
	case apiv1.ProviderType_GCP:
		return ("https://storage.googleapis.com")
	case apiv1.ProviderType_AZURE:
		account, _, err := splitAzureBucket(sharedPool.GetObjectStorage().GetBucket())
		if err != nil {
			return ""
		}
		return fmt.Sprintf("https://%s.blob.core.windows.net", account)
	case apiv1.ProviderType_ALICLOUD:
		return fmt.Sprintf("http://oss-%s-internal.aliyuncs.com", sharedPool.GetProvider().GetRegion())
	}
	return ""
}
func ComputeTiProxyConfig(sharedPool *SharedPool, tiproxyComp *Component) error {
	if tiproxyComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "tiproxy %s component, no configuration needed", tiproxyComp.GetId())
	}

	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tiproxyComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tiproxy-%s configuration: %s", tiproxyComp.GetId(), err)
	}

	if HasSharedTiProxy(sharedPool) {
		conf.Set("balance.routing-rule", "proxy_cidr")
		if balance, ok := conf.Get("balance").(map[string]any); ok {
			delete(balance, "label-name")
			conf.Set("balance", balance)
		}
	} else {
		conf.Set("balance.label-name", "keyspace")
	}

	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal tiproxy-%s configuration: %w", tiproxyComp.GetId(), err)
	}

	tiproxyComp.GetConfiguration().ConfigurationFileContent = string(configFile)
	return nil
}

func PatchTiProxyConfigWithMetering(tiproxyComp *Component, data *meteringsdkconfig.MeteringConfig) error {
	if data == nil {
		return nil
	}
	if tiproxyComp.GetConfiguration().GetConfigurationFileContent() == "" {
		return status.Errorf(codes.InvalidArgument, "tiproxy %s component, no configuration needed", tiproxyComp.GetId())
	}
	// get tiproxy config
	conf := schemav1.NewTomlConfig(map[string]any{})
	if err := conf.UnmarshalTOML([]byte(tiproxyComp.GetConfiguration().GetConfigurationFileContent())); err != nil {
		return status.Errorf(codes.InvalidArgument, "failed to unmarshal tiproxy-%s configuration: %s", tiproxyComp.GetId(), err)
	}
	// set metering config
	conf.Set("metering", data)

	// write toml back
	configFile, err := conf.MarshalTOML()
	if err != nil {
		return fmt.Errorf("failed to marshal tiproxy-%s configuration: %w", tiproxyComp.GetId(), err)
	}

	tiproxyComp.GetConfiguration().ConfigurationFileContent = string(configFile)

	return nil
}
