package cluster

import (
	"context"

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

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	brsvc "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/br"
	clustersvc "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/cluster"
	encryptionkeysvc "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/encryptionkey"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func NewServer(
	svc clustersvc.Service,
	brSvc brsvc.Service,
	encryptionKeySvc encryptionkeysvc.Service,
) globalngv1.ClusterServiceServer {
	return &server{
		clusterSvc:       svc,
		brSvc:            brSvc,
		encryptionKeySvc: encryptionKeySvc,
	}
}

type server struct {
	clusterSvc       clustersvc.Service
	brSvc            brsvc.Service
	encryptionKeySvc encryptionkeysvc.Service
}

func (s *server) CreateCluster(
	ctx context.Context,
	req *globalngv1.CreateClusterRequest,
) (*globalngv1.Cluster, error) {
	c, err := s.clusterSvc.Create(ctx, req)
	if err != nil {
		return nil, err
	}
	if c == nil || c.Cluster == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "return nil cluster")
	}
	return c.Cluster, nil
}

func (s *server) UpdateCluster(
	ctx context.Context,
	req *globalngv1.UpdateClusterRequest,
) (*globalngv1.Cluster, error) {
	c, err := s.clusterSvc.Update(ctx, req)
	if err != nil {
		return nil, err
	}
	if c == nil || c.Cluster == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "return nil cluster")
	}
	return c.Cluster, nil
}

func (s *server) ListClusters(
	ctx context.Context,
	req *globalngv1.ListClustersRequest,
) (*globalngv1.ListClustersResponse, error) {
	clusters, err := s.clusterSvc.List(ctx, req)
	if err != nil {
		return nil, err
	}

	output := lo.Map(clusters, func(e *entity.Cluster, _ int) *globalngv1.Cluster {
		if e == nil || e.Cluster == nil {
			return nil
		}
		return e.Cluster
	})

	return &globalngv1.ListClustersResponse{
		Clusters: output,
		Total:    uint32(len(output)),
	}, nil
}

func (s *server) GetCluster(
	ctx context.Context,
	req *globalngv1.GetClusterRequest,
) (*globalngv1.Cluster, error) {
	cluster, err := s.clusterSvc.Get(ctx, req)
	if err != nil {
		return nil, err
	}
	if cluster == nil || cluster.Cluster == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "return nil cluster")
	}

	return cluster.Cluster, nil
}

func (s *server) DeleteCluster(
	ctx context.Context,
	req *globalngv1.DeleteClusterRequest,
) (*globalngv1.Cluster, error) {
	c, err := s.clusterSvc.Delete(ctx, req)
	if err != nil {
		return nil, err
	}
	if c == nil || c.Cluster == nil {
		return &globalngv1.Cluster{}, nil
	}
	return c.Cluster, nil
}

func (s *server) EstimatePrice(
	ctx context.Context,
	req *globalngv1.EstimatePriceRequest,
) (*globalngv1.EstimatePriceResponse, error) {
	return s.clusterSvc.EstimatePrice(ctx, req)
}

func (s *server) ChangeClusterPasswd(
	ctx context.Context,
	req *globalngv1.ChangeClusterPasswdRequest,
) (*globalngv1.ChangeClusterPasswdResponse, error) {
	resp := &globalngv1.ChangeClusterPasswdResponse{}
	err := s.clusterSvc.ChangeRootPasswd(ctx, req)
	if err != nil {
		return nil, err
	}

	resp.Synced = true
	return resp, nil
}

func (s *server) DeleteClusterBackup(
	ctx context.Context,
	req *globalngv1.DeleteClusterBackupRequest,
) (*emptypb.Empty, error) {
	err := s.brSvc.DeleteBackup(ctx, &globalngv1.Backup{
		Id:        req.GetBackupId(),
		ClusterId: req.GetClusterId(),
	})
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *server) ListClusterBackups(
	ctx context.Context,
	req *globalngv1.ListClusterBackupsRequest,
) (*globalngv1.ListClusterBackupsResponse, error) {
	resp, err := s.brSvc.ListBackups(ctx, &globalngv1.ListBackupsRequest{
		ClusterIds:   []string{req.GetClusterId()},
		BackupIds:    req.GetBackupIds(),
		States:       req.GetStates(),
		RegionIds:    req.GetRegionIds(),
		TriggerTypes: req.GetTriggerTypes(),
		StartTime:    req.GetStartTime(),
		EndTime:      req.GetEndTime(),
		PageSize:     req.GetPageSize(),
		PageToken:    req.GetPageToken(),
		Skip:         req.GetSkip(),
	})
	if err != nil {
		return nil, err
	}
	return &globalngv1.ListClusterBackupsResponse{
		Backups:       resp.Backups,
		TotalSize:     resp.TotalSize,
		NextPageToken: resp.NextPageToken,
	}, nil
}

func (s *server) RestoreCluster(
	ctx context.Context,
	req *globalngv1.RestoreClusterRequest,
) (*emptypb.Empty, error) {
	err := s.brSvc.Restore(ctx, req)
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *server) TestClassicBackupAccess(
	ctx context.Context,
	req *globalngv1.TestClassicBackupAccessRequest,
) (*globalngv1.TestClassicBackupAccessResponse, error) {
	return s.brSvc.TestClassicBackupAccess(ctx, req)
}

func (s *server) RestoreStatus(
	ctx context.Context,
	req *globalngv1.RestoreStatusRequest,
) (*globalngv1.RestoreStatusResponse, error) {
	return s.brSvc.RestoreStatus(ctx, req)
}

func (s *server) StopRestoreCluster(
	ctx context.Context,
	req *globalngv1.StopRestoreClusterRequest,
) (*emptypb.Empty, error) {
	err := s.brSvc.StopRestore(ctx, req)
	if err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *server) GetBackupSetting(
	ctx context.Context,
	req *globalngv1.GetBackupSettingRequest,
) (*globalngv1.BackupSetting, error) {
	return s.brSvc.GetBackupSetting(ctx, req)
}

func (s *server) UpdateBackupSetting(
	ctx context.Context,
	req *globalngv1.UpdateBackupSettingRequest,
) (*globalngv1.BackupSetting, error) {
	return s.brSvc.UpdateBackupSetting(ctx, req)
}

func (s *server) GetProviderRole(
	ctx context.Context,
	req *globalngv1.GetProviderRoleRequest,
) (*globalngv1.ProviderRole, error) {
	return s.clusterSvc.GetProviderRole(ctx, req)
}

func (s *server) GetCaCertificateDownloadUrl(
	ctx context.Context,
	request *globalngv1.GetCaCertificateDownloadUrlRequest,
) (*globalngv1.CaCertificateDownloadUrl, error) {
	return s.clusterSvc.GetCaCertificateDownloadUrl(ctx, request)
}

func (s *server) UpdateClusterProject(
	ctx context.Context,
	req *globalngv1.UpdateClusterProjectRequest,
) (*globalngv1.UpdateClusterProjectResponse, error) {
	if req == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "request is nil")
	}

	if err := s.clusterSvc.UpdateProjectId(ctx, req.GetClusterId(), req.GetProjectId()); err != nil {
		return nil, err
	}
	return &globalngv1.UpdateClusterProjectResponse{}, nil
}

func (s *server) UpdateClusterDualLayerEncryption(
	ctx context.Context,
	req *globalngv1.UpdateClusterDualLayerEncryptionRequest,
) (*globalngv1.UpdateClusterDualLayerEncryptionResponse, error) {
	switchReq := &encryptionkeysvc.SwitchEncryptionRequest{
		ClusterId:    req.GetClusterId(),
		ValidateOnly: req.GetValidateOnly(),
	}
	switch {
	case req.GetCmek() != nil:
		switchReq.TargetCmek = req.GetCmek()
	case req.GetDefaultKey() != nil:
		switchReq.TargetDefaultKey = true
	case req.GetNoEncryption() != nil:
		switchReq.TargetDisable = true
	default:
		return nil, bizerr.New(bizerr.ErrCodeParams, "must specify cmek, default_key, or no_encryption")
	}

	if err := s.encryptionKeySvc.SwitchClusterEncryptionKey(ctx, switchReq); err != nil {
		return nil, err
	}

	return &globalngv1.UpdateClusterDualLayerEncryptionResponse{
		ClusterId: req.GetClusterId(),
	}, nil
}

func (s *server) GetClusterEncryptionStatus(
	ctx context.Context,
	req *globalngv1.GetClusterEncryptionStatusRequest,
) (*globalngv1.ClusterEncryptionStatus, error) {
	binding, err := s.encryptionKeySvc.GetEncryptionBinding(ctx, req.GetClusterId())
	if err != nil {
		if err.ErrCode() == bizerr.ErrCodeRecordNotFound {
			// No binding means the cluster has no encryption configured.
			return &globalngv1.ClusterEncryptionStatus{
				ClusterId: req.GetClusterId(),
				State:     globalngv1.ClusterEncryptionStatus_STATE_UNSPECIFIED,
				CurrentEncryption: &globalngv1.Cluster_DualLayerDataEncryption{
					Key: &globalngv1.Cluster_DualLayerDataEncryption_NoEncryption{},
				},
			}, nil
		}
		return nil, err
	}

	status := convertBindingToStatusProto(binding)

	currentEncryption, err := s.encryptionKeySvc.BuildCurrentEncryption(ctx, binding.EncryptionKeyId)
	if err != nil {
		return nil, err
	}
	status.CurrentEncryption = currentEncryption

	return status, nil
}

func convertBindingToStatusProto(b *entity.ClusterEncryptionBinding) *globalngv1.ClusterEncryptionStatus {
	var protoState globalngv1.ClusterEncryptionStatus_State
	switch b.State {
	case entity.ClusterEncryptionBindingStateModifying, entity.ClusterEncryptionBindingStatePendingRestore:
		protoState = globalngv1.ClusterEncryptionStatus_MODIFYING
	case entity.ClusterEncryptionBindingStateEnabled:
		protoState = globalngv1.ClusterEncryptionStatus_ENABLED
	case entity.ClusterEncryptionBindingStateRestoreFailed:
		// The public status API has no failed state. Keep the terminal failure
		// detail in Message while returning a non-ready state.
		protoState = globalngv1.ClusterEncryptionStatus_STATE_UNSPECIFIED
	default:
		protoState = globalngv1.ClusterEncryptionStatus_STATE_UNSPECIFIED
	}
	return &globalngv1.ClusterEncryptionStatus{
		ClusterId: b.ClusterId,
		State:     protoState,
		Message:   b.Message,
	}
}

func (s *server) ForceUpdateGatewayIdentity(
	ctx context.Context,
	req *globalngv1.ForceUpdateGatewayIdentityRequest,
) (*emptypb.Empty, error) {
	if req == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "request is nil")
	}

	if err := s.clusterSvc.ForceUpdateGatewayIdentity(ctx, req); err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}
