package azure

import (
	"context"
	"fmt"
	"time"

	"github.com/avast/retry-go/v4"
	"github.com/shiningrush/fastflow/pkg/entity/run"
	"github.com/tidbcloud/dataflow-service/app/models/bucket"
	"github.com/tidbcloud/dataflow-service/app/models/domain"
	"github.com/tidbcloud/dataflow-service/app/repo"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	"github.com/tidbcloud/dataflow-service/app/workflow"
	"github.com/tidbcloud/dataflow-service/app/workflow/action"
	"github.com/tidbcloud/dataflow-service/app/workflow/action/bucket/base"
	"github.com/tidbcloud/dataflow-service/internal/db"
	"go.uber.org/zap"
)

type CreateBucketAction struct {
	base.BaseAction
	AzureBucketManager bucket.AzureBucketManagerModel
}

func NewCreateBucketAction(
	transProvider db.TransProvider,
	bucketRepo repo.BucketRepo,
	azureBucketManager bucket.AzureBucketManagerModel,
) *CreateBucketAction {
	return &CreateBucketAction{
		BaseAction:         base.NewBaseAction(transProvider, bucketRepo),
		AzureBucketManager: azureBucketManager,
	}
}

func (a *CreateBucketAction) Name() string {
	return workflow.ActionCreateBucketAzure
}

func (a *CreateBucketAction) Run(ctx run.ExecuteContext, params interface{}) error {
	logger := action.GetLogger(ctx).Named(a.Name())
	logger.Info("Run action create_bucket_azure")
	err := action.InvokeRunActionWithRetry(ctx.Context(), logger, func() error {
		bkt, err := a.QueryBucket(ctx, logger)
		if err != nil {
			return err
		}
		isReady := a.checkBucketIsReady(ctx.Context(), logger, bkt)
		if isReady {
			logger.Info("bucket is ready", zap.Any("bucket id", bkt.ID))
			return nil
		}
		if err := a.createBucket(ctx, logger, bkt); err != nil {
			logger.Error("create azure bucket failed", zap.Error(err))
			return err
		}
		logger.Info("create azure bucket success")
		return nil
	})
	if err != nil {
		logger.Error("action run error", zap.Error(err))
		return err
	}
	logger.Info("action create azure bucket action run finished")
	return nil
}

func (a *CreateBucketAction) createBucket(ctx run.ExecuteContext, logger *zap.Logger, bkt *entity.Bucket) error {
	tags := map[string]string{
		"environment": action.GenInfraCREnvironmentTagValue(),
		"servicetype": "dedicated",
		"usedby":      "custom-tidb",
		"component":   bkt.GenResourceComponentTagValue(),
		"tenant":      bkt.TenantID,
		"project":     bkt.ProjectID,
	}
	createBucketReq := &domain.CreateAzureBucketReq{
		Name:           bkt.Name,
		SubscriptionID: *bkt.CloudAccountID,
		Region:         bkt.Regions[0],
		Tags:           tags, // We can set tags for Azure storage account during creation
		ProjectID:      bkt.ProjectID,
	}
	logger.Info("create azure bucket", zap.Any("request", *createBucketReq))
	if err := a.AzureBucketManager.CreateBucket(ctx.Context(), createBucketReq); err != nil {
		logger.Warn("create azure bucket error", zap.Any("bucket id", bkt.ID), zap.Error(err))
		return err
	}
	logger.Info("create azure bucket success")
	return nil
}

func (a *CreateBucketAction) checkBucketIsReady(ctx context.Context, logger *zap.Logger, bkt *entity.Bucket) bool {
	req := &domain.CheckAzureBucketReq{
		Name:           bkt.Name,
		SubscriptionID: *bkt.CloudAccountID,
	}
	err := a.AzureBucketManager.CheckBucketIfExists(ctx, req)
	if err != nil {
		logger.Warn("check bucket if exists error", zap.Error(err))
		return false
	}
	return true
}

//nolint:gomnd
func (a *CreateBucketAction) RunAfter(ctx run.ExecuteContext, params interface{}) error {
	logger := action.GetLogger(ctx).Named(a.Name())
	logger.Info("action runAfter start")
	check := func() error {
		bkt, err := a.QueryBucket(ctx, logger)
		if err != nil {
			return err
		}
		isReady := a.checkBucketIsReady(ctx.Context(), logger, bkt)
		if isReady {
			logger.Info("bucket is ready", zap.Any("bucket id", bkt.ID))
			return nil
		}
		return fmt.Errorf("bucket is not ready")
	}
	logErr := func(n uint, err error) {
		logger.Info("action runAfter error", zap.Uint("retry_times", n))
	}
	err := retry.Do(check,
		retry.Context(ctx.Context()),
		retry.DelayType(retry.FixedDelay),
		retry.Delay(time.Second*3),
		retry.Attempts(0),
		retry.OnRetry(logErr),
		retry.LastErrorOnly(true),
	)
	if err != nil {
		logger.Error("action runAfter error", zap.Error(err))
		return err
	}
	logger.Info("action runAfter finished")
	return nil
}
