package action

import (
	"context"
	"fmt"

	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/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"
)

const CommitDeleteComponentActionName = "commit-delete-component"

type CommitDeleteComponentAction struct {
	sharedPoolRepo repo.SharedPoolRepo
}

func NewCommitDeleteComponentAction(sharedPoolRepo repo.SharedPoolRepo) actions.Action {
	return &CommitDeleteComponentAction{
		sharedPoolRepo: sharedPoolRepo,
	}
}

func (a *CommitDeleteComponentAction) Name() string {
	return CommitDeleteComponentActionName
}

func (a *CommitDeleteComponentAction) Invoke(args actions.ActionArgs) error {
	ctx := utils.GenerateContext(args)
	tilog.Info(ctx, "commit delete component action invoked")

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

	targetSharedPool, err := deleteComponentTargetSharedPool(args)
	if err != nil {
		return err
	}

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

	targets := deleteComponentTargetKeys(ctx, targetSharedPool, args)
	currSharedPool.ComponentGroups = filterDeletedComponents(currSharedPool.GetComponentGroups(), targets)
	currSharedPool.Phase = apiv1.SharedPool_AVAILABLE
	if err := a.sharedPoolRepo.Save(ctx, currSharedPool); err != nil {
		return fmt.Errorf("failed to save sharedPool meta: %w", err)
	}
	logDeletedComponentTargets(ctx, targets)
	return nil
}

type componentDeleteKey struct {
	id            string
	componentType apiv1.ComponentType
}

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

func deleteComponentTargetKeys(ctx context.Context, sharedPool *entity.SharedPool, args actions.ActionArgs) map[componentDeleteKey]struct{} {
	targets := make(map[componentDeleteKey]struct{})
	for _, comp := range sharedPool.GetComponentGroups() {
		if comp.GetPhase() != apiv1.Component_DELETING || !workflow.CheckIsTargetComponent(ctx, comp, args) {
			continue
		}
		targets[componentDeleteKey{
			id:            comp.GetId(),
			componentType: comp.GetComponentType(),
		}] = struct{}{}
	}
	return targets
}

func filterDeletedComponents(components []*entity.Component, targets map[componentDeleteKey]struct{}) []*entity.Component {
	filtered := components[:0]
	for _, comp := range components {
		if _, ok := targets[componentDeleteKey{id: comp.GetId(), componentType: comp.GetComponentType()}]; ok {
			continue
		}
		filtered = append(filtered, comp)
	}
	return filtered
}

func logDeletedComponentTargets(ctx context.Context, targets map[componentDeleteKey]struct{}) {
	for target := range targets {
		tilog.Info(ctx, "committed deleted component",
			zap.String("componentId", target.id),
			zap.String("componentType", target.componentType.String()))
	}
}
