package action

import (
	"fmt"

	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"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"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const PruneDataplaneActionName = "prune-dataplane"

type PruneDataplaneAction struct {
	sharedPoolRepo         repo.SharedPoolRepo
	nodeGroupStateRepo     repo.DataplaneNodeInstanceGroupStateRepo
	nodeGroupLockRepo      repo.DataplaneNodeInstanceGroupLockRepo
	dataplaneNodeGroupRepo repo.DataplaneNodeInstanceGroupRepo
	client                 dplanemgrv1.DataPlaneServiceClient
}

func NewPruneDataplaneAction(
	sharedPoolRepo repo.SharedPoolRepo,
	nodeGroupStateRepo repo.DataplaneNodeInstanceGroupStateRepo,
	nodeGroupLockRepo repo.DataplaneNodeInstanceGroupLockRepo,
	dataplaneNodeGroupRepo repo.DataplaneNodeInstanceGroupRepo,
	client dplanemgrv1.DataPlaneServiceClient,
) actions.Action {
	return &PruneDataplaneAction{
		sharedPoolRepo:         sharedPoolRepo,
		nodeGroupStateRepo:     nodeGroupStateRepo,
		nodeGroupLockRepo:      nodeGroupLockRepo,
		dataplaneNodeGroupRepo: dataplaneNodeGroupRepo,
		client:                 client,
	}
}

func (a *PruneDataplaneAction) Name() string {
	return PruneDataplaneActionName
}

func (a *PruneDataplaneAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "prune dataplane action start")
	pruneDataplane, _ := args.GetString(workflow.PruneDataplaneKey)
	if pruneDataplane != workflow.PruneDataplaneValue {
		tilog.Info(ctx, "skip prune dataplane because prune flag is not enabled", zap.String("pruneDataplane", pruneDataplane))
		return nil
	}
	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)
	}
	dataplaneId := sharedPool.GetDataplaneName()
	tilog.Info(ctx, "checking whether dataplane can be pruned",
		zap.String("sharedPoolId", sharedPoolId),
		zap.String("dataplaneId", dataplaneId))
	existedSharedPools, err := a.sharedPoolRepo.List(ctx, &repo.SharedPoolFilter{
		DataplaneNames: []string{dataplaneId},
		Deleted:        false,
	})
	if err != nil {
		tilog.Error(ctx, "failed to list sharedPools", zap.Error(err))
		return fmt.Errorf("failed to list sharedPools: %w", err)
	}
	if len(existedSharedPools) > 0 {
		tilog.Info(ctx, "skip prune dataplane because active sharedPools still exist",
			zap.String("dataplaneId", dataplaneId),
			zap.Int("activeSharedPoolCount", len(existedSharedPools)))
		return nil
	}

	states, err := a.nodeGroupStateRepo.ListByDataplane(ctx, dataplaneId)
	if err != nil {
		tilog.Error(ctx, "failed to list node instance group states", zap.String("dataplaneId", dataplaneId), zap.Error(err))
		return fmt.Errorf("failed to list node instance group states: %w", err)
	}
	for _, state := range states {
		if err := a.nodeGroupStateRepo.Delete(ctx, state.ID); err != nil {
			tilog.Error(ctx, "failed to delete node instance group state",
				zap.String("dataplaneId", dataplaneId),
				zap.String("stateId", state.ID),
				zap.Error(err))
			return fmt.Errorf("failed to delete node instance group state: %w", err)
		}
	}
	if err := a.nodeGroupLockRepo.ForceRelease(ctx, dataplaneId); err != nil {
		tilog.Error(ctx, "failed to force release node instance group lock", zap.String("dataplaneId", dataplaneId), zap.Error(err))
		return fmt.Errorf("failed to force release node instance group lock: %w", err)
	}

	if err := a.dataplaneNodeGroupRepo.DeleteByDataplane(ctx, dataplaneId); err != nil {
		tilog.Error(ctx, "failed to delete dataplane node instance group overrides", zap.String("dataplaneId", dataplaneId), zap.Error(err))
		return fmt.Errorf("failed to delete dataplane node instance group overrides: %w", err)
	}

	{
		in := new(dplanemgrv1.DeleteDataPlaneRequest)
		in.DataPlaneId = dataplaneId
		tilog.Info(ctx, "deleting dataplane", zap.String("dataplaneId", dataplaneId))
		_, err = a.client.DeleteDataPlane(ctx, in)
		if err != nil {
			if status.Code(err) == codes.NotFound {
				tilog.Info(ctx, "dataplane already deleted", zap.String("dataplaneId", dataplaneId))
				return nil
			}
			tilog.Error(ctx, "failed to delete dataplane", zap.String("in", in.String()), zap.Error(err))
			return err
		}
		tilog.Info(ctx, "dataplane delete requested", zap.String("dataplaneId", dataplaneId))
	}
	return nil
}
