package br

import (
	"context"
	"encoding/base64"
	"fmt"
	"strconv"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
	wf "github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

type restoreFromClassicAdapter struct {
	restoreAdapterClassicBase
}

func (a *restoreFromClassicAdapter) PrepareRestore(
	ctx context.Context,
	req *globalngv1.RestoreClusterRequest,
) (submitRestore, bizerr.BizError) {
	targetClusterId, err := strconv.ParseUint(req.GetTargetClusterId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to parse target cluster id")
	}

	targetCluster, berr := a.clusterRepo.Get(ctx, targetClusterId, false)
	if berr != nil {
		return nil, berr.Chain("failed to get target cluster")
	}

	metadataJson, err := metadata.MarshalRequestMetaFromCtx(ctx)
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to marshal metadata from context")
	}

	backupInfo, berr := doTestClassicBackupAccess(ctx, &globalngv1.TestClassicBackupAccessRequest{
		ClassicBackupStorage: req.GetClassicBackupStorage(),
		ServicePlan:          targetCluster.ServicePlan,
		OrganizationId:       targetCluster.GetOrganizationId(),
	}, a.regionRepo, a.regionalCli)
	if berr != nil {
		return nil, berr.Chain("failed to get classic backup info")
	}

	restoreEntity := buildEntityForClassicRestore(req, targetCluster, backupInfo)

	return func(ctx context.Context) bizerr.BizError {
		err := a.transaction.Transaction(ctx, func(ctx context.Context) error {
			if berr := lockResourcePoolForRestore(
				ctx,
				a.resourcePoolRepo,
				a.clusterRepo,
				restoreEntity.TargetSharedPoolId,
				restoreEntity.TargetClusterId,
			); berr != nil {
				return berr
			}
			berr = a.restoreRepo.Create(ctx, restoreEntity)
			if berr != nil {
				return berr.Chain("failed to create classic restore record")
			}
			workflowErr := a.workflow.RunDagWithBizId(
				ctx,
				common.RestoreClusterFromClassic,
				fmt.Sprintf("%d", targetCluster.GetOrganizationId()),
				"0", // nextgen cluster don't have project id
				req.TargetClusterId,
				map[string]string{
					common.KeyTargetClusterId: req.TargetClusterId,
					wf.KeyTraceId:             wf.GetTraceId(ctx),
					wf.KeyRequestMeta:         string(metadataJson),
				},
			)
			if workflowErr != nil {
				return bizerr.WrapErrorWithCode(workflowErr, bizerr.ErrCodeUnknown, "create %s workflow failed", common.RestoreClusterFromClassic)
			}
			a.writeRestoreStartedDisplayEvent(ctx, restoreEntity)
			return nil
		})
		if err != nil {
			return bizerr.WrapError(err, "failed to create classic restore record or run %s dag", common.RestoreClusterFromClassic)
		}
		return nil
	}, nil
}

func (a *restoreFromClassicAdapter) StopRestore(ctx context.Context, req *globalngv1.StopRestoreClusterRequest) bizerr.BizError {
	return a.restoreAdapterClassicBase.StopRestore(ctx, req, common.RestoreClusterFromClassic)
}

func buildEntityForClassicRestore(
	req *globalngv1.RestoreClusterRequest,
	targetCluster *entity.Cluster,
	backupInfo *globalngv1.TestClassicBackupAccessResponse,
) *entity.Restore {
	stringToUint64 := func(s string) uint64 {
		id, _ := strconv.ParseUint(s, 10, 64)
		return id
	}

	restoreEntity := &entity.Restore{
		Restore: &schema.Restore{
			State:                 "pending",
			TargetClusterId:       targetCluster.GetClusterId(),
			TenantId:              targetCluster.GetOrganizationId(),
			ExpectRestoreDataSize: backupInfo.GetDataSizeBytes(),
			RestoreMode:           schema.RestoreModeClassicSnapshot,
			BackupId:              0, // classic restore has no backup id in the system
			ClassicBackupAuth: &schema.ClassicBackupAuth{
				AccessKeyId:     base64.StdEncoding.EncodeToString([]byte(req.GetClassicBackupStorage().GetAuth().GetAccessKeyId())),
				AccessKeySecret: base64.StdEncoding.EncodeToString([]byte(req.GetClassicBackupStorage().GetAuth().GetAccessKeySecret())),
			},
			TargetSharedPoolId:  stringToUint64(targetCluster.GetSharedPoolId()),
			TargetRegionId:      targetCluster.GetRegionId().String(),
			TargetCloudProvider: targetCluster.GetCloudProviderId().String(),
			TargetServicePlan:   targetCluster.GetServicePlan().String(),
			TargetMinRcu:        targetCluster.GetMinRcu(),
			TargetMaxRcu:        targetCluster.GetMaxRcu(),
			SourceClusterId:     0, // classic restore has no source cluster
			SourceSharedPoolId:  0, // only used for premium restore
			SourceRegionId:      backupInfo.GetRegion(),
			SourceCloudProvider: req.GetClassicBackupStorage().GetCloudProvider().String(),
			SourceServicePlan:   "", // from OSS, not managed by tidbcloud
			SourceBackupStorage: req.GetClassicBackupStorage().GetBucketUri(),
		},
	}

	return restoreEntity
}
