package privatelink_svc

import (
	"context"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/domain/service"
	"github.com/tidbcloud/serverless-service/internal/domain/vo"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	"github.com/tidbcloud/serverless-service/internal/utils"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.uber.org/zap"
)

var privateLinkConnectionV1beta1Tracer = otel.Tracer("privateLinkConnection.v1beta1")

type privateLinkConnectionV1beta1Service struct {
	mgmtsvcFacade            facade.MgmtSvcFacade
	privateLinkConnectionSvc service.PrivateLinkConnectionService
	v1beta1.UnimplementedPrivateLinkConnectionServiceServer
}

func NewPrivateLinkConnectionV1beta1Service(
	mgmtsvcFacade facade.MgmtSvcFacade,
	privateLinkConnectionSvc service.PrivateLinkConnectionService,
) v1beta1.PrivateLinkConnectionServiceServer {
	return &privateLinkConnectionV1beta1Service{
		mgmtsvcFacade:            mgmtsvcFacade,
		privateLinkConnectionSvc: privateLinkConnectionSvc,
	}
}

func (s *privateLinkConnectionV1beta1Service) GetPrivateLinkConnection(
	ctx context.Context,
	req *v1beta1.GetPrivateLinkConnectionRequest,
) (_ *v1beta1.PrivateLinkConnection, err error) {
	ctx, span := privateLinkConnectionV1beta1Tracer.Start(
		ctx,
		"privateLinkConnectionV1beta1Service.GetPrivateLinkConnection",
	)
	logger := log.WithContextL(ctx).
		With(zap.String("cluster_id", req.ClusterId), zap.String("private_link_id", req.PrivateLinkConnectionId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", req.ClusterId),
		attribute.String("privateLinkId", req.PrivateLinkConnectionId),
	)

	logger.Info("start to get private link connection")
	if req.PrivateLinkConnectionId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty private link id")
	}

	pl, err := s.privateLinkConnectionSvc.GetPrivateLinkConnection(ctx, req.ClusterId, req.PrivateLinkConnectionId)
	if err != nil {
		return nil, err
	}

	attachDomainsByPLCID, err := s.privateLinkConnectionSvc.ListAttachDomains(ctx,
		entity.ListPLCAttachDomainsFilter{
			ClusterID:               req.ClusterId,
			PrivateLinkConnectionID: req.PrivateLinkConnectionId,
		},
	)
	attachDomains := attachDomainsByPLCID[req.PrivateLinkConnectionId]
	v1beta1AttachDomains := make([]*v1beta1.AttachDomain, 0, len(attachDomains))
	for _, ad := range attachDomains {
		v1beta1AttachDomains = append(v1beta1AttachDomains, vo.AttachDomainToV1beta1(ctx, ad))
	}

	result := vo.PrivateLinkConnectionToV1beta1(pl)
	result.AttachedDomains = v1beta1AttachDomains
	return result, nil
}

func (s *privateLinkConnectionV1beta1Service) ListPrivateLinkConnections(
	ctx context.Context,
	req *v1beta1.ListPrivateLinkConnectionsRequest,
) (_ *v1beta1.ListPrivateLinkConnectionsResponse, err error) {
	ctx, span := privateLinkConnectionV1beta1Tracer.Start(
		ctx,
		"privateLinkConnectionV1beta1Service.ListPrivateLinkConnections",
	)
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", req.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
	}()
	span.SetAttributes(attribute.String("clusterId", req.ClusterId))

	logger.Info("start to list private link connections")
	pager := utils.NewAipPager()
	if err := pager.Normalize(req); err != nil {
		return nil, err
	}

	items, total, err := s.privateLinkConnectionSvc.ListPrivateLinkConnections(
		ctx,
		&vo.ListPrivateLinkConnectionOptions{
			ClusterID: req.ClusterId,
			Limit:     int32(pager.PageSize()),
			Offset:    int64(pager.Offset()),
			State:     vo.PrivateLinkConnectionsStateFromV1beta1(req.State),
		},
	)
	if err != nil {
		return nil, err
	}

	ids := make([]string, 0, len(items))
	for _, it := range items {
		ids = append(ids, it.ID)
	}
	attachDomainsByPLCID, err := s.privateLinkConnectionSvc.ListAttachDomains(ctx,
		entity.ListPLCAttachDomainsFilter{
			ClusterID:                req.ClusterId,
			PrivateLinkConnectionIDs: ids,
		},
	)
	if err != nil {
		return nil, err
	}

	v1Items := make([]*v1beta1.PrivateLinkConnection, 0, len(items))
	for _, it := range items {
		attachDomains := attachDomainsByPLCID[it.ID]
		v1beta1AttachDomains := make([]*v1beta1.AttachDomain, 0, len(attachDomains))
		for _, ad := range attachDomains {
			v1beta1AttachDomains = append(v1beta1AttachDomains, vo.AttachDomainToV1beta1(ctx, ad))
		}
		cItem := vo.PrivateLinkConnectionToV1beta1(it)
		cItem.AttachedDomains = v1beta1AttachDomains
		v1Items = append(v1Items, cItem)
	}

	return &v1beta1.ListPrivateLinkConnectionsResponse{
		PrivateLinkConnections: v1Items,
		NextPageToken:          pager.NextPageToken(total),
		TotalSize:              total,
	}, nil
}

func (s *privateLinkConnectionV1beta1Service) CreatePrivateLinkConnection(
	ctx context.Context,
	req *v1beta1.CreatePrivateLinkConnectionRequest,
) (_ *v1beta1.PrivateLinkConnection, err error) {
	ctx, span := privateLinkConnectionV1beta1Tracer.Start(
		ctx,
		"privateLinkConnectionV1beta1Service.CreatePrivateLinkConnection",
	)
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", req.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
	}()
	span.SetAttributes(attribute.String("clusterId", req.ClusterId))

	logger.Info("start to create private link connection")

	domainPL, err := vo.V1beta1ToPrivateLinkConnection(ctx, req.PrivateLinkConnection)
	if err != nil {
		return nil, err
	}

	created, err := s.privateLinkConnectionSvc.CreatePrivateLinkConnection(ctx, req.ClusterId, domainPL)
	if err != nil {
		return nil, err
	}
	return vo.PrivateLinkConnectionToV1beta1(created), nil
}

func (s *privateLinkConnectionV1beta1Service) DeletePrivateLinkConnection(
	ctx context.Context,
	req *v1beta1.DeletePrivateLinkConnectionRequest,
) (_ *v1beta1.PrivateLinkConnection, err error) {
	ctx, span := privateLinkConnectionV1beta1Tracer.Start(
		ctx,
		"privateLinkConnectionV1beta1Service.DeletePrivateLinkConnection",
	)
	logger := log.WithContextL(ctx).
		With(zap.String("cluster_id", req.ClusterId), zap.String("private_link_id", req.PrivateLinkConnectionId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", req.ClusterId),
		attribute.String("privateLinkId", req.PrivateLinkConnectionId),
	)

	logger.Info("start to delete private link connection")
	if req.PrivateLinkConnectionId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty private link id")
	}

	deleted, err := s.privateLinkConnectionSvc.DeletePrivateLinkConnection(
		ctx,
		req.ClusterId,
		req.PrivateLinkConnectionId,
	)
	if err != nil {
		return nil, err
	}
	return vo.PrivateLinkConnectionToV1beta1(deleted), nil
}

func (s *privateLinkConnectionV1beta1Service) GetAvailabilityZones(
	ctx context.Context,
	req *v1beta1.GetAvailabilityZonesRequest,
) (_ *v1beta1.GetAvailabilityZonesResponse, err error) {
	ctx, span := privateLinkConnectionV1beta1Tracer.Start(
		ctx,
		"privateLinkConnectionV1beta1Service.GetAvailabilityZones",
	)
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", req.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
	}()
	span.SetAttributes(attribute.String("clusterId", req.ClusterId))

	logger.Info("start to get availability zones for private link connection")
	accountID, azs, err := s.privateLinkConnectionSvc.GetAvailabilityZones(ctx, req.ClusterId)
	if err != nil {
		return nil, err
	}

	return &v1beta1.GetAvailabilityZonesResponse{
		AccountId: accountID,
		AzIds:     azs,
	}, nil
}

func (s *privateLinkConnectionV1beta1Service) AttachDomains(
	ctx context.Context,
	req *v1beta1.AttachDomainsRequest,
) (_ *v1beta1.AttachDomain, err error) {
	ctx, span := privateLinkConnectionV1beta1Tracer.Start(ctx, "privateLinkV1beta1Service.AttachDomains")
	logger := log.WithContextL(ctx).
		With(zap.String("cluster_id", req.GetClusterId()), zap.String("private_link_connection_id", req.GetPrivateLinkConnectionId()))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", req.GetClusterId()),
		attribute.String("privateLinkId", req.GetPrivateLinkConnectionId()),
	)

	logger.Info("start to attach domains for private link connection")
	plcd, err := vo.V1beta1ToAttachDomain(ctx, req)
	if err != nil {
		return nil, err
	}

	plcd, err = s.privateLinkConnectionSvc.AttachDomains(ctx, plcd, req.ValidateOnly)
	if err != nil {
		return nil, err
	}
	return vo.AttachDomainToV1beta1(ctx, plcd), nil
}

func (s *privateLinkConnectionV1beta1Service) DetachDomains(
	ctx context.Context,
	req *v1beta1.DetachDomainsRequest,
) (_ *v1beta1.AttachDomain, err error) {
	ctx, span := privateLinkConnectionV1beta1Tracer.Start(ctx, "privateLinkV1beta1Service.DetachDomains")
	logger := log.WithContextL(ctx).
		With(zap.String("cluster_id", req.GetClusterId()), zap.String("private_link_connection_id", req.GetPrivateLinkConnectionId()))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", req.GetClusterId()),
		attribute.String("privateLinkId", req.GetPrivateLinkConnectionId()),
	)

	logger.Info("start to detach domains for private link connection")

	if req.GetPrivateLinkConnectionId() == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty private link connection id")
	}
	if req.GetAttachDomainId() == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty attach domain id")
	}

	plcd, err := s.privateLinkConnectionSvc.DetachDomains(
		ctx,
		req.ClusterId,
		req.PrivateLinkConnectionId,
		req.AttachDomainId,
	)
	if err != nil {
		return nil, err
	}
	return vo.AttachDomainToV1beta1(ctx, plcd), nil
}
