package action

import (
	"fmt"

	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const (
	ApplyInternalBucketLifecycleRulesActionName = "apply-internal-bucket-lifecycle-rules"
	deleteInternalBucketLifecycleRuleId         = "DELETE_INTERNAL_BUCKET_RULE"
	abortIncompleteMultipartUploadRuleId        = "Abort Incomplete Multipart Upload"
)

// Bucket status constants

type ApplyInternalBucketLifecycleRulesAction struct {
	sharedPoolRepo repo.SharedPoolRepo
	client         dplanemgrv1.BucketServiceClient
}

func NewApplyInternalBucketLifecycleRulesAction(sharedPoolRepo repo.SharedPoolRepo, client dplanemgrv1.BucketServiceClient) actions.AsyncAction {
	return &ApplyInternalBucketLifecycleRulesAction{
		sharedPoolRepo: sharedPoolRepo,
		client:         client,
	}
}

func (a *ApplyInternalBucketLifecycleRulesAction) Name() string {
	return ApplyInternalBucketLifecycleRulesActionName
}

func (a *ApplyInternalBucketLifecycleRulesAction) Start(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "apply internal bucket lifecycle rules action invoked")

	sharedPoolId, err := args.GetString(workflow.SharedPoolIdKey)
	if err != nil {
		return fmt.Errorf("failed to get sharedPool id: %w", err)
	}

	// load sharedPool
	sharedPool, err := a.sharedPoolRepo.Load(ctx, sharedPoolId, true)
	if err != nil {
		tilog.Error(ctx, "failed to load sharedPool", zap.String("sharedPoolId", sharedPoolId), zap.Error(err))
		return fmt.Errorf("failed to load sharedPool: %w", err)
	}

	bucketId := entity.GetInternalBucketId(sharedPool.GetId(), sharedPool.GetInternalStorage().GetRegion())
	_, err = a.client.GetBucket(ctx, &dplanemgrv1.GetBucketRequest{
		DataplaneId: sharedPool.GetDataplaneName(),
		BucketId:    bucketId,
	})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Info(ctx, "bucket not found, skip delete", zap.String("bucketId", bucketId))
			return nil
		}
		tilog.Error(ctx, "failed to get bucket", zap.String("bucketId", bucketId), zap.Error(err))
		return fmt.Errorf("failed to get bucket: %w", err)
	}

	if _, err = a.client.ApplyLifecycleRules(ctx, &dplanemgrv1.ApplyLifecycleRulesRequest{
		DataplaneId: sharedPool.GetDataplaneName(),
		BucketId:    bucketId,
		Rules: []*dplanemgrv1.LifecycleRule{
			{
				Id:             deleteInternalBucketLifecycleRuleId,
				Enabled:        true,
				ExpirationDays: 1,
			},
			{
				Id:                             abortIncompleteMultipartUploadRuleId,
				Enabled:                        true,
				IncompleteUploadExpirationDays: 1,
			},
		},
	}); err != nil {
		tilog.Error(ctx, "failed to apply lifecycle rules to bucket", zap.String("bucketId", bucketId), zap.Error(err))
		return fmt.Errorf("failed to apply lifecycle rules to bucket: %w", err)
	}

	return nil
}

func (a *ApplyInternalBucketLifecycleRulesAction) CheckFinish(args actions.ActionArgs) (bool, error) {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "apply internal bucket lifecycle rules action invoked")

	sharedPoolId, err := args.GetString(workflow.SharedPoolIdKey)
	if err != nil {
		return false, fmt.Errorf("failed to get sharedPool id: %w", err)
	}

	// load sharedPool
	sharedPool, err := a.sharedPoolRepo.Load(ctx, sharedPoolId, true)
	if err != nil {
		tilog.Error(ctx, "failed to load sharedPool", zap.String("sharedPoolId", sharedPoolId), zap.Error(err))
		return false, fmt.Errorf("failed to load sharedPool: %w", err)
	}

	bucketId := entity.GetInternalBucketId(sharedPool.GetId(), sharedPool.GetInternalStorage().GetRegion())
	_, err = a.client.GetBucket(ctx, &dplanemgrv1.GetBucketRequest{
		DataplaneId: sharedPool.GetDataplaneName(),
		BucketId:    bucketId,
	})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Info(ctx, "bucket not found, skip delete", zap.String("bucketId", bucketId))
			return true, nil
		}
		tilog.Error(ctx, "failed to get bucket", zap.String("bucketId", bucketId), zap.Error(err))
		return false, fmt.Errorf("failed to get bucket: %w", err)
	}

	rules, err := a.client.GetLifecycleRules(ctx, &dplanemgrv1.GetLifecycleRulesRequest{
		DataplaneId: sharedPool.GetDataplaneName(),
		BucketId:    bucketId,
	})

	if err != nil {
		tilog.Error(ctx, "failed to get lifecycle rules", zap.String("bucketId", bucketId), zap.Error(err))
		return false, fmt.Errorf("failed to get lifecycle rules: %w", err)
	}

	if rules.GetStatus() != dplanemgrv1.GetLifecycleRulesResponse_Synced {
		tilog.Info(ctx, "lifecycle rules not synced yet", zap.String("bucketId", bucketId))
		return false, nil
	}

	for _, rule := range rules.GetRules() {
		if rule.GetId() == deleteInternalBucketLifecycleRuleId && rule.GetEnabled() {
			tilog.Info(ctx, "lifecycle rule applied successfully", zap.String("bucketId", bucketId))
			return true, nil
		}
	}

	tilog.Info(ctx, "lifecycle rule not applied yet", zap.String("bucketId", bucketId))
	return false, nil
}
