package cluster

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"time"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/timestamppb"
	"gorm.io/gorm"

	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	regionalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapiserverlessv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/serverless/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (c serverImpl) GetCluster(
	ctx context.Context,
	req *openapiserverlessv1beta1.GetClusterRequest,
) (res *openapiserverlessv1beta1.Cluster, err error) {
	startTime := time.Now()
	defer func() {
		recordQueryClusterAPIMetrics(apiGetCluster, time.Since(startTime).Seconds(), err)
	}()

	cluster := &entity.Cluster{}
	cluster.ClusterId, err = strconv.ParseUint(req.ClusterId, 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cluster id")
	}

	// get from domain service
	// Default to full view
	var view entity.ClusterView
	switch req.GetView() {
	case openapiserverlessv1beta1.ClusterView_BASIC:
		view = entity.ClusterViewClusterViewBasic
	case openapiserverlessv1beta1.ClusterView_FULL,
		openapiserverlessv1beta1.ClusterView_CLUSTER_VIEW_UNSPECIFIED:
		view = entity.ClusterViewClusterViewFull
	}

	cluster, err = c.clusterSvc.GetCluster(ctx, cluster.ClusterId, view)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerror.NewNotFound(err, "cluster not found")
		}
		return nil, bizerror.NewInternal(err, "failed to get cluster")
	}

	// check custom status
	var (
		customStatusMap map[uint64][]*entity.ClusterCustomStatus
		customStatuses  []*entity.ClusterCustomStatus
	)
	customStatusMap, err = c.customStatusSvc.ListClusterCustomStatus(ctx, []uint64{cluster.ClusterId}, nil, false)
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		tilog.Warn(ctx, "failed to get custom status", zap.String("cluster_id", req.ClusterId), zap.Error(err))
	}
	customStatuses = customStatusMap[cluster.ClusterId]

	res, err = buildClusterFromEntity(cluster, customStatuses, req.View)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get cluster")
	}
	return res, nil
}

func buildClusterFromEntity(
	cluster *entity.Cluster,
	customStatuses []*entity.ClusterCustomStatus,
	view openapiserverlessv1beta1.ClusterView,
) (*openapiserverlessv1beta1.Cluster, error) {
	if cluster == nil {
		return nil, errors.New("failed to get cluster entity")
	}

	if cluster.GlobalCluster == nil {
		return nil, errors.New("failed to get serverless cluster from global service")
	}

	serverlessCluster, err := cluster.GetServerlessCluster()
	if err != nil {
		return nil, err
	}

	clusterID := strconv.FormatUint(cluster.ClusterId, 10)
	auditLogEnabled := serverlessCluster.GetCluster().GetAuditLogConfig().GetEnabled()
	auditLogUnredacted := serverlessCluster.GetCluster().GetAuditLogConfig().GetUnredacted()
	result := openapiserverlessv1beta1.Cluster{
		Name:        fmt.Sprintf("clusters/%d", cluster.ClusterId),
		DisplayName: cluster.DisplayName,
		ClusterId:   clusterID,
		CreateTime:  timestamppb.New(cluster.CreatedAt),
		UpdateTime:  timestamppb.New(cluster.UpdatedAt),
		HighAvailabilityType: lo.Ternary(
			serverlessCluster.GetCluster().GetZoneRedundantEnabled(),
			openapiserverlessv1beta1.Cluster_REGIONAL,
			openapiserverlessv1beta1.Cluster_ZONAL,
		),
		AuditLogConfig: &openapiserverlessv1beta1.Cluster_AuditLogConfig{
			Enabled:    &auditLogEnabled,
			Unredacted: &auditLogUnredacted,
		},
	}

	if err := buildRegionFromRecord(&result, cluster, serverlessCluster); err != nil {
		return nil, err
	}

	result.CreatedBy = common.ConvCreatedBy(cluster.CreatedBy)
	result.Labels = cluster.Labels
	result.Annotations = cluster.Annotations
	result.Tags = common.CvtEntityTagsToOpenAPITags(cluster.Tags)
	if result.Labels == nil {
		result.Labels = make(map[string]string)
	}
	result.Labels[common.ProjectIDLabelKey] = strconv.FormatUint(cluster.ProjectId, 10)
	result.Labels[common.OrgIDLabelKey] = strconv.FormatUint(cluster.OrganizationId, 10)

	if serverlessCluster.Cluster != nil {
		if serverlessCluster.Cluster.UpdatedTs != 0 {
			result.UpdateTime = timestamppb.New(time.Unix(serverlessCluster.Cluster.UpdatedTs, 0))
		}

		// if serviceRecord.Cluster.DeleteTs != 0 {
		// 	result.DeleteTime = timestamppb.New(time.Unix(serviceRecord.Cluster.DeleteTs, 0))
		// }
		result.State = cvtUserState(serverlessCluster.UserStatus)
		result.Version = serverlessCluster.Cluster.Version
		result.UserPrefix = serverlessCluster.Cluster.UserPrefix

		result.EncryptionConfig = &openapiserverlessv1beta1.Cluster_EncryptionConfig{
			EnhancedEncryptionEnabled: serverlessCluster.Cluster.EnhancedEncryptionEnabled,
		}

		// cvtAvailableFeature
		result.Annotations[common.AvailableFeaturesKey] = common.CvtAvailableFeature(serverlessCluster.GetFeatures())
		result.Annotations[common.HasSetPwdAnnotationKey] = strconv.FormatBool(serverlessCluster.HasSetPwd)

		if serverlessCluster.Cluster.GetAutoScaling() != nil {
			result.AutoScaling = &openapiserverlessv1beta1.Cluster_AutoScaling{
				MinRcu: serverlessCluster.Cluster.AutoScaling.GetMinRcu(),
				MaxRcu: serverlessCluster.Cluster.AutoScaling.GetMaxRcu(),
			}
		}
	}

	// Cleanup internal info before return
	delete(result.Labels, metadata.UserIDHeaderKey)

	// Override state with custom status
	result.State = common.BuildClusterState(result.State, customStatuses)

	if serverlessCluster.Cluster != nil && serverlessCluster.Cluster.Endpoints != nil {
		result.Endpoints = &openapiserverlessv1beta1.Cluster_Endpoints{}
		if serverlessCluster.Cluster.Endpoints.Public != nil {
			result.Endpoints.Public = &openapiserverlessv1beta1.Cluster_Endpoints_Public{
				Host:     serverlessCluster.Cluster.Endpoints.Public.Host,
				Port:     int32(serverlessCluster.Cluster.Endpoints.Public.Port),
				Disabled: serverlessCluster.Cluster.Endpoints.Public.Disabled,
			}
			if len(serverlessCluster.Cluster.Endpoints.Public.AuthorizedNetwork) > 0 {
				result.Endpoints.Public.AuthorizedNetworks = make(
					[]*openapiserverlessv1beta1.Cluster_Endpoints_Public_AuthorizedNetwork,
					len(serverlessCluster.Cluster.Endpoints.Public.AuthorizedNetwork),
				)
				for k, v := range serverlessCluster.Cluster.Endpoints.Public.AuthorizedNetwork {
					result.Endpoints.Public.AuthorizedNetworks[k] = &openapiserverlessv1beta1.Cluster_Endpoints_Public_AuthorizedNetwork{
						StartIpAddress: v.StartIpAddress,
						EndIpAddress:   v.EndIpAddress,
						DisplayName:    v.DisplayName,
					}
				}
			}
		}
		if serverlessCluster.Cluster.Endpoints.Private != nil {
			result.Endpoints.Private = &openapiserverlessv1beta1.Cluster_Endpoints_Private{
				Host: serverlessCluster.Cluster.Endpoints.Private.Host,
				Port: int32(serverlessCluster.Cluster.Endpoints.Private.Port),
			}
			result.Endpoints.Private.AuthorizedNetworks = cvtRegionalPrivateAuthorizedNetworksToOpenAPI(
				serverlessCluster.Cluster.Endpoints.Private.AuthorizedNetwork,
			)
			if serverlessCluster.Cluster.Endpoints.Private.PrivateEndpoint != nil {
				switch pe := serverlessCluster.Cluster.Endpoints.Private.PrivateEndpoint.(type) {
				case *regionalserverlessv1.PrivateEndpoint_Aws:
					result.Endpoints.Private.Provider = &openapiserverlessv1beta1.Cluster_Endpoints_Private_Aws{
						Aws: &openapiserverlessv1beta1.Cluster_Endpoints_Private_AWS{
							ServiceName:      pe.Aws.ServiceName,
							AvailabilityZone: pe.Aws.AvailabilityZone,
						},
					}
				case *regionalserverlessv1.PrivateEndpoint_Gcp:
					result.Endpoints.Private.Provider = &openapiserverlessv1beta1.Cluster_Endpoints_Private_Gcp{
						Gcp: &openapiserverlessv1beta1.Cluster_Endpoints_Private_GCP{
							ServiceAttachmentName: pe.Gcp.TargetServiceAttachment,
						},
					}
				case *regionalserverlessv1.PrivateEndpoint_Alicloud:
					result.Endpoints.Private.Provider = &openapiserverlessv1beta1.Cluster_Endpoints_Private_Alicloud{
						Alicloud: &openapiserverlessv1beta1.Cluster_Endpoints_Private_ALICLOUD{
							ServiceName:      pe.Alicloud.ServiceName,
							AvailabilityZone: pe.Alicloud.AvailabilityZone,
						},
					}
				default:
				}
			}
		}
	}

	servicePlan := serverlessServicePlan(cluster, serverlessCluster)
	result.ServicePlan = servicePlan

	result.EngineType = cluster.EngineType
	if result.EngineType == openapicommonv1beta1.EngineType_ENGINE_TYPE_UNSPECIFIED {
		result.EngineType = openapicommonv1beta1.EngineType_MYSQL_COMPATIBLE
	}

	if view == openapiserverlessv1beta1.ClusterView_BASIC {
		// Default to full view
		return &result, nil
	}

	if serverlessCluster.BackupPolicy != nil {
		result.AutomatedBackupPolicy = &openapiserverlessv1beta1.Cluster_AutomatedBackupPolicy{
			RetentionDays: int32(serverlessCluster.BackupPolicy.RetentionDays),
			StartTime:     serverlessCluster.BackupPolicy.BackupTime,
		}
	}

	if serverlessCluster.SpendLimit != nil &&
		// ESSENTIAL plan does not return spending limit
		servicePlan != openapicommonv1beta1.ServicePlan_Essential {
		result.SpendingLimit = &openapiserverlessv1beta1.Cluster_SpendingLimit{
			Monthly: int32(serverlessCluster.SpendLimit.Monthly),
		}
	}

	return &result, nil
}

func serverlessServicePlan(
	cluster *entity.Cluster,
	serverlessCluster *globalserverlessv1.ServerlessServiceCluster,
) openapicommonv1beta1.ServicePlan {
	if cluster.ServicePlan != openapicommonv1beta1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return cluster.ServicePlan
	}

	if serverlessCluster.GetCluster().GetAutoScaling().GetMinRcu() > 0 {
		return openapicommonv1beta1.ServicePlan_Essential
	}
	return openapicommonv1beta1.ServicePlan_Starter
}

func buildRegionFromRecord(
	res *openapiserverlessv1beta1.Cluster,
	clusterEn *entity.Cluster,
	globalCluster *globalserverlessv1.ServerlessServiceCluster,
) error {
	if globalCluster == nil {
		return bizerror.NewInternal(
			errors.New("regional serverless cluster is nil"),
			"failed to get serverless cluster",
		)
	}

	r := &openapicommonv1beta1.Region{
		Name:          fmt.Sprintf("regions/%s-%s", clusterEn.CloudProvider, clusterEn.Region),
		CloudProvider: clusterEn.CloudProvider,
		RegionId:      clusterEn.Region,
		Provider:      lo.ToPtr(clusterEn.CloudProvider.String()),
	}

	if r.CloudProvider == openapicommonv1beta1.Region_CLOUD_PROVIDER_UNSPECIFIED || r.RegionId == "" {
		p, err := enum.FromString[openapicommonv1beta1.Region_CloudProvider](globalCluster.GetCloudProvider())
		if err != nil {
			return err
		}
		r.CloudProvider = openapicommonv1beta1.Region_CloudProvider(p)
		r.RegionId = globalCluster.GetRegion()
		r.DisplayName = globalCluster.GetCluster().GetRegion().GetDisplayName()
		r.Name = fmt.Sprintf("regions/%s-%s", r.CloudProvider, r.RegionId)
	}

	if globalCluster.GetCluster().GetRegion() != nil {
		r.DisplayName = globalCluster.GetCluster().GetRegion().GetDisplayName()
	} else {
		r.DisplayName = r.RegionId
	}

	res.Region = r

	return nil
}

// cvt user state
func cvtUserState(in globalserverlessv1.UserClusterStatus) openapicommonv1beta1.ClusterState {
	switch in {
	case globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_UNSPECIFIED,
		globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_UNAVAILABLE:
		return openapicommonv1beta1.ClusterState_CLUSTER_STATE_UNSPECIFIED
	case globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_NORMAL,
		globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_IMPORTING:
		return openapicommonv1beta1.ClusterState_ACTIVE
	case globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_CREATING:
		return openapicommonv1beta1.ClusterState_CREATING
	case globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_DELETED:
		return openapicommonv1beta1.ClusterState_DELETED
	case globalserverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_RECOVERING:
		return openapicommonv1beta1.ClusterState_RESTORING
	}
	return openapicommonv1beta1.ClusterState_CLUSTER_STATE_UNSPECIFIED
}
