package v1beta

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

	openapilegacy "github.com/tidbcloud/idl/pbgen/proto/openapi/cm"

	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"
	mgmtgrpc "github.com/tidbcloud/tidb-management-service/tempo-wrapper/pkg/grpc/mgmt"
)

const (
	shareNodeSize = "Shared0"
	projectKey    = "tidb.cloud/project"
)

// ClusterStatus_CLUSTER_STATUS_UNSPECIFIED ClusterStatus = 0
// ClusterStatus_AVAILABLE                  ClusterStatus = 1
// ClusterStatus_CREATING                   ClusterStatus = 2
// ClusterStatus_MODIFYING                  ClusterStatus = 3
// ClusterStatus_PAUSED                     ClusterStatus = 4
// ClusterStatus_RESUMING                   ClusterStatus = 5
// ClusterStatus_UNAVAILABLE                ClusterStatus = 6
// ClusterStatus_IMPORTING                  ClusterStatus = 7
// ClusterStatus_MAINTAINING                ClusterStatus = 8

var statusMap = map[openapicommonv1beta1.ClusterState]openapilegacy.ClusterStatus{
	openapicommonv1beta1.ClusterState_CLUSTER_STATE_UNSPECIFIED: openapilegacy.ClusterStatus_CLUSTER_STATUS_UNSPECIFIED,
	openapicommonv1beta1.ClusterState_CREATING:                  openapilegacy.ClusterStatus_CREATING,
	openapicommonv1beta1.ClusterState_DELETING:                  openapilegacy.ClusterStatus_UNAVAILABLE,
	openapicommonv1beta1.ClusterState_ACTIVE:                    openapilegacy.ClusterStatus_AVAILABLE,
	openapicommonv1beta1.ClusterState_RESTORING:                 openapilegacy.ClusterStatus_AVAILABLE,
	openapicommonv1beta1.ClusterState_MAINTENANCE:               openapilegacy.ClusterStatus_MAINTAINING,
	openapicommonv1beta1.ClusterState_INACTIVE:                  openapilegacy.ClusterStatus_UNAVAILABLE,
	openapicommonv1beta1.ClusterState_IMPORTING:                 openapilegacy.ClusterStatus_IMPORTING,
	openapicommonv1beta1.ClusterState_MODIFYING:                 openapilegacy.ClusterStatus_MODIFYING,
	openapicommonv1beta1.ClusterState_PAUSING:                   openapilegacy.ClusterStatus_PAUSING,
	openapicommonv1beta1.ClusterState_PAUSED:                    openapilegacy.ClusterStatus_PAUSED,
	openapicommonv1beta1.ClusterState_UPGRADING:                 openapilegacy.ClusterStatus_MODIFYING,
	openapicommonv1beta1.ClusterState_RESUMING:                  openapilegacy.ClusterStatus_RESUMING,
}

func convertServerlessCluster(cluster *openapiserverlessv1beta1.Cluster) (*openapilegacy.ClusterItem, 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)
	}

	projectID, err := strconv.ParseUint(cluster.Labels[projectKey], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("convert project 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}
	}

	provider := openapilegacy.CloudProvider_AWS
	switch cluster.Region.CloudProvider {
	case openapicommonv1beta1.Region_aws:
		provider = openapilegacy.CloudProvider_AWS
	case openapicommonv1beta1.Region_gcp:
		provider = openapilegacy.CloudProvider_GCP
	}

	return &openapilegacy.ClusterItem{
		Id:              clusterID,
		ProjectId:       projectID,
		Name:            cluster.DisplayName,
		ClusterType:     openapilegacy.ClusterType_DEVELOPER,
		CloudProvider:   provider,
		Region:          utils.ConvertRegionName(cluster.Region.Name),
		CreateTimestamp: cluster.CreateTime.Seconds,
		Config: &openapilegacy.GetClusterConfig{
			Port: cluster.Endpoints.Public.Port,
			Components: &openapilegacy.ClusterComponents{
				Tidb: &openapilegacy.TiDBComponent{
					NodeSize:     shareNodeSize,
					NodeQuantity: 1,
				},
				Tikv: &openapilegacy.TiKVComponent{
					NodeSize:     shareNodeSize,
					NodeQuantity: 1,
				},
				Tiflash: &openapilegacy.TiFlashComponent{
					NodeSize:     shareNodeSize,
					NodeQuantity: 1,
				},
			},
		},
		Status: &openapilegacy.ClusterItemStatus{
			TidbVersion:   cluster.Version,
			ClusterStatus: statusMap[cluster.State],
			NodeMap:       &openapilegacy.ClusterNodeMap{},
			ConnectionStrings: &openapilegacy.ClusterConnectionStrings{
				DefaultUser: cluster.UserPrefix + ".root",
				Standard: &openapilegacy.StandardConnection{
					Host: cluster.Endpoints.Public.Host,
					Port: cluster.Endpoints.Public.Port,
				},
				VpcPeering: &openapilegacy.VPCPeeringConnection{
					Host: cluster.Endpoints.Private.Host,
					Port: cluster.Endpoints.Private.Port,
				},
			},
		},
	}, nil
}

func convertDedicatedCluster(
	ctx context.Context,
	cluster *openapidedicatedv1beta1.Cluster,
) (*openapilegacy.ClusterItem, 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)
	}

	projectID, err := strconv.ParseUint(cluster.Labels[projectKey], 10, 64)
	if err != nil {
		return nil, fmt.Errorf("convert project id failed: %w", err)
	}

	components := &openapilegacy.ClusterComponents{}

	var defaultTidbGroup *openapidedicatedv1beta1.TidbNodeGroup
	if cluster.GetTidbNodeSetting() != nil && len(cluster.TidbNodeSetting.GetTidbNodeGroups()) > 0 {
		var quantity int32
		for _, tidbGroup := range cluster.TidbNodeSetting.GetTidbNodeGroups() {
			quantity += tidbGroup.GetNodeCount()
			if tidbGroup.IsDefaultGroup {
				defaultTidbGroup = tidbGroup
			}
		}

		components.Tidb = &openapilegacy.TiDBComponent{
			NodeSize:     cluster.GetTidbNodeSetting().GetNodeSpecKey(),
			NodeQuantity: quantity,
		}
	}

	if cluster.GetTikvNodeSetting() != nil {
		components.Tikv = &openapilegacy.TiKVComponent{
			NodeSize:       cluster.GetTikvNodeSetting().GetNodeSpecKey(),
			StorageSizeGib: cluster.GetTikvNodeSetting().GetStorageSizeGi(),
			NodeQuantity:   cluster.GetTikvNodeSetting().GetNodeCount(),
		}
	}

	if cluster.GetTiflashNodeSetting() != nil && cluster.GetTiflashNodeSetting().GetNodeCount() > 0 {
		components.Tiflash = &openapilegacy.TiFlashComponent{
			NodeSize:       cluster.GetTiflashNodeSetting().GetNodeSpecKey(),
			StorageSizeGib: cluster.GetTiflashNodeSetting().GetStorageSizeGi(),
			NodeQuantity:   cluster.GetTiflashNodeSetting().GetNodeCount(),
		}
	}

	var provider openapilegacy.CloudProvider
	switch strings.ToLower(cluster.CloudProvider.String()) {
	case utils.AwsProvider:
		provider = openapilegacy.CloudProvider_AWS
	case utils.GcpProvider:
		provider = openapilegacy.CloudProvider_GCP
	default:
		provider = openapilegacy.CloudProvider_CLOUD_PROVIDER_UNSPECIFIED // todo support other cloud provider
	}

	var nodeMap *openapilegacy.ClusterNodeMap
	if cluster.GetState() != openapicommonv1beta1.ClusterState_CREATING {
		nodeMap, err = convertDedicatedClusterNodeMap(ctx, cluster)
		if err != nil {
			return nil, fmt.Errorf("get cluster node map failed: %w", err)
		}
	}

	c := &openapilegacy.ClusterItem{
		Id:              clusterID,
		ProjectId:       projectID,
		Name:            cluster.DisplayName,
		ClusterType:     openapilegacy.ClusterType_DEDICATED,
		CloudProvider:   provider,
		Region:          utils.ConvertRegionName(cluster.RegionId),
		CreateTimestamp: cluster.CreateTime.Seconds,
		Config: &openapilegacy.GetClusterConfig{
			Components: components,
		},
		Status: &openapilegacy.ClusterItemStatus{
			TidbVersion:   cluster.Version,
			ClusterStatus: statusMap[cluster.State],
			NodeMap:       nodeMap,
			ConnectionStrings: &openapilegacy.ClusterConnectionStrings{
				DefaultUser: "root",
			},
		},
	}

	for _, endpoint := range defaultTidbGroup.GetEndpoints() {
		switch endpoint.GetConnectionType() {
		case openapidedicatedv1beta1.TidbNodeGroup_Endpoint_PUBLIC:
			c.Config.Port = endpoint.GetPort()
			c.Status.ConnectionStrings.Standard = &openapilegacy.StandardConnection{
				Host: endpoint.GetHost(),
				Port: endpoint.GetPort(),
			}
		case openapidedicatedv1beta1.TidbNodeGroup_Endpoint_VPC_PEERING:
			c.Status.ConnectionStrings.VpcPeering = &openapilegacy.VPCPeeringConnection{
				Host: endpoint.GetHost(),
				Port: endpoint.GetPort(),
			}
		}
	}

	return c, nil
}

func convertDedicatedClusterNodeMap(
	ctx context.Context,
	cluster *openapidedicatedv1beta1.Cluster,
) (*openapilegacy.ClusterNodeMap, error) {
	if cluster == nil {
		return nil, errors.New("cluster is nil")
	}

	nodeInfo, err := mgmtgrpc.GetDedicatedClusterNodeInfo(ctx, &openapidedicatedv1beta1.ListNodeInstancesRequest{
		ClusterId: cluster.ClusterId,
	})
	if err != nil {
		return nil, err
	}

	nodeMap := &openapilegacy.ClusterNodeMap{}

	if nodeInfo == nil || nodeInfo.GetTotalSize() == 0 {
		return nodeMap, nil
	}

	for _, node := range nodeInfo.GetNodeInstances() {
		switch node.ComponentType {
		case openapidedicatedv1beta1.ComponentType_TIDB:
			if nodeMap.Tidb == nil {
				nodeMap.Tidb = make([]*openapilegacy.TiDBNodeMap, 0)
			}
			nodeMap.Tidb = append(nodeMap.Tidb, &openapilegacy.TiDBNodeMap{
				NodeName:         node.InstanceId,
				AvailabilityZone: node.AvailabilityZone,
				NodeSize: fmt.Sprintf(
					"%dC%dG",
					node.VCpu,
					node.GetMemorySizeGi(),
				), // cpu: 8  memory: 16 => 8C16G
				VcpuNum:  node.GetVCpu(),
				RamBytes: int64(node.GetMemorySizeGi()) * 1024 * 1024 * 1024,
				Status:   cvtNodeState(node.State),
			})
		case openapidedicatedv1beta1.ComponentType_TIKV:
			if nodeMap.Tikv == nil {
				nodeMap.Tikv = make([]*openapilegacy.TiKVNodeMap, 0)
			}
			nodeMap.Tikv = append(nodeMap.Tikv, &openapilegacy.TiKVNodeMap{
				NodeName:         node.InstanceId,
				AvailabilityZone: node.AvailabilityZone,
				NodeSize: fmt.Sprintf(
					"%dC%dG",
					node.VCpu,
					node.GetMemorySizeGi(),
				), // cpu: 8  memory: 16 => 8C16G
				VcpuNum:        node.GetVCpu(),
				RamBytes:       int64(node.GetMemorySizeGi()) * 1024 * 1024 * 1024,
				StorageSizeGib: node.GetStorageSizeGi(),
				Status:         cvtNodeState(node.State),
			})
		case openapidedicatedv1beta1.ComponentType_TIFLASH:
			if nodeMap.Tiflash == nil {
				nodeMap.Tiflash = make([]*openapilegacy.TiFlashNodeMap, 0)
			}
			nodeMap.Tiflash = append(nodeMap.Tiflash, &openapilegacy.TiFlashNodeMap{
				NodeName:         node.InstanceId,
				AvailabilityZone: node.AvailabilityZone,
				NodeSize: fmt.Sprintf(
					"%dC%dG",
					node.VCpu,
					node.GetMemorySizeGi(),
				), // cpu: 8  memory: 16 => 8C16G
				VcpuNum:        node.GetVCpu(),
				RamBytes:       int64(node.GetMemorySizeGi()) * 1024 * 1024 * 1024,
				StorageSizeGib: node.GetStorageSizeGi(),
				Status:         cvtNodeState(node.State),
			})
		}
	}

	return nodeMap, nil
}

func cvtNodeState(state openapidedicatedv1beta1.NodeInstance_State) openapilegacy.NodeStatus {
	switch state {
	case openapidedicatedv1beta1.NodeInstance_AVAILABLE:
		return openapilegacy.NodeStatus_NODE_STATUS_AVAILABLE
	case openapidedicatedv1beta1.NodeInstance_UNAVAILABLE:
		return openapilegacy.NodeStatus_NODE_STATUS_UNAVAILABLE
	case openapidedicatedv1beta1.NodeInstance_CREATING:
		return openapilegacy.NodeStatus_NODE_STATUS_CREATING
	case openapidedicatedv1beta1.NodeInstance_DELETING:
		return openapilegacy.NodeStatus_NODE_STATUS_DELETING
	}
	return openapilegacy.NodeStatus_NODE_STATUS_UNSPECIFIED
}
