package privatelink

import (
	"context"
	"errors"
	"strconv"

	clusterngv1beta "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"gorm.io/gorm"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/resourcepoolpolicy"
	servicecommon "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/service/workflow/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow"
)

func (s *service) DeletePrivateLinkConnection(
	ctx context.Context,
	req *globalngv1.DeletePrivateLinkConnectionRequest,
) (*entity.PrivateLinkConnection, bizerr.BizError) {
	ctx, span := tracer.Start(ctx, "Domain.DeletePrivateLinkConnectionSvc")
	defer span.End()

	privateLinkConnectionID := req.GetPrivateLinkConnectionId()
	if privateLinkConnectionID == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "private link connection id is required")
	}
	privateLinkConnectionIDUint64, err := strconv.ParseUint(privateLinkConnectionID, 10, 64)
	if err != nil {
		return nil, bizerr.WrapError(err, "parse private link connection id failed")
	}

	privateLinkConnection, err := s.privatelinkRepo.GetPrivateLinkConnection(ctx, privateLinkConnectionIDUint64)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "private link connection not found")
		}
		return nil, bizerr.WrapError(err, "get private link connection failed")
	}
	privateLinkConnection.State = globalngv1.PrivateLinkConnection_DELETING

	cluster, err := s.clusterRepo.Get(ctx, privateLinkConnection.ClusterId, false)
	if err != nil {
		return nil, bizerr.WrapError(err, "get cluster failed")
	}
	if berr := servicecommon.CheckClusterResourcePoolLifecycle(
		ctx, s.resourcePoolRepo, cluster, resourcepoolpolicy.OperationUpdateNetwork,
	); berr != nil {
		return nil, berr
	}

	if cluster.State != globalngv1.Cluster_AVAILABLE {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster is not available")
	}

	infraEndpointID := privateLinkConnection.EndpointId
	region, err := s.regionRepo.Get(ctx, cluster.RegionId)
	if err != nil {
		return nil, bizerr.WrapError(err, "get region failed")
	}

	privateLinkService, err := s.regionalCli.GetPrivateLinkService(
		ctx,
		region,
		&clusterngv1beta.GetPrivateLinkServiceRequest{
			ClusterId:            strconv.FormatUint(cluster.ClusterId, 10),
			TidbGroupId:          strconv.FormatUint(privateLinkConnection.TidbGroupId, 10),
			PrivateLinkServiceId: strconv.FormatUint(privateLinkConnection.PrivateLinkServiceID, 10),
		},
	)
	if err != nil {
		return nil, bizerr.WrapError(err, "get private link service failed")
	}

	if err := s.checkDeleteEndpoint(privateLinkService); err != nil {
		return nil, bizerr.WrapError(err, "check delete endpoint failed")
	}

	terr := s.transaction.Transaction(ctx, func(ctx context.Context) error {
		if berr := servicecommon.LockClusterResourcePoolLifecycle(
			ctx, s.resourcePoolRepo, cluster, resourcepoolpolicy.OperationUpdateNetwork,
		); berr != nil {
			return berr
		}
		if err := s.privatelinkRepo.DeletePrivateLinkConnection(ctx, privateLinkConnectionIDUint64); err != nil {
			return err
		}
		// Start async reconciliation workflow.
		workFlowErr := s.flow.RunDagWithBizId(
			ctx,
			common.DeletePrivateLinkConnection,
			strconv.FormatUint(cluster.OrganizationId, 10),
			"0",
			strconv.FormatUint(cluster.ClusterId, 10),
			map[string]string{
				workflow.KeyTraceId:            workflow.GetTraceId(ctx),
				common.KeyClusterId:            strconv.FormatUint(cluster.ClusterId, 10),
				common.KeyTidbGroupId:          strconv.FormatUint(privateLinkConnection.TidbGroupId, 10),
				common.KeyPrivateEndpointId:    infraEndpointID,
				common.KeyPrivateLinkServiceId: strconv.FormatUint(privateLinkConnection.PrivateLinkServiceID, 10),
			},
		)
		if workFlowErr != nil {
			return bizerr.WrapErrorWithCode(workFlowErr, bizerr.ErrCodeUnknown, "create async workflow failed")
		}

		return nil
	})
	if terr != nil {
		return nil, bizerr.WrapError(terr, "delete private link connection failed")
	}

	return privateLinkConnection, nil
}

func (s *service) checkDeleteEndpoint(privateLinkService *clusterngv1beta.PrivateLinkService) bizerr.BizError {
	if privateLinkService.Status != clusterngv1beta.PrivateLinkService_ACTIVE {
		return bizerr.New(bizerr.ErrCodeParams,
			"unable to delete endpoint when PrivateLink service status is: %s", privateLinkService.Status.String())
	}

	return nil
}
