package server

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/domain/workflow/dag"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"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) DeleteTiDBGroup(ctx context.Context, tidbClusterId, tidbGroupId string) (*entity.TiDBGroup, error) {
	if tidbGroupId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbGroup id is required")
	}

	if tidbClusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbCluster id is required")
	}

	var tidbGroup *entity.TiDBGroup
	var dagExecId string
	var dagSubmitted bool

	if err := s.transactioner.Transaction(ctx, func(ctx context.Context) error {
		var err error
		// get tidb group from repo
		tidbGroup, err = s.tidbGroupRepo.Load(ctx, tidbGroupId, false)
		if err != nil {
			if status.Code(err) != codes.NotFound {
				return status.Errorf(codes.Internal, "failed to load tidbGroup: %s", err)
			}

			// not found, check if it's in creating
			return status.Errorf(codes.NotFound, "tidbGroup %s not found", tidbGroupId)
		}

		if tidbGroup.GetClusterId() != tidbClusterId {
			return status.Errorf(codes.FailedPrecondition, "tidbGroup %s is not belong to tidbCluster %s", tidbGroupId, tidbClusterId)
		}

		sharedPool, err := s.sharedPoolRepo.Load(ctx, tidbGroup.GetSharedPoolId(), false)
		if err != nil {
			if status.Code(err) != codes.NotFound {
				return status.Errorf(codes.Internal, "failed to load sharedPool: %s", err)
			}
			// not found, check if it's in creating
			return status.Errorf(codes.NotFound, "sharedPool %s not found", tidbGroup.GetSharedPoolId())
		}
		if err := guardSharedPoolMutation(sharedPool, sharedPoolMutationDeleteInstance); err != nil {
			return err
		}

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

		// delete api not need to check sharedPool phase
		if sharedPool.Phase != apiv1.SharedPool_AVAILABLE {
			tilog.Warn(ctx, "sharedpool is not ready", zap.String("sharedPoolId", tidbGroup.GetSharedPoolId()), zap.String("status", sharedPool.GetPhase().String()))
		}

		// first cancel running dag
		needCancelPlans := []string{
			dag.PlanCreateTiDBGroup,
			dag.PlanUpdateTiDBGroup,
		}

		dagIns, err := s.getRunningDagIns(ctx, needCancelPlans, nil, nil, nil, []string{tidbGroupId})
		if err != nil {
			return status.Errorf(codes.Internal, "failed to get running dag instances: %s", err)
		}

		for id, task := range dagIns {
			// abort create cluster dag
			tilog.Info(ctx, "aborting tidbgroup dag", zap.String("exec id", id), zap.String("root name", task.ExecTask.RootName))
			err = s.dagExecutor.Abort(ctx, task.ExecTask.ExecId)
			if err != nil {
				tilog.Error(ctx, "failed to abort tidbgroup dag", zap.String("task id", task.ExecTask.ExecId), zap.String("root name", task.ExecTask.RootName), zap.Error(err))
				return status.Errorf(codes.Internal, "failed to abort tidbgroup dag %s: %s", task.ExecTask.ExecId, err)
			}
		}

		var tidbComp *entity.Component
		for _, comp := range sharedPool.GetComponentGroups() {
			if comp.GetComponentType() == apiv1.ComponentType_TIDB && comp.GetId() == tidbGroup.GetId() {
				tidbComp = comp
			}
		}

		if tidbComp == nil {
			tilog.Warn(ctx, "tidb group comp not found in cluster, maybe it not created yet", zap.String("tidbGroupId", tidbGroupId))
			tidbComp = &entity.Component{
				Id:            tidbGroupId,
				Phase:         apiv1.Component_DELETING,
				ComponentType: apiv1.ComponentType_TIDB,
				NodeQuota:     &entity.NodeQuota{NodeCount: 0},
			}
			sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, tidbComp)
		}

		// set phase to deleting
		tidbComp.Phase = apiv1.Component_DELETING
		if tidbComp.NodeQuota != nil {
			tidbComp.GetNodeQuota().NodeCount = 0
		}

		// set tidb group phase to unavailable
		tidbGroup.Phase = apiv1.TiDBGroup_UNAVAILABLE

		deleteIns, err := s.getRunningDagIns(ctx,
			[]string{dag.PlanDeleteTiDBGroup},
			[]string{sharedPool.GetId()},
			nil, nil,
			[]string{tidbGroupId})
		if err != nil {
			return status.Errorf(codes.Internal, "failed to get running delete dag instance: %s", err)
		}

		if len(deleteIns) > 0 {
			tilog.Info(ctx, "delete tidb group dag is already running", zap.String("sharedPoolId", tidbGroup.GetSharedPoolId()), zap.String("tidbGroupId", tidbGroupId))
			return nil
		}

		// delete from repo
		if err := s.tidbGroupRepo.Delete(ctx, tidbGroupId); err != nil {
			tilog.Error(ctx, "failed to delete tidb group", zap.Error(err))
			return fmt.Errorf("failed to delete tidb group: %w", err)
		}

		// if shared pool is creating, just return, not need submit delete dag
		if sharedPool.Phase == apiv1.SharedPool_CREATING {
			return nil
		}

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

		args := actions.ActionArgs{
			workflow.SharedPoolIdKey:   actions.ActionArgString(sharedPool.GetId()),
			workflow.SharedPoolKey:     actions.ActionArgString(string(sharedPoolStr)),
			workflow.ComponentIdsKey:   actions.ActionArgString(tidbComp.GetId()),
			workflow.ComponentTypesKey: actions.ActionArgString(entity.GetComponentTypeName(tidbComp)),
			workflow.TiDBGroupIdKey:    actions.ActionArgString(tidbComp.GetId()),
			workflow.TiDBClusterIdKey:  actions.ActionArgString(tidbGroup.GetClusterId()),
			utils.TraceIdKey:           utils.GetTraceId(ctx),
		}

		dagExecId = utils.GeneratedDagExecId(ctx, fmt.Sprintf("delete-tidbgroup-%s-%s", sharedPool.GetId(), tidbComp.GetId()))
		err = s.dagExecutor.Submit(ctx, dag.PlanDeleteTiDBGroup, args, dagExecId)
		if err != nil {
			return status.Errorf(codes.Internal, "failed to submit delete tidb group dag: %s", err)
		}
		dagSubmitted = true
		return nil
	}); err != nil {
		if dagSubmitted {
			if abortErr := s.dagExecutor.Abort(ctx, dagExecId); abortErr != nil {
				tilog.Error(ctx, "failed to abort dag after transaction failure",
					zap.String("dagExecId", dagExecId),
					zap.Error(abortErr))
			}
		}
		return nil, err
	}

	return tidbGroup, nil
}
