package action

import (
	"context"
	"fmt"

	certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
	tiproxyv1alpha1 "github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"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/repo"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/encoding/protojson"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// CleanUpCertAction is an action to clean up the pvcs
type CleanUpCertAction struct {
	sharedPoolRepo   repo.SharedPoolRepo
	dataplaneManager dataplane.Manager
}

const CleanUpCertActionName = "clean-up-cert"

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

func (a *CleanUpCertAction) Name() string {
	return CleanUpCertActionName
}

// Invoke clean up the pvcs
func (a *CleanUpCertAction) Invoke(args actions.ActionArgs) error {

	// generate context
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "clean up cert action invoked")

	_, err := args.GetString(workflow.ComponentIdsKey)
	if err != nil {
		return fmt.Errorf("target component id is required, but not found")
	}

	sharedPoolStr, err := args.GetString(workflow.SharedPoolKey)
	if err != nil {
		return fmt.Errorf("failed to get sharedPool: %w", err)
	}

	var targetSharedPool entity.SharedPool
	err = protojson.Unmarshal([]byte(sharedPoolStr), &targetSharedPool)
	if err != nil {
		tilog.Error(ctx, "failed to unmarshal sharedPool", zap.Error(err))
		return fmt.Errorf("failed to unmarshal sharedPool: %w", err)
	}

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

	for _, comp := range targetSharedPool.GetComponentGroups() {

		// check if the component is target component
		if !workflow.CheckIsTargetComponent(ctx, comp, args) {
			continue
		}

		if comp.GetPhase() != apiv1.Component_DELETING {
			tilog.Info(ctx, "component is active, skip clean up cert", zap.String("componentId type", comp.GetComponentType().String()), zap.String("componentId", comp.GetId()))
			continue
		}

		var remoteCli client.Client

		// check comp running status
		for _, currComp := range currSharedPool.GetComponentGroups() {
			if currComp.GetId() != comp.GetId() || currComp.GetComponentType() != comp.GetComponentType() {
				continue
			}

			stillRunning := true
			if comp.GetComponentType() == apiv1.ComponentType_TIPROXY {
				if remoteCli == nil {
					remoteCli, err = a.dataplaneManager.GetClient(currSharedPool.GetDataplaneName())
					if err != nil {
						tilog.Error(ctx, "failed to get remote client for shared pool", zap.String("dataplaneName", currSharedPool.GetDataplaneName()), zap.Error(err))
						return fmt.Errorf("failed to get remote client for shared pool: %w", err)
					}
				}

				stillRunning, err = tiProxyGroupExists(ctx, remoteCli, currSharedPool, comp)
				if err != nil {
					return err
				}
			}
			if !stillRunning {
				break
			}
			tilog.Warn(ctx, "component is running, skip clean up cert", zap.String("componentId type", comp.GetComponentType().String()), zap.String("componentId", comp.GetId()))
			return fmt.Errorf("component is running, skip clean up cert")
		}

		if comp.GetComponentType() == apiv1.ComponentType_TIDB || comp.GetComponentType() == apiv1.ComponentType_TIDB_WORKER {
			tilog.Info(ctx, "tidb/tidb worker comp use global cert, skip delete", zap.String("componentId type", comp.GetComponentType().String()), zap.String("componentId", comp.GetId()))
			continue
		}

		// clean up cert
		compCert := cert.ComponentCert(entity.GetComponentTypeName(comp), comp.GetId(), currSharedPool.GetDataPlaneNamespace())

		if remoteCli == nil {
			remoteCli, err = a.dataplaneManager.GetClient(currSharedPool.GetDataplaneName())
			if err != nil {
				tilog.Error(ctx, "failed to get remote client for shared pool", zap.String("dataplaneName", currSharedPool.GetDataplaneName()), zap.Error(err))
				return fmt.Errorf("failed to get remote client for shared pool: %w", err)
			}
		}
		if err := k8s.DeleteObject(ctx, remoteCli, compCert); err != nil {
			tilog.Error(ctx, "failed to delete cert", zap.Any("certName", compCert.Name), zap.Error(err))
			return fmt.Errorf("failed to delete cert: %w", err)
		}
		tilog.Info(ctx, "successfully cleaned up cert", zap.String("certName", compCert.Name), zap.String("namespace", compCert.Namespace))

		// delete secret
		compCertSecret := &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      compCert.GetName(),
				Namespace: currSharedPool.GetDataPlaneNamespace(),
			},
		}
		if err := k8s.DeleteObject(ctx, remoteCli, compCertSecret); err != nil {
			tilog.Error(ctx, "failed to delete cert secret", zap.String("certName", compCertSecret.Name), zap.Error(err))
			return fmt.Errorf("failed to delete cert secret: %w", err)
		}
		tilog.Info(ctx, "successfully cleaned up cert secret", zap.String("cert secret Name", compCertSecret.Name), zap.String("namespace", compCertSecret.Namespace))

		if comp.GetComponentType() == apiv1.ComponentType_TIPROXY {
			// delete tiproxy server cert
			tiproxyServerCert := &certmanagerv1.Certificate{
				ObjectMeta: metav1.ObjectMeta{
					Name:      cert.ServerCertSecret(entity.GetComponentTypeName(comp), comp.GetId()),
					Namespace: currSharedPool.GetDataPlaneNamespace(),
				},
			}

			if err := k8s.DeleteObject(ctx, remoteCli, tiproxyServerCert); err != nil {
				tilog.Error(ctx, "failed to delete tiproxy cert", zap.String("certName", tiproxyServerCert.Name), zap.Error(err))
				return fmt.Errorf("failed to delete tiproxy cert: %w", err)
			}
			tilog.Info(ctx, "successfully cleaned up tiproxy cert", zap.String("certName", tiproxyServerCert.Name), zap.String("namespace", tiproxyServerCert.Namespace))

			// delete secret
			tiproxyCertSecret := &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      tiproxyServerCert.GetName(),
					Namespace: currSharedPool.GetDataPlaneNamespace(),
				},
			}
			if err := k8s.DeleteObject(ctx, remoteCli, tiproxyCertSecret); err != nil {
				tilog.Error(ctx, "failed to delete tiproxy cert secret", zap.String("certName", tiproxyCertSecret.Name), zap.Error(err))
				return fmt.Errorf("failed to delete tiproxy cert secret: %w", err)
			}
			tilog.Info(ctx, "successfully cleaned up tiproxy cert secret", zap.String("cert secret Name", tiproxyCertSecret.Name),
				zap.String("namespace", tiproxyCertSecret.Namespace))

		}
	}

	return nil
}

func tiProxyGroupExists(ctx context.Context, remoteCli client.Client, sharedPool *entity.SharedPool, comp *entity.Component) (bool, error) {
	tiproxyGroup := &tiproxyv1alpha1.TiProxyGroup{}
	err := remoteCli.Get(ctx, client.ObjectKey{
		Namespace: sharedPool.GetDataPlaneNamespace(),
		Name:      entity.GetComponentGroupName(comp),
	}, tiproxyGroup)
	if apierrors.IsNotFound(err) {
		return false, nil
	}
	if err != nil {
		return true, fmt.Errorf("failed to get tiproxy group: %w", err)
	}

	return true, nil
}
