package server

import (
	"context"
	"fmt"
	"strconv"

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

const defaultTiProxyExternalDNSTTL = 30

func (s *serverImpl) SetTiProxyExternalDNS(ctx context.Context, req *entity.SetTiProxyExternalDNSRequest) error {
	if req == nil {
		return status.Errorf(codes.InvalidArgument, "request is nil")
	}
	if req.GetClusterId() == "" {
		return status.Errorf(codes.InvalidArgument, "cluster id is empty")
	}
	if req.GetTidbGroupId() == "" {
		return status.Errorf(codes.InvalidArgument, "tidb group id is empty")
	}

	ttl := defaultTiProxyExternalDNSTTL
	if req.Ttl != nil {
		if req.GetTtl() <= 0 {
			return status.Errorf(codes.InvalidArgument, "ttl must be greater than 0")
		}
		ttl = int(req.GetTtl())
	}

	var dagExecId string
	var dagSubmitted bool
	if err := s.transactioner.Transaction(ctx, func(ctx context.Context) error {
		tidbGroup, err := s.tidbGroupRepo.Load(ctx, req.GetTidbGroupId(), false)
		if err != nil {
			if status.Code(err) != codes.NotFound {
				return status.Errorf(codes.Internal, "failed to load tidbGroup: %s", err)
			}
			return status.Errorf(codes.NotFound, "tidbGroup %s not found", req.GetTidbGroupId())
		}

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

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

		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)
			}
			return status.Errorf(codes.NotFound, "sharedPool %s not found", tidbGroup.GetSharedPoolId())
		}
		if err := guardSharedPoolMutation(sharedPool, sharedPoolMutationBlockedWhilePaused); err != nil {
			return err
		}

		if _, err := s.tidbClusterRepo.Load(ctx, req.GetClusterId(), false); err != nil {
			if status.Code(err) != codes.NotFound {
				return status.Errorf(codes.Internal, "failed to load tidbCluster: %s", err)
			}
			return status.Errorf(codes.NotFound, "tidbCluster %s not found", req.GetClusterId())
		}

		dagIns, err := s.getRunningDagIns(ctx, []string{dag.PlanSetTiProxyExternalDNS}, []string{sharedPool.GetId()}, nil, []string{req.GetClusterId()}, []string{req.GetTidbGroupId()})
		if err != nil {
			return fmt.Errorf("failed to get running set tiproxy external dns dag instance: %w", err)
		}
		if len(dagIns) > 0 {
			return status.Errorf(codes.AlreadyExists, "existing set tiproxy external dns workflow not finished, please try again later")
		}

		args := actions.ActionArgs{
			workflow.SharedPoolIdKey:      actions.ActionArgString(sharedPool.GetId()),
			workflow.TiDBClusterIdKey:     actions.ActionArgString(req.GetClusterId()),
			workflow.TiDBGroupIdKey:       actions.ActionArgString(req.GetTidbGroupId()),
			workflow.EnableExternalDNSKey: actions.ActionArgString(strconv.FormatBool(req.GetEnable())),
			workflow.ExternalDNSTTLKey:    actions.ActionArgString(strconv.Itoa(ttl)),
			utils.TraceIdKey:              utils.GetTraceId(ctx),
		}

		dagExecId = utils.GeneratedDagExecId(ctx, fmt.Sprintf("set-tiproxy-external-dns-%s-%s-%t-%d", sharedPool.GetId(), req.GetTidbGroupId(), req.GetEnable(), ttl))
		if err := s.dagExecutor.Submit(ctx, dag.PlanSetTiProxyExternalDNS, args, dagExecId); err != nil {
			return status.Errorf(codes.Internal, "failed to submit set tiproxy external dns 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 err
	}

	return nil
}
