package rcuquota

import (
	"context"
	"strconv"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	clusterrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

func (s *serviceImpl) UpdateRcuQuota(ctx context.Context, request *globalngv1.UpdateRcuQuotaRequest) (*entity.RcuQuota, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.UpdateRcuQuota")
	defer span.End()

	if request == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "request cannot be nil")
	}

	if request.RcuQuota.OrganizationId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organizationId cannot be empty")
	}

	if request.RcuQuota.ServicePlan == globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		return nil, bizerr.New(bizerr.ErrCodeParams, "service plan cannot be empty")
	}

	tErr := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		originalRcuQuota, err := s.rcuQuotaRepo.Load(ctx, request.RcuQuota.OrganizationId, request.RcuQuota.ServicePlan.String())

		if err != nil {
			return err
		}

		if originalRcuQuota == nil {
			return bizerr.New(bizerr.ErrCodeRecordNotFound, "rcu quota not found")
		}

		if request.RcuQuota.InstanceMaxRcu == 0 {
			request.RcuQuota.InstanceMaxRcu = originalRcuQuota.InstanceMaxRcu
		}

		if request.RcuQuota.TotalMaxRcu == 0 {
			request.RcuQuota.TotalMaxRcu = originalRcuQuota.TotalMaxRcu
		}

		if request.RcuQuota.BurstyRatio == 0 {
			request.RcuQuota.BurstyRatio = originalRcuQuota.BurstyRatio
		}

		if request.RcuQuota.IdleResourcePoolTtlInHours == 0 {
			request.RcuQuota.IdleResourcePoolTtlInHours = originalRcuQuota.IdleResourcePoolTtlInHours
		}

		if request.RcuQuota.VcpusLimitPerResourcePool == 0 {
			request.RcuQuota.VcpusLimitPerResourcePool = originalRcuQuota.VcpusLimitPerResourcePool
		}

		if request.RcuQuota.MinimalRcuBaseline == 0 {
			request.RcuQuota.MinimalRcuBaseline = originalRcuQuota.MinimalRcuBaseline
		}

		if request.RcuQuota.MinimalRcuCoefficient == 0 {
			request.RcuQuota.MinimalRcuCoefficient = originalRcuQuota.MinimalRcuCoefficient
		}

		if request.RcuQuota.MinimalRcuBaseline > 50000 {
			return bizerr.New(bizerr.ErrCodeParams, "minimal RCU baseline must <= 50000")
		}

		if request.RcuQuota.ScaleDownCooldownMinutes == 0 {
			request.RcuQuota.ScaleDownCooldownMinutes = originalRcuQuota.ScaleDownCooldownMinutes
		}

		if request.RcuQuota.MinimalRcuCoefficient < 0 || request.RcuQuota.MinimalRcuCoefficient > 1 {
			return bizerr.New(bizerr.ErrCodeParams, "minimal RCU coefficient must be between 0 and 1")
		}

		if request.RcuQuota.InstanceMaxRcu < 5000 {
			return bizerr.New(bizerr.ErrCodeParams, "instance RCU limit must > 5000")
		}

		if request.RcuQuota.TotalMaxRcu < 20000 {
			return bizerr.New(bizerr.ErrCodeParams, "total RCU limit must > 20000")
		}

		if request.RcuQuota.BurstyRatio < 1 {
			return bizerr.New(bizerr.ErrCodeParams, "bursty ratio must >= 1")
		}

		err = s.rcuQuotaRepo.Save(ctx, request.RcuQuota)
		if err != nil {
			return err
		}

		metadataJson, merr := metadata.MarshalRequestMetaFromCtx(ctx)
		if merr != nil {
			tilog.Warn(ctx, "failed to marshal request metadata", zap.Error(merr))
		}

		// Trigger cluster updates if BurstyRatio changed
		if request.RcuQuota.BurstyRatio != originalRcuQuota.BurstyRatio {
			tilog.Info(ctx, "RCU quota bursty ratio updated, need to update all clusters' max rcu",
				zap.Uint64("organizationId", request.RcuQuota.OrganizationId),
				zap.String("servicePlan", request.RcuQuota.ServicePlan.String()),
				zap.Float32("originalBurstyRatio", originalRcuQuota.BurstyRatio),
				zap.Float32("newBurstyRatio", request.RcuQuota.BurstyRatio),
			)

			clusters, err := s.clusterRepo.Query(ctx, &clusterrepo.Filter{
				OrganizationIds: []uint64{request.RcuQuota.OrganizationId},
				ServicePlans:    []globalcommonv1.ServicePlan{request.RcuQuota.ServicePlan},
			})

			if err != nil {
				return err
			}

			for _, cluster := range clusters {
				cluster.State = globalngv1.Cluster_MODIFYING
				if err := s.clusterRepo.Save(ctx, cluster); err != nil {
					return bizerr.WrapGormError(err, "save cluster failed")
				}

				if err := s.workflow.RunDagWithBizId(
					ctx,
					common.UpdateCluster,
					strconv.FormatUint(cluster.OrganizationId, 10),
					"0",
					strconv.FormatUint(cluster.ClusterId, 10),
					map[string]string{
						workflow.KeyTraceId:     workflow.GetTraceId(ctx),
						workflow.KeyRequestMeta: string(metadataJson),
						common.KeyClusterId:     strconv.FormatUint(cluster.ClusterId, 10),
						common.KeyTargetStatus:  globalngv1.Cluster_AVAILABLE.String(),
					},
				); err != nil {
					return bizerr.WrapError(err, "run workflow failed")
				}
			}
		}

		return nil
	})

	if tErr != nil {
		return nil, bizerr.WrapError(tErr, "failed to update rcu quota")
	}

	return request.RcuQuota, nil
}
