package action

import (
	"context"
	"fmt"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	"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"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
)

const (
	DeleteCassandraProxyPLSActionName = "delete-cassandra-proxy-pls"
	DeleteCassandraProxyLBActionName  = "delete-cassandra-proxy-lb"
)

type DeleteCassandraProxyPLSAction struct {
	sharedPoolRepo repo.SharedPoolRepo
	plsClient      dplanemgrv1.PrivateLinkConnectionServiceClient
}

func NewDeleteCassandraProxyPLSAction(
	sharedPoolRepo repo.SharedPoolRepo,
	plsClient dplanemgrv1.PrivateLinkConnectionServiceClient,
) actions.Action {
	return &DeleteCassandraProxyPLSAction{
		sharedPoolRepo: sharedPoolRepo,
		plsClient:      plsClient,
	}
}

func (a *DeleteCassandraProxyPLSAction) Name() string {
	return DeleteCassandraProxyPLSActionName
}

func (a *DeleteCassandraProxyPLSAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "delete cassandra-proxy pls action")

	sharedPool, components, err := loadTargetCassandraProxyComponents(ctx, a.sharedPoolRepo, args)
	if err != nil {
		return err
	}

	for _, comp := range components {
		plsID := entity.GetCassandraProxyLbName(comp.GetId())
		if _, err := a.plsClient.DeletePrivateLinkService(ctx, &dplanemgrv1.DeletePrivateLinkServiceRequest{
			DataPlaneId: sharedPool.GetDataplaneName(),
			Id:          plsID,
		}); err != nil {
			if status.Code(err) == codes.NotFound {
				continue
			}
			return fmt.Errorf("failed to delete cassandra-proxy PLS %s: %w", plsID, err)
		}
	}

	return nil
}

type DeleteCassandraProxyLBAction struct {
	sharedPoolRepo repo.SharedPoolRepo
	lbClient       dplanemgrv1.LoadBalancerServiceClient
}

func NewDeleteCassandraProxyLBAction(
	sharedPoolRepo repo.SharedPoolRepo,
	lbClient dplanemgrv1.LoadBalancerServiceClient,
) actions.Action {
	return &DeleteCassandraProxyLBAction{
		sharedPoolRepo: sharedPoolRepo,
		lbClient:       lbClient,
	}
}

func (a *DeleteCassandraProxyLBAction) Name() string {
	return DeleteCassandraProxyLBActionName
}

func (a *DeleteCassandraProxyLBAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "delete cassandra-proxy lb action")

	sharedPool, components, err := loadTargetCassandraProxyComponents(ctx, a.sharedPoolRepo, args)
	if err != nil {
		return err
	}

	for _, comp := range components {
		lbID := entity.GetCassandraProxyLbName(comp.GetId())
		if _, err := a.lbClient.DeleteLoadBalancer(ctx, &dplanemgrv1.DeleteLoadBalancerRequest{
			DataPlaneId: sharedPool.GetDataplaneName(),
			Id:          lbID,
		}); err != nil {
			if status.Code(err) == codes.NotFound {
				continue
			}
			return fmt.Errorf("failed to delete cassandra-proxy LB %s: %w", lbID, err)
		}
	}

	return nil
}

func loadTargetCassandraProxyComponents(
	ctx context.Context,
	sharedPoolRepo repo.SharedPoolRepo,
	args actions.ActionArgs,
) (*entity.SharedPool, []*entity.Component, error) {
	if _, err := args.GetString(workflow.ComponentIdsKey); err != nil {
		return nil, nil, fmt.Errorf("target component id is required, but not found")
	}

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

	sharedPool, err := sharedPoolRepo.Load(ctx, sharedPoolID, false)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to load sharedPool: %w", err)
	}

	sharedPoolStr, err := args.GetString(workflow.SharedPoolKey)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get sharedPool: %w", err)
	}
	var tempSharedPool entity.SharedPool
	if err := protojson.Unmarshal([]byte(sharedPoolStr), &tempSharedPool); err != nil {
		return nil, nil, fmt.Errorf("failed to unmarshal sharedPool: %w", err)
	}

	components := make([]*entity.Component, 0)
	for _, comp := range tempSharedPool.GetComponentGroups() {
		if comp.GetComponentType() != apiv1.ComponentType_CASSANDRA_PROXY {
			continue
		}
		if !workflow.CheckIsTargetComponent(ctx, comp, args) {
			continue
		}
		components = append(components, comp)
	}

	return sharedPool, components, nil
}
