package br

import (
	"context"
	"errors"
	"fmt"
	"time"

	v1alpha1br "github.com/pingcap/tidb-operator/api/v2/br/v1alpha1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/cert"
	"github.com/tidbcloud/cluster-service-ng/pkg/dataplane"
	"github.com/tidbcloud/cluster-service-ng/pkg/k8s"
	"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/tioperator"
	"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"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const DeleteTiBRManualBackupActionName = "delete-tibr-manual-backup"

var errDeletedPoolDataplaneNotFound = errors.New("deleted shared pool dataplane not found")

type deleteTiBRManualBackupAction struct {
	dataplaneManager dataplane.Manager
	sharedPoolRepo   repo.SharedPoolRepo
	manualBackupRepo repo.ManualBackupRepo
}

func NewDeleteTiBRManualBackupAction(
	manualBackupRepo repo.ManualBackupRepo,
	sharedPoolRepo repo.SharedPoolRepo,
	dataplaneManager dataplane.Manager) actions.Action {
	return actions.CreateActionFromAsync(&deleteTiBRManualBackupAction{
		manualBackupRepo: manualBackupRepo,
		sharedPoolRepo:   sharedPoolRepo,
		dataplaneManager: dataplaneManager,
	}, 1*time.Minute, 30*time.Minute)
}

func (a *deleteTiBRManualBackupAction) Name() string {
	return DeleteTiBRManualBackupActionName
}

func (a *deleteTiBRManualBackupAction) Start(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "delete tibr manual backup...")

	remoteCli, tibrName, namespace, manualBackup, err := a.getContextInfos(ctx, args)
	if err != nil {
		if errors.Is(err, errDeletedPoolDataplaneNotFound) {
			tilog.Info(ctx, "skip TiBR deletion because deleted shared pool dataplane is gone")
			return nil
		}
		if skip, skipErr := skipManualBackupActionForMissingSharedPool(ctx, a.manualBackupRepo, manualBackup, err, a.Name()); skip {
			return skipErr
		}
		return fmt.Errorf("failed to get delete tibr manual backup context infos: %w", err)
	}

	err = AddActionForManualBackup(ctx, a.manualBackupRepo, manualBackup, schema.ManualBackupActionDeleteTiBR)
	if err != nil {
		return fmt.Errorf("failed to add action to manual backup: %w", err)
	}

	// delete tibr
	err = k8s.DeleteObject(ctx, remoteCli, &v1alpha1br.TiBR{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tibrName,
			Namespace: namespace,
		},
	})
	if err != nil {
		tilog.Error(ctx, "failed to delete tibr", zap.String("tibrName", tibrName), zap.String("namespace", namespace), zap.Error(err))
		return fmt.Errorf("failed to delete tibr %s: %w", tibrName, err)
	}

	// delete certificate
	err = k8s.DeleteObject(ctx, remoteCli, cert.TiBRCert(tibrName, namespace))
	if err != nil {
		tilog.Error(ctx, "failed to delete certificate for tibr", zap.String("tibrName", tibrName), zap.String("namespace", namespace), zap.Error(err))
		return fmt.Errorf("failed to delete certificate for tibr %s: %w", tibrName, err)
	}

	tilog.Info(ctx, "tibr and certificates deleted", zap.String("tibrName", tibrName), zap.String("namespace", namespace))

	return nil
}

func (a *deleteTiBRManualBackupAction) CheckFinish(args actions.ActionArgs) (bool, error) {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "check delete tibr manual backup finished...")

	remoteCli, tibrName, namespace, manualBackup, err := a.getContextInfos(ctx, args)
	if err != nil {
		if errors.Is(err, errDeletedPoolDataplaneNotFound) {
			tilog.Info(ctx, "TiBR deletion is complete because deleted shared pool dataplane is gone")
			return true, nil
		}
		if skip, skipErr := skipManualBackupActionForMissingSharedPool(ctx, a.manualBackupRepo, manualBackup, err, a.Name()); skip {
			return true, skipErr
		}
		return false, fmt.Errorf("failed to get delete tibr manual backup context infos: %w", err)
	}

	// check tibr deleted
	tibr := &v1alpha1br.TiBR{}
	err = remoteCli.Get(ctx, types.NamespacedName{
		Name:      tibrName,
		Namespace: namespace,
	}, tibr)
	if err != nil && !apierrors.IsNotFound(err) {
		return false, fmt.Errorf("failed to get tibr %s: %w", tibrName, err)
	}

	if err != nil && apierrors.IsNotFound(err) {
		tilog.Info(ctx, "tibr deletion finished", zap.String("tibrName", tibrName), zap.String("namespace", namespace))
		return true, nil
	}

	tilog.Info(ctx, "tibr deletion not finished...", zap.String("tibrName", tibrName), zap.String("namespace", namespace))
	return false, nil
}

func (a *deleteTiBRManualBackupAction) getContextInfos(ctx context.Context, args actions.ActionArgs) (remoteCli client.Client, tibrName, namespace string, manualBackup *entity.ManualBackup, err error) {
	tilog.Info(ctx, "get delete tibr manual backup context infos...")

	targetSharedPool, remoteCli, manualBackup, err := getManualBackupActionContextInfosWithDeletedPool(
		ctx, args, a.manualBackupRepo, a.sharedPoolRepo, a.dataplaneManager, true,
	)
	if err != nil {
		if targetSharedPool != nil && targetSharedPool.DeletedAt != nil && status.Code(err) == codes.NotFound {
			err = fmt.Errorf("%w: %w", errDeletedPoolDataplaneNotFound, err)
			return
		}
		err = fmt.Errorf("failed to get manual backup action context infos: %w", err)
		return
	}

	namespace = targetSharedPool.GetDataPlaneNamespace()
	tibrName = tioperator.TiBRCRNameOfManualBackup(manualBackup.ClusterID, manualBackup.BackupID)

	tilog.Info(ctx, "get delete tibr manual backup context infos success", zap.String("tibrName", tibrName), zap.String("namespace", namespace))
	return
}
