package central

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	centralapi "github.com/tidbcloud/core-svc/idl/pbgen/proto/central"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	openapiserverlessv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/serverless/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/internal/api/utils"
)

const (
	ShareNodeName        = "Shared0"
	ShareNodeDisplayName = "Free shared(dev tier)"

	HasSetPwdKey         = "tidb.cloud/has-set-password"
	AvailableFeaturesKey = "tidb.cloud/available-features"
)

var (
	statusMap = map[openapicommonv1beta1.ClusterState]centralapi.Cluster_Status{
		openapicommonv1beta1.ClusterState_CLUSTER_STATE_UNSPECIFIED: centralapi.Cluster_UNAVAILABLE,
		openapicommonv1beta1.ClusterState_CREATING:                  centralapi.Cluster_CREATING,
		openapicommonv1beta1.ClusterState_DELETING:                  centralapi.Cluster_DELETED,
		openapicommonv1beta1.ClusterState_ACTIVE:                    centralapi.Cluster_NORMAL,
		openapicommonv1beta1.ClusterState_RESTORING:                 centralapi.Cluster_RECOVERING,
		openapicommonv1beta1.ClusterState_MAINTENANCE:               centralapi.Cluster_MAINTAINING,
		openapicommonv1beta1.ClusterState_MODIFYING:                 centralapi.Cluster_SCALING,
		openapicommonv1beta1.ClusterState_PAUSED:                    centralapi.Cluster_PAUSED,
		openapicommonv1beta1.ClusterState_PAUSING:                   centralapi.Cluster_PAUSING,
		openapicommonv1beta1.ClusterState_RESUMING:                  centralapi.Cluster_RESUMING,
		openapicommonv1beta1.ClusterState_INACTIVE:                  centralapi.Cluster_UNAVAILABLE,
		openapicommonv1beta1.ClusterState_UPGRADING:                 centralapi.Cluster_UPGRADING,
		openapicommonv1beta1.ClusterState_IMPORTING:                 centralapi.Cluster_IMPORTING,
	}

	displayStatusMap = map[openapicommonv1beta1.ClusterState]centralapi.Cluster_DisplayStatus{
		openapicommonv1beta1.ClusterState_CLUSTER_STATE_UNSPECIFIED: centralapi.Cluster_DISPLAY_UNSPECIFIED,
		openapicommonv1beta1.ClusterState_CREATING:                  centralapi.Cluster_DISPLAY_CREATING,
		openapicommonv1beta1.ClusterState_DELETING:                  centralapi.Cluster_DISPLAY_UNAVAILABLE,
		openapicommonv1beta1.ClusterState_ACTIVE:                    centralapi.Cluster_DISPLAY_AVAILABLE,
		openapicommonv1beta1.ClusterState_INACTIVE:                  centralapi.Cluster_DISPLAY_UNAVAILABLE,
		openapicommonv1beta1.ClusterState_RESTORING:                 centralapi.Cluster_DISPLAY_RESTORING,
		openapicommonv1beta1.ClusterState_MAINTENANCE:               centralapi.Cluster_DISPLAY_MAINTAINING,
		openapicommonv1beta1.ClusterState_MODIFYING:                 centralapi.Cluster_DISPLAY_MODIFYING,
		openapicommonv1beta1.ClusterState_PAUSED:                    centralapi.Cluster_DISPLAY_PAUSED,
		openapicommonv1beta1.ClusterState_PAUSING:                   centralapi.Cluster_DISPLAY_PAUSING,
		openapicommonv1beta1.ClusterState_RESUMING:                  centralapi.Cluster_DISPLAY_RESUMING,
		openapicommonv1beta1.ClusterState_UPGRADING:                 centralapi.Cluster_DISPLAY_MODIFYING,
		openapicommonv1beta1.ClusterState_IMPORTING:                 centralapi.Cluster_DISPLAY_IMPORTING,
	}
)

func convertServerlessCluster(cluster *openapiserverlessv1beta1.Cluster) (*centralapi.Cluster, error) {
	if cluster == nil {
		return nil, errors.New("cluster is nil")
	}

	if cluster.Endpoints == nil {
		cluster.Endpoints = &openapiserverlessv1beta1.Cluster_Endpoints{}
	}

	clusterID, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("parse cluster id failed: %w", err)
	}

	if cluster.Endpoints.Public == nil {
		cluster.Endpoints.Public = &openapiserverlessv1beta1.Cluster_Endpoints_Public{Host: "", Port: 0}
	}

	if cluster.Endpoints.Private == nil {
		cluster.Endpoints.Private = &openapiserverlessv1beta1.Cluster_Endpoints_Private{Host: "", Port: 0}
	}

	hasSetPwd := false
	var availableFeatures []centralapi.Cluster_AvailableFeature
	if cluster.Annotations != nil {
		if v, ok := cluster.Annotations[HasSetPwdKey]; ok {
			hasSetPwd, err = strconv.ParseBool(v)
			if err != nil {
				return nil, fmt.Errorf("parse has set password failed: %w", err)
			}
		}

		if v, ok := cluster.Annotations[AvailableFeaturesKey]; ok {
			for feature := range strings.SplitSeq(v, ",") {
				switch feature {
				case globalcommonv1.AvailableFeature_DELEGATE_USER.String():
					availableFeatures = append(availableFeatures, centralapi.Cluster_DELEGATE_USER)
				case globalcommonv1.AvailableFeature_DISABLE_PUBLIC_LB.String():
					availableFeatures = append(availableFeatures, centralapi.Cluster_DISABLE_PUBLIC_LB)
				case globalcommonv1.AvailableFeature_PRIVATELINK.String():
					availableFeatures = append(availableFeatures, centralapi.Cluster_PRIVATELINK)
				}
			}
		}
	}

	provider := utils.AwsProvider
	switch cluster.Region.CloudProvider {
	case openapicommonv1beta1.Region_aws:
		provider = utils.AwsProvider
	case openapicommonv1beta1.Region_gcp:
		provider = utils.GcpProvider
	case openapicommonv1beta1.Region_azure:
		provider = utils.AzureProvider
	case openapicommonv1beta1.Region_alicloud:
		provider = utils.AlicloudProvider
	}

	return &centralapi.Cluster{
		Id:              clusterID,
		Name:            cluster.DisplayName,
		Status:          statusMap[cluster.State],
		SecondaryStatus: centralapi.Cluster_HEALTHY,
		Version:         cluster.Version,
		Provider:        provider,
		Region:          utils.ConvertRegionName(cluster.Region.Name),
		RegionName:      cluster.Region.DisplayName,
		CreatedAt:       cluster.CreateTime,
		CreatedBy:       &centralapi.User{Email: cluster.CreatedBy},
		Endpoint: &centralapi.Endpoint{
			Ready:   true,
			Address: cluster.Endpoints.Public.Host,
			Port:    cluster.Endpoints.Public.Port,
			Token:   cluster.UserPrefix,
		},
		VpcCidrBlock: "",
		Capacity:     1,
		ClusterComponents: &centralapi.ClusterComponents{
			Tikv: &centralapi.Component{
				Replicas: 1,
				NodeProfile: &centralapi.NodeProfile{
					Compute:     &centralapi.Compute{Cpu: 0, MemoryMi: 0},
					StorageGi:   0,
					Name:        ShareNodeName,
					DisplayName: ShareNodeDisplayName,
				},
				ModifyProgress: &centralapi.Component_ModifyProgress{},
			},
			Tidb: &centralapi.Component{
				Replicas: 1,
				NodeProfile: &centralapi.NodeProfile{
					Compute:     &centralapi.Compute{Cpu: 0, MemoryMi: 0},
					StorageGi:   0,
					Name:        ShareNodeName,
					DisplayName: ShareNodeDisplayName,
				},
				ModifyProgress: &centralapi.Component_ModifyProgress{},
			},
			Tiflash: &centralapi.Component{
				Replicas: 1,
				NodeProfile: &centralapi.NodeProfile{
					Compute:     &centralapi.Compute{Cpu: 0, MemoryMi: 0},
					StorageGi:   0,
					Name:        ShareNodeName,
					DisplayName: ShareNodeDisplayName,
				},
				ModifyProgress: &centralapi.Component_ModifyProgress{},
			},
		},
		CreateProgress:  centralapi.Cluster_COMPLETED,
		PrometheusReady: true,
		PrivateEndpoint: &centralapi.Endpoint{
			Ready:   cluster.Endpoints.Private.Host != "",
			Address: cluster.Endpoints.Private.Host,
			Port:    cluster.Endpoints.Private.Port,
			Token:   cluster.UserPrefix,
		},
		Restored:                 false,
		ImportId:                 0,
		LastErrors:               []*centralapi.LastError{},
		Replications:             []*centralapi.Replication{},
		ClusterType:              centralapi.Cluster_COMMON,
		WriteStatus:              centralapi.WriteStatus_WRITABLE,
		IsDevTier:                true,
		CrossAccountDeploymentId: 0,
		DisplayStatus:            displayStatusMap[cluster.State],
		HasSetPwd:                hasSetPwd,
		DevTierType:              centralapi.Cluster_DEVTIER_SERVERLESS,
		Features:                 availableFeatures,
	}, nil
}

func convertDedicatedCluster(cluster *openapidedicatedv1beta1.Cluster) (*centralapi.Cluster, error) {
	if cluster == nil {
		return nil, errors.New("cluster is nil")
	}

	clusterID, err := strconv.ParseUint(cluster.ClusterId, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("parse cluster id failed: %w", err)
	}

	hasSetPwd := false
	availableFeatures := []centralapi.Cluster_AvailableFeature{
		centralapi.Cluster_THREE_LAYER_ARCH,
	}
	if cluster.Annotations != nil {
		if v, ok := cluster.Annotations[HasSetPwdKey]; ok {
			hasSetPwd, err = strconv.ParseBool(v)
			if err != nil {
				return nil, fmt.Errorf("parse has set password failed: %w", err)
			}
		}

		if v, ok := cluster.Annotations[AvailableFeaturesKey]; ok {
			for feature := range strings.SplitSeq(v, ",") {
				switch feature {
				case globalcommonv1.AvailableFeature_DELEGATE_USER.String():
					availableFeatures = append(availableFeatures, centralapi.Cluster_DELEGATE_USER)
				case globalcommonv1.AvailableFeature_DISABLE_PUBLIC_LB.String():
					availableFeatures = append(availableFeatures, centralapi.Cluster_DISABLE_PUBLIC_LB)
				case globalcommonv1.AvailableFeature_PRIVATELINK.String():
					availableFeatures = append(availableFeatures, centralapi.Cluster_PRIVATELINK)
				}
			}
		}
	}

	components := &centralapi.ClusterComponents{}
	var capacity uint32
	if cluster.GetTidbNodeSetting() != nil && len(cluster.GetTidbNodeSetting().GetTidbNodeGroups()) > 0 {
		components.Tidb = &centralapi.Component{
			NodeProfile: &centralapi.NodeProfile{
				Compute: &centralapi.Compute{
					Cpu:      float32(utils.GetCpuNum(cluster.GetTidbNodeSetting().GetNodeSpecKey())),
					MemoryMi: uint32(utils.GetMemorySize(cluster.GetTidbNodeSetting().GetNodeSpecKey()) * 1024),
				},
				StorageGi:   0,
				Name:        cluster.GetTidbNodeSetting().GetNodeSpecKey(),
				DisplayName: cluster.GetTidbNodeSetting().GetNodeSpecDisplayName(),
			},
		}

		for _, group := range cluster.GetTidbNodeSetting().GetTidbNodeGroups() {
			components.Tidb.Replicas += uint32(group.GetNodeCount())
			if group.GetNodeChangingProgress() != nil {
				if components.Tidb.ModifyProgress == nil {
					components.Tidb.ModifyProgress = &centralapi.Component_ModifyProgress{}
				}
				components.Tidb.ModifyProgress.ExpectedQuantity += group.GetNodeCount()
				components.Tidb.ModifyProgress.UptodateQuantity += group.GetNodeChangingProgress().
					GetMatchingNodeSpecNodeCount()
				components.Tidb.ModifyProgress.RemainingDeletionQuantity += group.GetNodeChangingProgress().
					GetRemainingDeletionNodeCount()
			}
		}
	}

	if cluster.GetTikvNodeSetting() != nil {
		components.Tikv = &centralapi.Component{
			Replicas: uint32(cluster.TikvNodeSetting.GetNodeCount()),
			NodeProfile: &centralapi.NodeProfile{
				Compute: &centralapi.Compute{
					Cpu:      float32(utils.GetCpuNum(cluster.GetTikvNodeSetting().GetNodeSpecKey())),
					MemoryMi: uint32(utils.GetMemorySize(cluster.GetTikvNodeSetting().GetNodeSpecKey()) * 1024),
				},
				StorageGi:   uint32(cluster.GetTikvNodeSetting().GetStorageSizeGi()),
				Name:        cluster.GetTikvNodeSetting().GetNodeSpecKey(),
				DisplayName: cluster.GetTikvNodeSetting().GetNodeSpecDisplayName(),
			},
		}

		if cluster.GetTikvNodeSetting().GetNodeChangingProgress() != nil {
			components.Tikv.ModifyProgress = &centralapi.Component_ModifyProgress{
				ExpectedQuantity: cluster.TikvNodeSetting.GetNodeCount(),
				UptodateQuantity: cluster.GetTikvNodeSetting().
					GetNodeChangingProgress().
					GetMatchingNodeSpecNodeCount(),
				RemainingDeletionQuantity: cluster.GetTikvNodeSetting().
					GetNodeChangingProgress().
					GetRemainingDeletionNodeCount(),
			}
		}

		capacity = uint32(cluster.TikvNodeSetting.GetNodeCount() * cluster.TikvNodeSetting.GetStorageSizeGi())
	}

	if cluster.GetTiflashNodeSetting() != nil && cluster.GetTiflashNodeSetting().GetNodeCount() > 0 {
		components.Tiflash = &centralapi.Component{
			Replicas: uint32(cluster.TiflashNodeSetting.GetNodeCount()),
			NodeProfile: &centralapi.NodeProfile{
				Compute: &centralapi.Compute{
					Cpu:      float32(utils.GetCpuNum(cluster.GetTiflashNodeSetting().GetNodeSpecKey())),
					MemoryMi: uint32(utils.GetMemorySize(cluster.GetTiflashNodeSetting().GetNodeSpecKey()) * 1024),
				},
				StorageGi:   uint32(cluster.GetTiflashNodeSetting().GetStorageSizeGi()),
				Name:        cluster.GetTiflashNodeSetting().GetNodeSpecKey(),
				DisplayName: cluster.GetTiflashNodeSetting().GetNodeSpecDisplayName(),
			},
		}

		if cluster.GetTiflashNodeSetting().GetNodeChangingProgress() != nil {
			components.Tiflash.ModifyProgress = &centralapi.Component_ModifyProgress{
				ExpectedQuantity: cluster.TiflashNodeSetting.GetNodeCount(),
				UptodateQuantity: cluster.GetTiflashNodeSetting().
					GetNodeChangingProgress().
					GetMatchingNodeSpecNodeCount(),
				RemainingDeletionQuantity: cluster.GetTiflashNodeSetting().
					GetNodeChangingProgress().
					GetRemainingDeletionNodeCount(),
			}
		}

		capacity += uint32(cluster.TiflashNodeSetting.GetNodeCount() * cluster.TiflashNodeSetting.GetStorageSizeGi())
	}

	provider := utils.AwsProvider
	switch strings.ToLower(cluster.GetCloudProvider().String()) {
	case utils.AwsProvider:
		provider = utils.AwsProvider
	case utils.GcpProvider:
		provider = utils.GcpProvider
	case utils.AzureProvider:
		provider = utils.AzureProvider
	}

	// build endpoint
	var (
		endpoint *centralapi.Endpoint
		private  *centralapi.Endpoint
	)

	for _, group := range cluster.GetTidbNodeSetting().GetTidbNodeGroups() {
		if group.GetIsDefaultGroup() {
			for _, e := range group.GetEndpoints() {
				switch e.GetConnectionType() {
				case openapidedicatedv1beta1.TidbNodeGroup_Endpoint_PUBLIC:
					endpoint = &centralapi.Endpoint{
						Ready:   e.GetHost() != "",
						Address: e.GetHost(),
						Port:    e.GetPort(),
						Token:   "",
					}
				case openapidedicatedv1beta1.TidbNodeGroup_Endpoint_PRIVATE_ENDPOINT:
					private = &centralapi.Endpoint{
						Ready:   e.GetHost() != "",
						Address: e.GetHost(),
						Port:    e.GetPort(),
						Token:   "",
					}
				}
			}
		}
	}

	c := &centralapi.Cluster{
		Id:                       clusterID,
		Name:                     cluster.DisplayName,
		Status:                   statusMap[cluster.State],
		SecondaryStatus:          centralapi.Cluster_HEALTHY,
		Version:                  cluster.Version,
		Provider:                 provider,
		Region:                   utils.ConvertRegionName(cluster.GetRegionId()),
		RegionName:               cluster.GetRegionDisplayName(),
		CreatedAt:                cluster.CreateTime,
		CreatedBy:                &centralapi.User{Email: cluster.CreatedBy},
		VpcCidrBlock:             "",
		Capacity:                 capacity,
		ClusterComponents:        components,
		CreateProgress:           centralapi.Cluster_COMPLETED,
		PrometheusReady:          true,
		Restored:                 false,
		ImportId:                 0,
		LastErrors:               []*centralapi.LastError{},
		Replications:             []*centralapi.Replication{},
		ClusterType:              centralapi.Cluster_COMMON,
		WriteStatus:              centralapi.WriteStatus_WRITABLE,
		IsDevTier:                false,
		CrossAccountDeploymentId: 0,
		DisplayStatus:            displayStatusMap[cluster.State],
		HasSetPwd:                hasSetPwd,
		Features:                 availableFeatures,
		Endpoint:                 endpoint,
		PrivateEndpoint:          private,
	}

	return c, nil
}
