package privatelink

import (
	"context"
	"strings"

	"github.com/avast/retry-go"
	"google.golang.org/protobuf/types/known/emptypb"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/service/privatelink"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const (
	infraConflictErrMsg = "the object has been modified; please apply your changes to the latest version and try again"
)

func NewServer(
	privateLinkSvc privatelink.Service,
) globaldedicatedv1.PrivateLinkConnectionServiceServer {
	return &server{
		privateLinkSvc: privateLinkSvc,
	}
}

type server struct {
	privateLinkSvc privatelink.Service
}

func (s *server) CreatePrivateLinkService(
	ctx context.Context,
	request *globaldedicatedv1.CreatePrivateLinkServiceRequest,
) (*globaldedicatedv1.CreatePrivateLinkServiceResponse, error) {
	resp := &globaldedicatedv1.CreatePrivateLinkServiceResponse{}

	var privateLinkService *globaldedicatedv1.PrivateLinkService
	err := retry.Do(
		func() error {
			var be bizerr.BizError
			privateLinkService, be = s.privateLinkSvc.CreateService(
				ctx,
				&globaldedicatedv1.CreatePrivateLinkServiceRequest{
					OrgId:                   request.OrgId,
					ProjectId:               request.ProjectId,
					ClusterId:               request.ClusterId,
					PrivateLinkService:      request.PrivateLinkService,
					GenerateUniqueServiceId: request.GenerateUniqueServiceId,
				},
			)
			return be
		},
		retry.Context(ctx),
		retry.RetryIf(func(err error) bool { return strings.Contains(err.Error(), infraConflictErrMsg) }),
	)
	if err != nil {
		return resp, bizerr.WrapError(err, "failed to create private link")
	}

	resp.PrivateLinkService = privateLinkService

	return resp, nil
}

func (s *server) DeletePrivateLinkService(
	ctx context.Context,
	request *globaldedicatedv1.DeletePrivateLinkServiceRequest,
) (*emptypb.Empty, error) {
	if err := s.privateLinkSvc.DeleteService(ctx, request); err != nil {
		return nil, bizerr.WrapError(err, "transaction")
	}

	return &emptypb.Empty{}, nil
}

func (s *server) GetPrivateLinkService(
	ctx context.Context,
	request *globaldedicatedv1.GetPrivateLinkServiceRequest,
) (*globaldedicatedv1.GetPrivateLinkServiceResponse, error) {
	resp := &globaldedicatedv1.GetPrivateLinkServiceResponse{}

	privateLinkService, err := s.privateLinkSvc.GetPrivateLinkService(ctx, request)
	if err != nil {
		return resp, bizerr.WrapError(err, "failed to get private link service")
	}

	resp.PrivateLinkService = privateLinkService

	return resp, nil
}

func (s *server) CreatePrivateLinkEndpoint(
	ctx context.Context,
	request *globaldedicatedv1.CreatePrivateLinkEndpointRequest,
) (*globaldedicatedv1.CreatePrivateLinkEndpointResponse, error) {
	resp := &globaldedicatedv1.CreatePrivateLinkEndpointResponse{}

	endpoint, err := s.privateLinkSvc.CreatePrivateLinkEndpoint(ctx, request)
	if err != nil {
		return resp, bizerr.WrapError(err, "transaction")
	}

	resp.PrivateLinkEndpoint = endpoint

	return resp, nil
}

func (s *server) DeletePrivateLinkEndpoint(
	ctx context.Context,
	request *globaldedicatedv1.DeletePrivateLinkEndpointRequest,
) (*emptypb.Empty, error) {
	if err := s.privateLinkSvc.DeleteEndpoint(ctx, request); err != nil {
		return nil, bizerr.WrapError(err, "transaction")
	}

	return &emptypb.Empty{}, nil
}

func (s *server) ListPrivateLinkEndpoints(
	ctx context.Context,
	request *globaldedicatedv1.ListPrivateLinkEndpointsRequest,
) (*globaldedicatedv1.ListPrivateLinkEndpointsResponse, error) {
	resp := &globaldedicatedv1.ListPrivateLinkEndpointsResponse{}

	out, err := s.privateLinkSvc.ListPrivateLinkEndpoints(ctx, request)
	if err != nil {
		return resp, bizerr.WrapError(err, "failed to list private link endpoints")
	}

	resp.Endpoints = out

	return resp, nil
}

func (s *server) ListTidbNodeGroupPrivateLinkEndpoints(
	ctx context.Context,
	request *globaldedicatedv1.ListTidbNodeGroupPrivateLinkEndpointsRequest,
) (*globaldedicatedv1.ListTidbNodeGroupPrivateLinkEndpointsResponse, error) {
	resp := &globaldedicatedv1.ListTidbNodeGroupPrivateLinkEndpointsResponse{
		Endpoints: nil,
	}

	privateLinkService, endpoints, err := s.privateLinkSvc.ListTidbNodeGroupPrivateLinkEndpoints(ctx, request)
	if err != nil {
		return resp, bizerr.WrapError(err, "failed to list tidb node group private link endpoints")
	}

	resp.Endpoints = endpoints
	resp.PrivateLinkService = privateLinkService

	return resp, nil
}

func (s *server) RejectVpcEndpointConnections(
	ctx context.Context,
	request *globaldedicatedv1.RejectVpcEndpointConnectionsRequest,
) (*emptypb.Empty, error) {
	if err := s.privateLinkSvc.RejectVpcEndpointConnections(ctx, request); err != nil {
		return nil, bizerr.WrapError(err, "failed to reject vpc endpoint connections")
	}

	return &emptypb.Empty{}, nil
}

func (s *server) PausePrivateLinkService(
	ctx context.Context,
	request *globaldedicatedv1.PausePrivateLinkServiceRequest,
) (*emptypb.Empty, error) {
	if err := s.privateLinkSvc.PausePrivateLinkService(ctx, request); err != nil {
		return nil, bizerr.WrapError(err, "failed to pause private link service")
	}

	return &emptypb.Empty{}, nil
}

func (s *server) PurgePrivateLinkServiceMetadata(
	ctx context.Context,
	request *globaldedicatedv1.PurgePrivateLinkServiceMetadataRequest,
) (*emptypb.Empty, error) {
	if err := s.privateLinkSvc.PurgePrivateLinkServiceMetadata(ctx, request); err != nil {
		return nil, bizerr.WrapError(err, "failed to purge private link service metadata")
	}

	return &emptypb.Empty{}, nil
}
