package br

import (
	"context"
	"strconv"

	"google.golang.org/grpc/codes"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/resourcepoolpolicy"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

// Restore implements Service.
func (s *serviceImpl) Restore(ctx context.Context, req *globalngv1.RestoreClusterRequest) bizerr.BizError {
	ctx, span := tracer.Start(ctx, "Domain.Restore")
	defer span.End()
	if req == nil {
		return bizerr.New(bizerr.ErrCodeParams, "request is required")
	}

	targetClusterID, err := strconv.ParseUint(req.GetTargetClusterId(), 10, 64)
	if err != nil || targetClusterID == 0 {
		return bizerr.New(bizerr.ErrCodeParams, "target cluster id is invalid")
	}
	targetCluster, berr := s.clusterRepo.Get(ctx, targetClusterID, false)
	if berr != nil {
		return berr.Chain("failed to get target cluster")
	}
	resourcePoolID, err := strconv.ParseUint(targetCluster.GetSharedPoolId(), 10, 64)
	if err != nil || resourcePoolID == 0 {
		return bizerr.New(bizerr.ErrCodeUnknown, "target cluster has an invalid resource pool id")
	}
	resourcePool, berr := s.resourcePoolRepo.Load(ctx, resourcePoolID, false)
	if berr != nil {
		return berr.Chain("failed to load target resource pool")
	}
	initialDecision := resourcepoolpolicy.Decide(
		resourcepoolpolicy.OperationRestoreInstance,
		resourcePool.GetLifecycleState(),
	)
	if initialDecision != resourcepoolpolicy.DecisionAllowed &&
		initialDecision != resourcepoolpolicy.DecisionTriggerOrReuseResume {
		return bizerr.New(
			bizerr.ErrCode(codes.FailedPrecondition),
			"restore is not allowed while resource pool lifecycle state is %s",
			resourcePool.GetLifecycleState().String(),
		)
	}
	restoreAdapter, berr := s.getRestoreAdapter(ctx, req, nil)
	if berr != nil {
		return berr.Chain("failed to get restore adapter")
	}
	submit, berr := restoreAdapter.PrepareRestore(ctx, req)
	if berr != nil {
		return berr.Chain("failed to prepare restore")
	}
	resourcePool, berr = s.resourcePoolRepo.Load(ctx, resourcePoolID, false)
	if berr != nil {
		return berr.Chain("failed to load target resource pool")
	}
	decision := resourcepoolpolicy.Decide(
		resourcepoolpolicy.OperationRestoreInstance,
		resourcePool.GetLifecycleState(),
	)
	switch decision {
	case resourcepoolpolicy.DecisionAllowed:
	case resourcepoolpolicy.DecisionTriggerOrReuseResume:
		if resourcePool.GetLifecycleState() != globalngv1.ResourcePool_RESUMING {
			operationID := strconv.FormatUint(s.idGeneratorAdapter.MustNew(), 10)
			_, resumeErr := s.resourcePoolService.ResumeResourcePool(ctx, &globalngv1.ResumeResourcePoolRequest{
				ResourcePoolId: resourcePoolID,
				OperationId:    operationID,
			})
			if resumeErr != nil {
				if resumeErr.ErrCode() != bizerr.ErrCode(codes.FailedPrecondition) {
					return resumeErr
				}
				latest, loadErr := s.resourcePoolRepo.Load(ctx, resourcePoolID, false)
				if loadErr != nil {
					return loadErr
				}
				if latest.GetLifecycleState() != globalngv1.ResourcePool_RESUMING &&
					latest.GetLifecycleState() != globalngv1.ResourcePool_ACTIVE {
					return resumeErr
				}
			}
		}
	default:
		return bizerr.New(
			bizerr.ErrCode(codes.FailedPrecondition),
			"restore is not allowed while resource pool lifecycle state is %s",
			resourcePool.GetLifecycleState().String(),
		)
	}

	berr = submit(ctx)
	if berr != nil {
		return berr.Chain("failed to create restore")
	}
	return nil
}
