package cron

import (
	"context"
	"fmt"
	"time"

	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/br"
	"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 CleanExpiredAutoBackupDataJob struct {
	expression                string
	gcRepo                    repo.BackupGcSettingRepo
	sharedPoolRepo            repo.SharedPoolRepo
	sharedPoolObjectStoreRepo repo.SharedPoolObjectStoreRepo
	bucketSvc                 dplanemgrv1.BucketServiceClient
}

func NewCleanExpiredAutoBackupDataJob(
	expression string,
	gcRepo repo.BackupGcSettingRepo,
	sharedPoolRepo repo.SharedPoolRepo,
	sharedPoolObjectStoreRepo repo.SharedPoolObjectStoreRepo,
	bucketSvc dplanemgrv1.BucketServiceClient,
) *CleanExpiredAutoBackupDataJob {
	return &CleanExpiredAutoBackupDataJob{
		expression:                expression,
		gcRepo:                    gcRepo,
		sharedPoolRepo:            sharedPoolRepo,
		sharedPoolObjectStoreRepo: sharedPoolObjectStoreRepo,
		bucketSvc:                 bucketSvc,
	}
}

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

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

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

	// get all possible auto backup expired settings
	gcSettings, err := j.gcRepo.List(ctx, &repo.BackupGcSettingFilter{
		IncludeStatuses: []string{schema.BackupGcStatusSPDeleted},
	})
	if err != nil {
		tilog.Error(ctx, "failed to list backup gc settings", zap.Error(err))
		return fmt.Errorf("failed to list backup gc settings: %w", err)
	}
	if len(gcSettings) == 0 {
		tilog.Info(ctx, "no backup gc settings found")
		return nil
	}

	for _, gcSetting := range gcSettings {
		// check if auto backup is expired
		maxRetentionDays := gcSetting.GetMaxAutoBackupDataRetentionDays()
		if gcSetting.SharedPoolDeletedAt.Add(time.Duration(maxRetentionDays) * 24 * time.Hour).After(time.Now()) {
			tilog.Info(ctx, "auto backup data is not expired",
				zap.String("shared_pool_id", gcSetting.SharedPoolID),
				zap.Duration("max_retention_days", time.Duration(maxRetentionDays)*24*time.Hour),
				zap.Time("shared_pool_deleted_at", *gcSetting.SharedPoolDeletedAt),
			)
			continue
		}
		tilog.Info(ctx, "auto backup data is expired",
			zap.String("shared_pool_id", gcSetting.SharedPoolID),
			zap.Duration("max_retention_days", time.Duration(maxRetentionDays)*24*time.Hour),
			zap.Time("shared_pool_deleted_at", *gcSetting.SharedPoolDeletedAt),
		)

		// set lifecycle rule
		bucketInfo, err := j.sharedPoolObjectStoreRepo.Load(ctx, gcSetting.SharedPoolID, schema.SharedPoolObjectStoreTypeData, true)
		if err != nil {
			tilog.Error(ctx, "failed to load data bucket info", zap.Error(err), zap.String("shared_pool_id", gcSetting.SharedPoolID))
			continue
		}
		sharedPool, err := j.sharedPoolRepo.Load(ctx, gcSetting.SharedPoolID, true)
		if err != nil {
			tilog.Error(ctx, "failed to load shared pool", zap.Error(err), zap.String("shared_pool_id", gcSetting.SharedPoolID))
			continue
		}

		// put it here to generate lifecycle rules based on the status
		gcSetting.Status = schema.BackupGcStatusAutoBackupExpired

		// apply lifecycle rules
		_, err = j.bucketSvc.GetBucket(ctx, &dplanemgrv1.GetBucketRequest{
			DataplaneId: sharedPool.GetDataplaneName(),
			BucketId:    bucketInfo.StoreBucket,
		})
		if err != nil {
			if status.Code(err) == codes.NotFound {
				tilog.Info(ctx, "bucket not found, skip apply lifecycle rules", zap.String("bucketId", bucketInfo.StoreBucket))
			} else {
				tilog.Error(ctx, "failed to get bucket info from dataplane manager", zap.Error(err), zap.String("shared_pool_id", gcSetting.SharedPoolID))
				continue
			}
		} else {
			req := &dplanemgrv1.ApplyLifecycleRulesRequest{
				DataplaneId: sharedPool.GetDataplaneName(),
				BucketId:    bucketInfo.StoreBucket,
				Rules:       br.MakeLifecycleRules(gcSetting, sharedPool),
			}
			resp, err := j.bucketSvc.ApplyLifecycleRules(ctx, req)
			if err != nil {
				tilog.Error(ctx, "failed to apply lifecycle rules for auto backup expired bucket", zap.Any("request", req), zap.Error(err))
				continue
			}
			tilog.Info(ctx, "successfully apply lifecycle rules for auto backup expired bucket", zap.Any("response", resp), zap.Any("request", req))
		}

		// save expired status
		if err := j.gcRepo.Save(ctx, gcSetting); err != nil {
			tilog.Error(ctx, "failed to save backup gc setting for auto backup expired", zap.Error(err), zap.String("shared_pool_id", gcSetting.SharedPoolID))
			continue
		}
		tilog.Info(ctx, "successfully save backup gc setting for auto backup expired", zap.String("shared_pool_id", gcSetting.SharedPoolID))
	}

	return nil
}
