package cron

import (
	"context"
	"fmt"
	"time"

	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/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type CleanInternalBucketJob struct {
	sharedPoolRepo            repo.SharedPoolRepo
	sharedPoolObjectStoreRepo repo.SharedPoolObjectStoreRepo
	bucketSvc                 dplanemgrv1.BucketServiceClient
	expression                string
}

func NewCleanInternalBucketJob(
	expression string,
	sharedPoolRepo repo.SharedPoolRepo,
	sharedPoolObjectStoreRepo repo.SharedPoolObjectStoreRepo,
	bucketSvc dplanemgrv1.BucketServiceClient,
) *CleanInternalBucketJob {
	return &CleanInternalBucketJob{
		expression:                expression,
		sharedPoolRepo:            sharedPoolRepo,
		sharedPoolObjectStoreRepo: sharedPoolObjectStoreRepo,
		bucketSvc:                 bucketSvc,
	}
}

func (j *CleanInternalBucketJob) Name() string {
	return "CleanInternalBucketJob"
}

func (j *CleanInternalBucketJob) CronExpression() string {
	return j.expression
}

func (j *CleanInternalBucketJob) Execute(ctx context.Context) error {
	tilog.Info(ctx, "start CleanInternalBucketJob execution")

	internalBuckets, err := j.sharedPoolObjectStoreRepo.ListWithDeletedSharedPool(ctx, []string{}, []string{schema.SharedPoolObjectStoreTypeInternal})
	if err != nil {
		return fmt.Errorf("failed to list shared pools: %w", err)
	}

	if len(internalBuckets) == 0 {
		tilog.PlainInfo("no shared pools found, skip CleanInternalBucketJob")
		return nil
	}

	for _, ib := range internalBuckets {
		sp, err := j.sharedPoolRepo.Load(ctx, ib.SharedPoolID, true)
		if err != nil {
			tilog.Error(ctx, "failed to load shared pool", zap.String("sharedPoolId", ib.SharedPoolID), zap.Error(err))
			continue
		}

		// only 7 days after deletion
		if sp.DeletedAt == nil || sp.DeletedAt.IsZero() {
			tilog.Info(ctx, "shared pool not deleted or deletedAt is zero, skip delete internal bucket", zap.String("sharedPoolId", ib.SharedPoolID))
			continue
		}

		if time.Since(*sp.DeletedAt) < 7*24*time.Hour {
			tilog.Info(ctx, "shared pool deleted less than 7 days ago, skip delete internal bucket", zap.String("sharedPoolId", ib.SharedPoolID))
		} else {
			// delete internal bucket
			tilog.Info(ctx, "deleting internal bucket for shared pool", zap.String("sharedPoolId", ib.SharedPoolID))
			// get internal bucket id and dataplane id
			internalBucketId := entity.GetInternalBucketId(ib.SharedPoolID, ib.StoreRegion)

			dataplaneId := sp.GetDataplaneName()

			// first get bucket info to check if bucket exists
			_, err = j.bucketSvc.GetBucket(ctx, &dplanemgrv1.GetBucketRequest{
				DataplaneId: dataplaneId,
				BucketId:    internalBucketId,
			})
			if err != nil {
				st, ok := status.FromError(err)
				if ok && st.Code() == codes.NotFound {
					tilog.Info(ctx, "internal bucket not found, skip delete", zap.String("sharedPoolId", sp.GetId()))
					// delete internal bucket record in db
					err = j.sharedPoolObjectStoreRepo.Delete(ctx, ib.SharedPoolID, schema.SharedPoolObjectStoreTypeInternal)
					if err != nil {
						tilog.Error(ctx, "failed to delete internal bucket record in db", zap.String("sharedPoolId", sp.GetId()), zap.Error(err))
					}

					continue
				} else {
					tilog.Error(ctx, "failed to get internal bucket for shared pool", zap.String("sharedPoolId", sp.GetId()), zap.Error(err))
					continue
				}
			}

			_, err = j.bucketSvc.DeleteBucket(ctx, &dplanemgrv1.DeleteBucketRequest{
				DataplaneId: dataplaneId,
				BucketId:    internalBucketId,
			})
			if err != nil {
				tilog.Error(ctx, "failed to delete internal bucket for shared pool", zap.String("sharedPoolId", sp.GetId()), zap.Error(err))
			} else {
				tilog.Info(ctx, "internal bucket deleted successfully", zap.String("sharedPoolId", sp.GetId()))

				// delete internal bucket record in db
				err = j.sharedPoolObjectStoreRepo.Delete(ctx, ib.SharedPoolID, schema.SharedPoolObjectStoreTypeInternal)
				if err != nil {
					tilog.Error(ctx, "failed to delete internal bucket record in db", zap.String("sharedPoolId", sp.GetId()), zap.Error(err))
				}
			}
		}
	}

	return nil
}
