package br

import (
	"fmt"
	"slices"
	"time"

	v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
	v1alpha1br "github.com/pingcap/tidb-operator/api/v2/br/v1alpha1"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"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"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// this action
// 1. is used to delete the tibrgc when sharedpool is deleted
// 2. change the status of backup gc setting to sp_deleted, update the lifecycle rules related parameters
const DeleteTiBRGCActionName = "delete-tibrgc"

type deleteTiBRGCAction struct {
	gcRepo           repo.BackupGcSettingRepo
	sharedPoolRepo   repo.SharedPoolRepo
	dataplaneManager dataplane.Manager
}

func NewDeleteTiBRGCAction(gcRepo repo.BackupGcSettingRepo, sharedPoolRepo repo.SharedPoolRepo, dataplaneManager dataplane.Manager) actions.Action {
	return actions.CreateActionFromAsync(&deleteTiBRGCAction{gcRepo: gcRepo, sharedPoolRepo: sharedPoolRepo, dataplaneManager: dataplaneManager}, time.Minute, time.Minute*20)
}

func (a *deleteTiBRGCAction) Name() string {
	return DeleteTiBRGCActionName
}

func (a *deleteTiBRGCAction) Start(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "delete tibrgc...")
	sharedPoolId, err := args.GetString(workflow.SharedPoolIdKey)
	if err != nil {
		tilog.Error(ctx, "failed to get shared pool id", zap.Error(err))
		return fmt.Errorf("failed to get shared pool id: %w", err)
	}

	backupGcSetting, err := a.gcRepo.Load(ctx, sharedPoolId)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			tilog.Error(ctx, "failed to load backup gc setting", zap.Error(err))
			return fmt.Errorf("failed to load backup gc setting: %w", err)
		}
		tilog.Info(ctx, "backup gc setting not found, skip delete tibrgc", zap.String("sharedPoolId", sharedPoolId))
		return nil
	}
	if !slices.Contains(schema.BackupGcStatusesOfLivingSharedpool, backupGcSetting.Status) {
		tilog.Info(ctx, "shared pool is deleted, skip delete backup gc setting", zap.String("sharedPoolId", sharedPoolId))
		return nil
	}

	sharedPool, err := a.sharedPoolRepo.Load(ctx, sharedPoolId, true)
	if err != nil {
		tilog.Error(ctx, "failed to load shared pool", zap.Error(err))
		return fmt.Errorf("failed to load sharedPool: %w", err)
	}

	remoteCli, err := a.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for shared pool", zap.Error(err))
		return fmt.Errorf("failed to get remote client for shared pool: %w", err)
	}

	err = k8s.DeleteObject(ctx, remoteCli, cert.TiBRGCRCert(tioperator.DefaultTiBRGCRName, sharedPool.GetDataPlaneNamespace()))
	if err != nil {
		tilog.Error(ctx, "failed to delete tibrgc cert", zap.String("tibrgcName", tioperator.DefaultTiBRGCRName), zap.String("namespace", sharedPool.GetDataPlaneNamespace()), zap.Error(err))
		return fmt.Errorf("failed to delete tibrgc cert: %w", err)
	}

	tibrgcCR := &v1alpha1br.TiBRGC{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tioperator.DefaultTiBRGCRName,
			Namespace: sharedPool.GetDataPlaneNamespace(),
		},
	}

	err = k8s.DeleteObject(ctx, remoteCli, tibrgcCR)
	if err != nil {
		tilog.Error(ctx, "failed to delete tibrgc", zap.String("tibrgcName", tioperator.DefaultTiBRGCRName), zap.String("namespace", sharedPool.GetDataPlaneNamespace()), zap.Error(err))
		return fmt.Errorf("failed to delete tibrgc: %w", err)
	}
	tilog.Info(ctx, "tibrgc deleted", zap.String("tibrgcName", tioperator.DefaultTiBRGCRName), zap.String("namespace", sharedPool.GetDataPlaneNamespace()))

	return nil
}

func (a *deleteTiBRGCAction) CheckFinish(args actions.ActionArgs) (bool, error) {
	ctx := utils.GenerateContext(args)
	sharedPoolId, err := args.GetString(workflow.SharedPoolIdKey)
	if err != nil {
		tilog.Error(ctx, "failed to get shared pool id", zap.Error(err))
		return false, fmt.Errorf("failed to get shared pool id: %w", err)
	}

	backupGcSetting, err := a.gcRepo.Load(ctx, sharedPoolId)
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Info(ctx, "backup gc setting not found, skip delete tibrgc check finish", zap.String("shared_pool_id", sharedPoolId))
			return true, nil
		}
		tilog.Error(ctx, "failed to load backup gc setting", zap.Error(err))
		return false, fmt.Errorf("failed to load backup gc setting: %w", err)
	}
	if !slices.Contains(schema.BackupGcStatusesOfLivingSharedpool, backupGcSetting.Status) {
		tilog.Info(ctx, "tibrgc is already in deleted status, no need to check again", zap.Any("backupGcSetting", backupGcSetting))
		return true, nil
	}

	sharedPool, err := a.sharedPoolRepo.Load(ctx, sharedPoolId, true)
	if err != nil {
		tilog.Error(ctx, "failed to load shared pool", zap.Error(err))
		return false, fmt.Errorf("failed to load sharedPool: %w", err)
	}

	remoteCli, err := a.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		tilog.Error(ctx, "failed to get remote client for shared pool", zap.Error(err))
		return false, fmt.Errorf("failed to get remote client for shared pool: %w", err)
	}

	certificate := &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tioperator.DefaultTiBRGCRName,
			Namespace: sharedPool.GetDataPlaneNamespace(),
		},
	}
	err = remoteCli.Get(ctx, client.ObjectKeyFromObject(certificate), certificate)
	if err == nil {
		tilog.Info(ctx, "tibrgc certificate is still exist, need to check again later", zap.Any("certificate", certificate))
		return false, nil
	}
	if !apierrors.IsNotFound(err) {
		tilog.Error(ctx, "failed to get tibrgc certificate", zap.Any("certificate", certificate), zap.Error(err))
		return false, fmt.Errorf("failed to get tibrgc certificate: %w", err)
	}

	tibrgcCR := &v1alpha1br.TiBRGC{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tioperator.DefaultTiBRGCRName,
			Namespace: sharedPool.GetDataPlaneNamespace(),
		},
	}
	err = remoteCli.Get(ctx, client.ObjectKeyFromObject(tibrgcCR), tibrgcCR)
	if err == nil {
		tilog.Info(ctx, "tibrgc is still exist,  need to check again later", zap.Any("tibrgcCR", tibrgcCR))
		return false, nil
	}
	if !apierrors.IsNotFound(err) {
		tilog.Error(ctx, "failed to get tibrgc", zap.Any("tibrgcCR", tibrgcCR), zap.Error(err))
		return false, fmt.Errorf("failed to get tibrgc: %w", err)
	}

	tilog.Info(ctx, "successfully delete tibrgc, check finished", zap.Any("backupGcSetting", backupGcSetting))

	return true, nil
}
