package server

import (
	"context"
	"fmt"
	"strings"

	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/domain/workflow/dag"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"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"
	"google.golang.org/protobuf/encoding/protojson"
)

func (s *serverImpl) DeleteTiKVGroup(ctx context.Context, sharedPoolId, tikvGroupId string) error {
	if sharedPoolId == "" {
		return status.Errorf(codes.InvalidArgument, "shared_pool_id is required")
	}
	if tikvGroupId == "" {
		return status.Errorf(codes.InvalidArgument, "tikv_group_id is required")
	}
	if err := validateTikvGroupId(tikvGroupId); err != nil {
		return err
	}

	var dagExecID string
	var dagArgs actions.ActionArgs
	var shouldSubmit bool

	unlockFunc, err := s.lock(ctx, sharedPoolId)
	if err != nil {
		tilog.Error(ctx, "failed to lock sharedPool", zap.String("sharedPoolId", sharedPoolId), zap.Error(err))
		return err
	}
	defer func() {
		if err := unlockFunc(); err != nil {
			tilog.Error(ctx, "failed to unlock sharedPool", zap.String("sharedPoolId", sharedPoolId), zap.Error(err))
		}
	}()

	if err := s.transactioner.Transaction(ctx, func(ctx context.Context) error {
		sharedPool, err := s.sharedPoolRepo.Load(ctx, sharedPoolId, false)
		if err != nil {
			if status.Code(err) != codes.NotFound {
				return status.Errorf(codes.Internal, "failed to load sharedPool: %s", err)
			}
			return status.Errorf(codes.NotFound, "sharedPool %s not found", sharedPoolId)
		}
		if err := guardSharedPoolMutation(sharedPool, sharedPoolMutationBlockedWhilePaused); err != nil {
			return err
		}

		if err := s.mergeSharedPoolStatusFromDagTask(ctx, sharedPool); err != nil {
			return status.Errorf(codes.Internal, "failed to merge sharedPool status from dag task: %s", err)
		}

		if isDefaultTiKVGroup(sharedPool, tikvGroupId) {
			return status.Errorf(codes.FailedPrecondition, "cannot delete default tikv group %s", tikvGroupId)
		}

		tikvComp := findComponentByTypeAndId(sharedPool, apiv1.ComponentType_TIKV, tikvGroupId)
		if tikvComp == nil {
			return status.Errorf(codes.NotFound, "tikv group %s not found in shared pool %s", tikvGroupId, sharedPoolId)
		}

		deleteIns, err := s.getRunningDagIns(ctx,
			[]string{dag.PlanDeleteTiKVGroup},
			[]string{sharedPool.GetId()},
			nil,
			nil,
			[]string{tikvGroupId},
		)
		if err != nil {
			return status.Errorf(codes.Internal, "failed to get running delete component dag instance: %s", err)
		}
		if len(deleteIns) > 0 {
			tilog.Info(ctx, "delete tikv group dag is already running",
				zap.String("sharedPoolId", sharedPoolId),
				zap.String("tikvGroupId", tikvGroupId))
			return nil
		}

		if tikvComp.GetPhase() != apiv1.Component_DELETING {
			if sharedPool.GetPhase() != apiv1.SharedPool_AVAILABLE {
				return status.Errorf(codes.FailedPrecondition, "shared pool is not available, current phase: %s", sharedPool.GetPhase().String())
			}
			if tikvComp.GetPhase() != apiv1.Component_AVAILABLE {
				return status.Errorf(codes.FailedPrecondition, "tikv group %s is not available, current phase: %s", tikvGroupId, tikvComp.GetPhase().String())
			}

			runningComponentIns, err := s.getRunningDagIns(ctx,
				[]string{dag.PlanCreateComponent, dag.PlanUpdateComponent, dag.PlanApplyComponent},
				[]string{sharedPool.GetId()},
				nil,
				nil,
				[]string{tikvGroupId},
			)
			if err != nil {
				return status.Errorf(codes.Internal, "failed to get running component dag instances: %s", err)
			}
			if len(runningComponentIns) > 0 {
				return status.Errorf(codes.FailedPrecondition, "tikv group %s has running component dag, please try again later", tikvGroupId)
			}

			remoteCli, err := s.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
			if err != nil {
				return status.Errorf(codes.Internal, "failed to get remote client for shared pool %s: %s", sharedPoolId, err)
			}

			placementPolicies, err := tioperator.ListPlacementPolicies(ctx, remoteCli, sharedPoolId, tikvGroupId)
			if err != nil {
				return err
			}
			placementPolicyIDs := make([]string, 0, len(placementPolicies))
			for _, policy := range placementPolicies {
				placementPolicyIDs = append(placementPolicyIDs, policy.GetPlacementPolicyId())
			}
			if len(placementPolicyIDs) > 0 {
				return status.Errorf(
					codes.FailedPrecondition,
					"cannot delete tikv group %s because placement policies reference it: %s",
					tikvGroupId,
					strings.Join(placementPolicyIDs, ","),
				)
			}
		}

		tikvComp.Phase = apiv1.Component_DELETING
		if tikvComp.NodeQuota != nil {
			tikvComp.GetNodeQuota().NodeCount = 0
		}
		sharedPool.Phase = apiv1.SharedPool_MODIFYING

		if err := s.sharedPoolRepo.Save(ctx, sharedPool); err != nil {
			return status.Errorf(codes.Internal, "failed to save sharedPool: %s", err)
		}

		sharedPoolStr, err := protojson.Marshal(sharedPool)
		if err != nil {
			return status.Errorf(codes.Internal, "failed to marshal sharedPool: %s", err)
		}

		dagArgs = actions.ActionArgs{
			workflow.SharedPoolIdKey:   actions.ActionArgString(sharedPool.GetId()),
			workflow.SharedPoolKey:     actions.ActionArgString(string(sharedPoolStr)),
			workflow.ComponentIdsKey:   actions.ActionArgString(tikvComp.GetId()),
			workflow.ComponentTypesKey: actions.ActionArgString(entity.GetComponentTypeName(tikvComp)),
			workflow.MeteringTypeKey:   actions.ActionArgString(workflow.MeteringTypeValueLogicMetaBySharedPool),
			utils.TraceIdKey:           utils.GetTraceId(ctx),
		}

		dagExecID = utils.GeneratedDagExecId(ctx, fmt.Sprintf("delete-tikv-group-%s-%s-%s", sharedPool.GetId(), tikvGroupId, utils.FnvHash(string(sharedPoolStr))))
		shouldSubmit = true
		return nil
	}); err != nil {
		return err
	}

	if !shouldSubmit {
		return nil
	}

	if err := s.dagExecutor.Submit(ctx, dag.PlanDeleteTiKVGroup, dagArgs, dagExecID); err != nil {
		return status.Errorf(codes.Internal, "failed to submit delete tikv group dag: %s", err)
	}

	tilog.Info(ctx, "submitted delete tikv group dag",
		zap.String("sharedPoolId", sharedPoolId),
		zap.String("tikvGroupId", tikvGroupId),
		zap.String("dagExecID", dagExecID))
	return nil
}

func isDefaultTiKVGroup(sharedPool *entity.SharedPool, tikvGroupId string) bool {
	return tikvGroupId == defaultSharedPoolComponentID(sharedPool)
}
