package br

import (
	"context"
	"fmt"
	"time"

	v1alpha1br "github.com/pingcap/tidb-operator/api/v2/br/v1alpha1"
	"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"
	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 DeleteTiBRRestoreActionName = "delete-tibr-restore"

type deleteTiBRRestoreAction struct {
	dataplaneManager dataplane.Manager
	sharedPoolRepo   repo.SharedPoolRepo
	lwRestoreRepo    repo.LightweightRestoreRepo
}

func NewDeleteTiBRRestoreAction(
	lwRestoreRepo repo.LightweightRestoreRepo,
	sharedPoolRepo repo.SharedPoolRepo,
	dataplaneManager dataplane.Manager) actions.Action {
	return actions.CreateActionFromAsync(&deleteTiBRRestoreAction{
		lwRestoreRepo:    lwRestoreRepo,
		sharedPoolRepo:   sharedPoolRepo,
		dataplaneManager: dataplaneManager,
	}, 1*time.Minute, 30*time.Minute)
}

func (a *deleteTiBRRestoreAction) Name() string {
	return DeleteTiBRRestoreActionName
}

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

	restoreID, existed := GetRestoreID(args)
	if !existed {
		return fmt.Errorf("restore id is required, but not found in args: %v", args)
	}
	err := AddActionForLWRestore(ctx, a.lwRestoreRepo, restoreID, schema.LightweightRestoreActionDeleteTiBR)
	if err != nil {
		return fmt.Errorf("failed to add action to restore: %w", err)
	}

	remoteCli, tibrName, namespace, err := a.getDeleteTiBRRestoreContextInfos(ctx, restoreID)
	if err != nil {
		return fmt.Errorf("failed to get delete tibr restore context infos: %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", restoreID, 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 *deleteTiBRRestoreAction) CheckFinish(args actions.ActionArgs) (bool, error) {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "check delete tibr restore finished...")

	restoreID, existed := GetRestoreID(args)
	if !existed {
		return false, fmt.Errorf("restore id is required, but not found in args: %v", args)
	}

	remoteCli, tibrName, namespace, err := a.getDeleteTiBRRestoreContextInfos(ctx, restoreID)
	if err != nil {
		return false, fmt.Errorf("failed to get delete tibr restore 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 *deleteTiBRRestoreAction) getDeleteTiBRRestoreContextInfos(ctx context.Context, restoreID string) (remoteCli client.Client, tibrName, namespace string, err error) {
	tilog.Info(ctx, "get delete tibr restore context infos...")

	lwRestore, err := a.lwRestoreRepo.Load(ctx, restoreID, false)
	if err != nil {
		err = fmt.Errorf("failed to load lw restore: %w", err)
		return
	}

	targetSharedPool, err := a.sharedPoolRepo.Load(ctx, lwRestore.TargetSharedPoolID, false)
	if err != nil {
		err = fmt.Errorf("failed to load target sharedPool: %w", err)
		return
	}
	namespace = targetSharedPool.GetDataPlaneNamespace()

	remoteCli, err = a.dataplaneManager.GetClient(targetSharedPool.GetDataplaneName())
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for target shared pool", zap.String("dataplaneName", targetSharedPool.GetDataplaneName()), zap.Error(err))
		err = fmt.Errorf("failed to get remote client for target shared pool: %w", err)
		return
	}
	tibrName = tioperator.TiBRCRNameOfRestore(restoreID)

	tilog.Info(ctx, "get delete tibr restore context infos success", zap.String("tibrName", tibrName), zap.String("namespace", namespace))

	return
}
