package br

import (
	"context"
	"fmt"

	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/mysql/schema"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const MarkManualBackupDeletedActionName = "mark-manual-backup-deleted"

type markManualBackupDeletedAction struct {
	manualBackupRepo    repo.ManualBackupRepo
	backupGcSettingRepo repo.BackupGcSettingRepo
	transactioner       db.Transactioner
}

func NewMarkManualBackupDeletedAction(manualBackupRepo repo.ManualBackupRepo, backupGcSettingRepo repo.BackupGcSettingRepo, transactioner db.Transactioner) actions.Action {
	return &markManualBackupDeletedAction{manualBackupRepo: manualBackupRepo, backupGcSettingRepo: backupGcSettingRepo, transactioner: transactioner}
}

func (a *markManualBackupDeletedAction) Name() string {
	return MarkManualBackupDeletedActionName
}

func (a *markManualBackupDeletedAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, fmt.Sprintf("start %s action...", a.Name()))

	backupID, ok := GetManualBackupID(args)
	if !ok {
		return fmt.Errorf("manual backup id is not found")
	}

	clusterID, ok := GetTiDBClusterID(args)
	if !ok {
		return fmt.Errorf("tidb cluster id is not found")
	}

	manualBackup, err := a.manualBackupRepo.Load(ctx, clusterID, backupID, true)
	if err != nil {
		tilog.Error(ctx, "failed to load manual backup", zap.Error(err), zap.String("backupId", backupID), zap.String("clusterId", clusterID))
		return fmt.Errorf("failed to load manual backup: %w", err)
	}

	err = AddActionForManualBackup(ctx, a.manualBackupRepo, manualBackup, schema.ManualBackupActionMarkDeleted)
	if err != nil {
		tilog.Error(ctx, "failed to add action to manual backup", zap.Error(err), zap.String("backupId", backupID), zap.String("clusterId", clusterID))
		return fmt.Errorf("failed to add action to manual backup: %w", err)
	}

	err = a.manualBackupRepo.SetStatus(ctx, clusterID, backupID, schema.ManualBackupStatusDeleted)
	if err != nil {
		tilog.Error(ctx, "failed to set manual backup status to deleted", zap.Error(err), zap.String("backupId", backupID), zap.String("clusterId", clusterID))
		return fmt.Errorf("failed to set manual backup status to deleted: %w", err)
	}

	err = a.transactioner.Transaction(ctx, func(txCtx context.Context) error {
		if err := a.manualBackupRepo.Delete(txCtx, clusterID, backupID); err != nil {
			return fmt.Errorf("failed to soft-delete manual backup: %w", err)
		}

		hasManualBackup, err := a.manualBackupRepo.ExistsUndeletedBySharedPool(txCtx, manualBackup.SharedPoolID)
		if err != nil {
			return fmt.Errorf("failed to check remaining manual backups: %w", err)
		}
		if hasManualBackup {
			return nil
		}

		gcSetting, err := a.backupGcSettingRepo.Load(txCtx, manualBackup.SharedPoolID)
		if err == nil {
			gcSetting.HasManualBackup = new(bool)
			*gcSetting.HasManualBackup = false
			if err := a.backupGcSettingRepo.Save(txCtx, gcSetting); err != nil {
				return fmt.Errorf("failed to update has_manual_backup=false: %w", err)
			}
			return nil
		}
		if status.Code(err) != codes.NotFound {
			return fmt.Errorf("failed to load backup gc setting: %w", err)
		}
		return nil
	})
	if err != nil {
		tilog.Error(ctx, "failed to mark manual backup deleted transactionally", zap.Error(err), zap.String("backupId", backupID), zap.String("clusterId", clusterID), zap.String("sharedPoolId", manualBackup.SharedPoolID))
		return err
	}

	tilog.Info(ctx, "mark manual backup deleted and soft-deleted", zap.String("backupId", backupID), zap.String("clusterId", clusterID))
	return nil
}
