package cluster

import (
	"strings"

	"github.com/samber/lo"
	"google.golang.org/protobuf/types/known/timestamppb"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	customstatussvc "github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/app/internalapi/customstatus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/spu/storagetype"
)

func cvtToMgmtCluster(cluster *entity.Cluster, statuses []*entity.ClusterCustomStatus) *tidbmgmtservicev1.Cluster {
	if cluster == nil {
		return nil
	}

	var deletedTime *timestamppb.Timestamp
	if cluster.DeletedAt != nil {
		deletedTime = timestamppb.New(*cluster.DeletedAt)
	}

	statusesMgmt := lo.Map(
		statuses,
		func(status *entity.ClusterCustomStatus, _ int) *tidbmgmtservicev1.ClusterCustomStatus {
			return customstatussvc.CvtCustomStatusMgmt(status)
		},
	)

	originalProjectId := cluster.OriginalProjectId
	if originalProjectId == 0 {
		// if the cluster deleted before introducing OriginalProjectId field, we didn't supplement the value in DB,
		// so we set OriginalProjectId to ProjectId to be compatible.
		originalProjectId = cluster.ProjectId
	}

	mgmtCluster := &tidbmgmtservicev1.Cluster{
		ClusterId:         cluster.ClusterId,
		ProjectId:         cluster.ProjectId,
		OriginalProjectId: originalProjectId,
		OrganizationId:    cluster.OrganizationId,
		ClusterName:       cluster.DisplayName,
		CloudProvider:     cluster.CloudProvider.String(),
		Region:            cluster.Region,
		CreatedBy:         cluster.UserId,
		CustomStatuses:    statusesMgmt,
		CreateTime:        timestamppb.New(cluster.CreatedAt),
		UpdateTime:        timestamppb.New(cluster.UpdatedAt),
		DeleteTime:        deletedTime,
		RuVersion:         cluster.RuVersion,
		Tags:              common.CvtEntityTagsToOpenAPITags(cluster.Tags),
		Labels:            cluster.Labels,
		ServicePlan:       cluster.ServicePlan,
	}

	switch cluster.DeployType {
	case entity.DeployTypeServerless:
		// global cluster info from global service
		serverlessGlobal, _ := cluster.GetServerlessCluster()
		// Override project_id in the embedded global cluster with the authoritative value from tidb-mgmt-service.
		if serverlessGlobal != nil {
			serverlessGlobal.ProjectId = cluster.ProjectId
		}
		mgmtCluster.DeployType = tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS
		mgmtCluster.ClusterInfo = &tidbmgmtservicev1.Cluster_ServerlessCluster{ServerlessCluster: serverlessGlobal}
		if mgmtCluster.ServicePlan == openapicommonv1beta1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
			mgmtCluster.ServicePlan = openapicommonv1beta1.ServicePlan_Starter
			if serverlessGlobal.GetCluster().GetAutoScaling().GetMaxRcu() > 0 {
				mgmtCluster.ServicePlan = openapicommonv1beta1.ServicePlan_Essential
			}
		}

	case entity.DeployTypeDedicated:
		// global cluster info from global service
		dedicatedGlobal, _ := cluster.GetDedicatedCluster()
		// Override project_id in the embedded global cluster with the authoritative value from tidb-mgmt-service.
		if dedicatedGlobal != nil {
			dedicatedGlobal.ProjectId = cluster.ProjectId
		}
		mgmtCluster.DeployType = tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED
		mgmtCluster.ClusterInfo = &tidbmgmtservicev1.Cluster_DedicatedCluster{DedicatedCluster: dedicatedGlobal}
		// build storage type
		tikvComp, ok := lo.Find(
			dedicatedGlobal.GetClusterComponents(),
			func(i *globaldedicatedv1.Cluster_ClusterComponent) bool {
				return i.GetComponentType() == globaldedicatedv1.ComponentType_TIKV
			},
		)
		if ok {
			dataVt := tikvComp.GetStorages()[globaldedicatedv1.Sku_DATA.String()].GetVolumeType()
			raftVt := tikvComp.GetStorages()[globaldedicatedv1.Sku_RAFT_LOG.String()].GetVolumeType()
			mgmtCluster.TikvStorageType = storagetype.BuildCurrentTikvStorageType(dataVt, raftVt)
		}
		tiflashComp, ok := lo.Find(
			dedicatedGlobal.GetClusterComponents(),
			func(i *globaldedicatedv1.Cluster_ClusterComponent) bool {
				return i.GetComponentType() == globaldedicatedv1.ComponentType_TIFLASH
			},
		)
		if ok {
			dataVt := tiflashComp.GetStorages()[globaldedicatedv1.Sku_DATA.String()].GetVolumeType()
			mgmtCluster.TiflashStorageType = lo.ToPtr(
				storagetype.BuildCurrentTiflashStorageType(dataVt, globaldedicatedv1.StorageMu_VOLUME_TYPE_UNSPECIFIED),
			)
		}
	case entity.DeployTypeNextGen:
		// global cluster info from global service
		nextGenGlobal, _ := cluster.GetNextGenCluster()
		mgmtCluster.DeployType = tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN
		mgmtCluster.ClusterInfo = &tidbmgmtservicev1.Cluster_NextGenCluster{NextGenCluster: nextGenGlobal}
		if nextGenGlobal != nil && nextGenGlobal.GetPlanReplayerStatus() != nil {
			mgmtCluster.PlanReplayerStatus = &tidbmgmtservicev1.PlanReplayerStatus{
				Enabled:    nextGenGlobal.GetPlanReplayerStatus().GetEnabled(),
				ExpireTime: nextGenGlobal.GetPlanReplayerStatus().GetExpireTime(),
			}
		}
	}

	return mgmtCluster
}

func cvtFilter(filter *tidbmgmtservicev1.ClusterFilter) *repository.ClusterFilter {
	if filter == nil {
		return &repository.ClusterFilter{
			View:         entity.ClusterViewClusterViewBasic,
			QueryDeleted: true, // default show deleted cluster
		}
	}

	var deployType entity.DeployType
	switch filter.GetClusterType() {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		deployType = entity.DeployTypeServerless
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		deployType = entity.DeployTypeDedicated
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		deployType = entity.DeployTypeNextGen
	}

	queryDeleted := cvtFilterToQueryDeleted(filter)

	// count cluster from domain service.
	var view entity.ClusterView
	if filter.View == 0 {
		view = entity.ClusterViewClusterViewBasic
	} else {
		view = filter.View
	}
	clusterFilter := &repository.ClusterFilter{
		ClusterIds:        filter.ClusterIds,
		ProjectIds:        filter.ProjectIds,
		ExcludeProjectIds: filter.ExcludeProjectIds,
		OrganizationIds:   filter.TenantIds,
		CloudProviders: lo.FlatMap(filter.CloudProviders, func(provider string, _ int) []string {
			return []string{strings.ToLower(provider)}
		}),
		Regions:       filter.Regions,
		View:          view,
		States:        filter.States,
		ExcludeStates: filter.ExcludeStates,
		QueryDeleted:  queryDeleted,
		DeployType:    deployType,
		RefreshCache:  filter.RefreshCache,
	}
	if filter.ServicePlan != openapicommonv1beta1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		clusterFilter.ServicePlans = []string{filter.ServicePlan.String()}
	}
	if len(filter.GetTags()) > 0 {
		tags, err := entity.NormalizeCanonicalTags(filter.GetTags())
		if err == nil {
			clusterFilter.TagCanonicalNames = entity.CanonicalTags(tags)
		}
	}
	return clusterFilter
}

func cvtFilterToQueryDeleted(filter *tidbmgmtservicev1.ClusterFilter) bool {
	if filter == nil {
		return true
	}

	// default query deleted cluster
	queryDeleted := true

	// if delete is specified, the specified parameter is used
	if filter.Deleted != nil {
		queryDeleted = *filter.Deleted
	}

	// converting status to delete
	if len(filter.States) > 0 {
		// if filter contain deleted or deleting status, query deleted cluster
		queryDeleted = lo.ContainsBy(filter.States, func(state openapicommonv1beta1.ClusterState) bool {
			return state == openapicommonv1beta1.ClusterState_DELETED ||
				state == openapicommonv1beta1.ClusterState_DELETING
		})
	}

	// converting exclude status to delete
	if len(filter.ExcludeStates) > 0 {
		// if filter contain deleted or deleting status, query deleted cluster
		if lo.ContainsBy(filter.ExcludeStates, func(state openapicommonv1beta1.ClusterState) bool {
			return state == openapicommonv1beta1.ClusterState_DELETED ||
				state == openapicommonv1beta1.ClusterState_DELETING
		}) {
			queryDeleted = false
		}
	}

	return queryDeleted
}
