package br

import (
	"context"
	"fmt"
	"strings"

	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/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"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"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

const DeleteAllTiBRsActionName = "delete-all-tibrs"

type deleteAllTiBRsAction struct {
	dataplaneManager dataplane.Manager
	sharedPoolRepo   repo.SharedPoolRepo
}

func NewDeleteAllTiBRsAction(
	sharedPoolRepo repo.SharedPoolRepo,
	dataplaneManager dataplane.Manager) actions.Action {
	return &deleteAllTiBRsAction{
		sharedPoolRepo:   sharedPoolRepo,
		dataplaneManager: dataplaneManager,
	}
}

func (a *deleteAllTiBRsAction) Name() string {
	return DeleteAllTiBRsActionName
}

func (a *deleteAllTiBRsAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "delete all tibrs...")
	sharedPoolId, err := args.GetString(workflow.SharedPoolIdKey)
	if err != nil {
		return fmt.Errorf("failed to get sharedPool id: %w", err)
	}
	sharedPool, err := a.sharedPoolRepo.Load(ctx, sharedPoolId, true)
	if err != nil {
		return fmt.Errorf("failed to load sharedPool: %w", err)
	}

	remoteCli, err := a.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		if status.Code(err) == codes.NotFound {
			tilog.Warn(ctx, "dataplane not found, skip delete all tibrs", zap.String("dataplaneName", sharedPool.GetDataplaneName()), zap.Error(err))
			return nil
		}
		tilog.Error(ctx, "failed to get remote client for shared pool", zap.String("dataplaneName", sharedPool.GetDataplaneName()), zap.Error(err))
		return fmt.Errorf("failed to get remote client for shared pool: %w", err)
	}

	namespace := sharedPool.GetDataPlaneNamespace()

	// list all tibrs in the namespace, filter by cluster name
	tibrList, certificates, err := a.getAllTiBRsAndCertificates(ctx, remoteCli, "db", namespace)
	if err != nil {
		return fmt.Errorf("failed to get all tibrs and certificates: %w", err)
	}
	// delete all tibr
	for _, tibr := range tibrList {
		if err = remoteCli.Delete(ctx, tibr); err != nil {
			tilog.Error(ctx, "failed to delete tibr", zap.String("tibrName", tibr.Name), zap.String("namespace", namespace), zap.Error(err))
			return fmt.Errorf("failed to delete tibr %s: %w", tibr.Name, err)
		}
		tilog.Info(ctx, "deleted tibr", zap.String("tibrName", tibr.Name), zap.String("namespace", namespace))
	}
	// delete all certificates
	for _, cert := range certificates {
		if err = remoteCli.Delete(ctx, cert); err != nil {
			tilog.Error(ctx, "failed to delete certificate", zap.String("certName", cert.Name), zap.String("namespace", namespace), zap.Error(err))
			return fmt.Errorf("failed to delete certificate %s: %w", cert.Name, err)
		}
		tilog.Info(ctx, "deleted certificate", zap.String("certName", cert.Name), zap.String("namespace", namespace))
	}

	if len(tibrList) != 0 || len(certificates) != 0 {
		return fmt.Errorf("not all tibrs and certificates were deleted done, let next retry check again")
	}

	tilog.Info(ctx, "all tibrs and certificates deleted", zap.String("namespace", namespace))

	return nil
}

func (a *deleteAllTiBRsAction) getAllTiBRsAndCertificates(ctx context.Context,
	cli client.Client, clusterName, namespace string,
) (tibrs []*v1alpha1br.TiBR, certificates []*v1.Certificate, err error) {

	var tibrList v1alpha1br.TiBRList
	err = cli.List(ctx, &tibrList, &client.ListOptions{Namespace: namespace})
	if err != nil {
		tilog.Error(ctx, "failed to list tibr", zap.String("namespace", namespace), zap.Error(err))
		return nil, nil, fmt.Errorf("failed to list tibr: %w", err)
	}
	//nolint:gocritic // Items is not defined in this repo, so safe to ignore value copy
	for _, tibr := range tibrList.Items {
		if tibr.Spec.Cluster.Name == clusterName {
			tibrs = append(tibrs, &tibr)
		}
	}

	var certList v1.CertificateList
	err = cli.List(ctx, &certList, &client.ListOptions{Namespace: namespace})
	if err != nil {
		tilog.Error(ctx, "failed to list certificates", zap.String("namespace", namespace), zap.Error(err))
		return nil, nil, fmt.Errorf("failed to list certificates: %w", err)
	}
	//nolint:gocritic // Items is not defined in this repo, so safe to ignore value copy
	for _, certificate := range certList.Items {
		if !strings.HasSuffix(certificate.Name, cert.TiBRCertificateSuffix) {
			continue
		}
		for _, tibr := range tibrs {
			if certificate.Name == cert.TiBRCertName(tibr.Name) {
				certificates = append(certificates, &certificate)
				break
			}
		}
	}

	return tibrs, certificates, nil
}
