package cluster

import (
	"context"
	"fmt"
	"maps"
	"strconv"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
)

func (svc *service) UpdateCluster(
	ctx context.Context,
	newCluster *entity.Cluster,
	validateOnly bool,
) (
	*entity.Cluster,
	*entity.ClusterCustomStatus,
	error,
) {
	spanName := "ClusterService.UpdateCluster"
	ctx, span := tracer.Start(ctx, spanName)
	defer span.End()

	var (
		cluster *entity.Cluster
		status  *entity.ClusterCustomStatus
	)

	if newCluster.ClusterId == 0 {
		return nil, nil, fmt.Errorf("cluster id is invalid")
	}

	if err := svc.transaction.Transaction(ctx, func(ctx context.Context) error {
		var err error
		// First make sure cluster exists in database
		cluster, err = svc.clusterRepo.GetCluster(ctx, newCluster.ClusterId, false)
		if err != nil {
			return fmt.Errorf("failed to get cluster record: %w", err)
		}

		if cluster.DeployType != entity.DeployTypeNextGen {
			return fmt.Errorf("cluster deploy type is not nextgen, got: %s", cluster.DeployType)
		}

		if cluster.DisplayName != newCluster.DisplayName && newCluster.DisplayName != "" {
			// Check cluster name is unique under the project
			count, err := svc.clusterRepo.CountDisplayNameUnderProject(ctx, cluster.ProjectId, newCluster.DisplayName)
			if err != nil {
				return fmt.Errorf("failed to count cluster display name under project: %w", err)
			}
			if count > 0 {
				return constant.ErrDuplicatedClusterName
			}

			// if validateOnly is true, then no need to update cluster display name in database.
			if !validateOnly {
				err = svc.clusterRepo.UpdateDisplayName(ctx, cluster.ClusterId, newCluster.DisplayName)
				if err != nil {
					return fmt.Errorf("failed to update cluster display name in database: %w", err)
				}
				cluster.DisplayName = newCluster.DisplayName

				_, err = svc.nextGenCli.BatchUpdateBackupSettingNames(ctx, &globalngv1.BatchUpdateBackupSettingNamesRequest{
					OrganizationId: strconv.FormatUint(cluster.OrganizationId, 10),
					Items: []*globalngv1.BatchUpdateBackupSettingNamesRequest_BackupSettingName{
						{
							ClusterId: cluster.ClusterId,
							Name:      cluster.DisplayName,
						},
					},
				})
				if err != nil {
					return fmt.Errorf("failed to update backup setting display name in global service: %w", err)
				}
			}
		}

		if newCluster.Labels != nil {
			// if validateOnly is true, then no need to update cluster labels in database.
			if !validateOnly {
				err := svc.clusterRepo.UpdateLabels(ctx, cluster.ClusterId, cluster.Labels)
				if err != nil {
					return fmt.Errorf("failed to update cluster labels in database: %w", err)
				}
			}

			maps.Copy(cluster.Labels, newCluster.Labels)
		}

		updateRequest, err := buildUpdateCluster(newCluster)
		if err != nil {
			return fmt.Errorf("failed to build update request for nextgen cluster: %w", err)
		}

		if updateRequest.GetMinRcu() != 0 || updateRequest.GetMaxRcu() != 0 || updateRequest.GetReservedCapacityTemplateId() != 0 {
			if _, err := svc.nextGenCli.UpdateCluster(ctx, &globalngv1.UpdateClusterRequest{
				Cluster:      updateRequest,
				ValidateOnly: validateOnly,
			}); err != nil {
				return fmt.Errorf("failed to update cluster in global service: %w", err)
			}

			var nextgenCluster *globalngv1.Cluster
			nextgenCluster, err = svc.nextGenCli.GetCluster(ctx, &globalngv1.GetClusterRequest{ClusterId: newCluster.ClusterId})
			if err != nil {
				return err
			}
			if nextgenCluster == nil {
				return fmt.Errorf("global cluster object should not be nil")
			}
			// touch cluster record in schema.
			// if validateOnly is true, then no need to touch cluster record in schema.
			if !validateOnly {
				err = svc.clusterRepo.TouchCluster(ctx, newCluster.ClusterId)
				if err != nil {
					return fmt.Errorf("failed to touch cluster record in database: %w", err)
				}
			}

			cluster = cvtNextGenCluster(cluster, nextgenCluster)
		}

		return nil
	}); err != nil {
		return nil, status, fmt.Errorf("failed to update cluster: %w", err)
	}

	return cluster, status, nil
}

func (svc *service) UpdateClusterRuVersion(ctx context.Context, clusterId uint64, ruVersion int32) error {
	ctx, span := tracer.Start(ctx, "ClusterService.UpdateClusterRuVersion")
	defer span.End()

	if clusterId == 0 {
		return fmt.Errorf("cluster id is invalid")
	}

	_, err := svc.nextGenCli.UpdateCluster(ctx, &globalngv1.UpdateClusterRequest{
		Cluster: &globalngv1.Cluster{
			ClusterId: clusterId,
			RuVersion: ruVersion,
		},
	})
	if err != nil {
		return fmt.Errorf("failed to update ru_version in global service: %w", err)
	}

	return nil
}

func buildUpdateCluster(
	cluster *entity.Cluster,
) (*globalngv1.Cluster, error) {
	nextgenCluster, err := cluster.GetNextGenCluster()
	if err != nil {
		return nil, err
	}

	return &globalngv1.Cluster{
		ClusterId:                  cluster.ClusterId,
		OrganizationId:             cluster.OrganizationId,
		CloudProviderId:            nextgenCluster.CloudProviderId,
		RegionId:                   nextgenCluster.RegionId,
		RootPassword:               nextgenCluster.RootPassword,
		Version:                    nextgenCluster.Version,
		MinRcu:                     nextgenCluster.MinRcu,
		MaxRcu:                     nextgenCluster.MaxRcu,
		ServicePlan:                nextgenCluster.ServicePlan,
		ReservedCapacityTemplateId: nextgenCluster.GetReservedCapacityTemplateId(),
	}, nil
}
