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/console"
	"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 privateLinkConnectionConsoleTracer = otel.Tracer("privateLinkConnection.console")

type privateLinkConnectionConsoleService struct {
	mgmtsvcFacade            facade.MgmtSvcFacade
	privateLinkConnectionSvc service.PrivateLinkConnectionService
	console.UnimplementedPrivateLinkConnectionServiceServer
}

func NewPrivateLinkConnectionConsoleService(
	mgmtsvcFacade facade.MgmtSvcFacade,
	privateLinkConnectionSvc service.PrivateLinkConnectionService,
) console.PrivateLinkConnectionServiceServer {
	return &privateLinkConnectionConsoleService{
		mgmtsvcFacade:            mgmtsvcFacade,
		privateLinkConnectionSvc: privateLinkConnectionSvc,
	}
}

func (s *privateLinkConnectionConsoleService) GetPrivateLinkConnection(
	ctx context.Context,
	req *console.GetPrivateLinkConnectionRequest,
) (_ *console.PrivateLinkConnection, err error) {
	ctx, span := privateLinkConnectionConsoleTracer.Start(
		ctx,
		"privateLinkConnectionConsoleService.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,
		})
	if err != nil {
		return nil, err
	}
	attachDomains := attachDomainsByPLCID[req.PrivateLinkConnectionId]
	consoleAttachDomains := make([]*console.AttachDomain, 0, len(attachDomains))
	for _, ad := range attachDomains {
		consoleAttachDomains = append(consoleAttachDomains, vo.AttachDomainToConsole(ctx, ad))
	}

	result := vo.PrivateLinkConnectionToConsole(pl)
	result.AttachedDomains = consoleAttachDomains
	return result, nil
}

func (s *privateLinkConnectionConsoleService) ListPrivateLinkConnections(
	ctx context.Context,
	req *console.ListPrivateLinkConnectionsRequest,
) (_ *console.ListPrivateLinkConnectionsResponse, err error) {
	ctx, span := privateLinkConnectionConsoleTracer.Start(
		ctx,
		"privateLinkConnectionConsoleService.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.NewPagerNumberPager()
	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.PrivateLinkConnectionsStateFromConsole(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
	}

	cItems := make([]*console.PrivateLinkConnection, 0, len(items))
	for _, it := range items {
		attachDomains := attachDomainsByPLCID[it.ID]
		consoleAttachDomains := make([]*console.AttachDomain, 0, len(attachDomains))
		for _, ad := range attachDomains {
			consoleAttachDomains = append(consoleAttachDomains, vo.AttachDomainToConsole(ctx, ad))
		}
		cItem := vo.PrivateLinkConnectionToConsole(it)
		cItem.AttachedDomains = consoleAttachDomains
		cItems = append(cItems, cItem)
	}

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

func (s *privateLinkConnectionConsoleService) CreatePrivateLinkConnection(
	ctx context.Context,
	req *console.CreatePrivateLinkConnectionRequest,
) (resp *console.PrivateLinkConnection, err error) {
	ctx, span := privateLinkConnectionConsoleTracer.Start(
		ctx,
		"privateLinkConnectionConsoleService.CreatePrivateLinkConnection",
	)
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", req.ClusterId))
	var created *entity.PrivateLinkConnection
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
		go recordCreatePrivateEndpointForExternalServiceAuditLog(ctx, req, created, err)
	}()
	span.SetAttributes(attribute.String("clusterId", req.ClusterId))

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

	var bizErr bizerr.BizError
	domainPL, bizErr := vo.ConsoleToPrivateLinkConnection(ctx, req.PrivateLinkConnection)
	if bizErr != nil {
		return nil, bizErr
	}

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

func (s *privateLinkConnectionConsoleService) DeletePrivateLinkConnection(
	ctx context.Context,
	req *console.DeletePrivateLinkConnectionRequest,
) (resp *console.PrivateLinkConnection, err error) {
	ctx, span := privateLinkConnectionConsoleTracer.Start(
		ctx,
		"privateLinkConnectionConsoleService.DeletePrivateLinkConnection",
	)
	logger := log.WithContextL(ctx).
		With(zap.String("cluster_id", req.ClusterId), zap.String("private_link_id", req.PrivateLinkConnectionId))
	var deleted *entity.PrivateLinkConnection
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
		go recordDeletePrivateEndpointForExternalServiceAuditLog(ctx, req, deleted, 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")
	}

	var bizErr bizerr.BizError
	deleted, bizErr = s.privateLinkConnectionSvc.DeletePrivateLinkConnection(
		ctx,
		req.ClusterId,
		req.PrivateLinkConnectionId,
	)
	if bizErr != nil {
		return nil, bizErr
	}
	return vo.PrivateLinkConnectionToConsole(deleted), nil
}

func (s *privateLinkConnectionConsoleService) GetAvailabilityZones(
	ctx context.Context,
	req *console.GetAvailabilityZonesRequest,
) (_ *console.GetAvailabilityZonesResponse, err error) {
	ctx, span := privateLinkConnectionConsoleTracer.Start(
		ctx,
		"privateLinkConnectionConsoleService.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, bizErr := s.privateLinkConnectionSvc.GetAvailabilityZones(ctx, req.ClusterId)
	if bizErr != nil {
		return nil, bizErr
	}

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

func (s *privateLinkConnectionConsoleService) AttachDomains(
	ctx context.Context,
	req *console.AttachDomainsRequest,
) (resp *console.AttachDomain, err error) {
	ctx, span := privateLinkConnectionConsoleTracer.Start(ctx, "privateLinkConnectionConsoleService.AttachDomains")
	logger := log.WithContextL(ctx).
		With(zap.String("cluster_id", req.GetClusterId()), zap.String("private_link_connection_id", req.GetPrivateLinkConnectionId()))
	var plcd *entity.PLCAttachDomain
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
		if !req.GetValidateOnly() {
			go recordAttachPrivateEndpointForExternalServiceDomainsAuditLog(ctx, req, plcd, err)
		}
	}()
	span.SetAttributes(
		attribute.String("clusterId", req.GetClusterId()),
		attribute.String("privateLinkId", req.GetPrivateLinkConnectionId()),
	)

	logger.Info("start to attach domains for private link connection")
	var bizErr bizerr.BizError
	plcd, bizErr = vo.ConsoleToAttachDomain(ctx, req)
	if bizErr != nil {
		return nil, bizErr
	}

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

func (s *privateLinkConnectionConsoleService) DetachDomains(
	ctx context.Context,
	req *console.DetachDomainsRequest,
) (resp *console.AttachDomain, err error) {
	ctx, span := privateLinkConnectionConsoleTracer.Start(ctx, "privateLinkConnectionConsoleService.DetachDomains")
	logger := log.WithContextL(ctx).
		With(zap.String("cluster_id", req.GetClusterId()), zap.String("private_link_connection_id", req.GetPrivateLinkConnectionId()))
	var plcd *entity.PLCAttachDomain
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PreparePrivateLinkConnectionErrorMessage(ctx, err)
		go recordDetachPrivateEndpointForExternalServiceDomainsAuditLog(ctx, req, plcd, 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")
	}

	var bizErr bizerr.BizError
	plcd, bizErr = s.privateLinkConnectionSvc.DetachDomains(
		ctx,
		req.ClusterId,
		req.PrivateLinkConnectionId,
		req.AttachDomainId,
	)
	if bizErr != nil {
		return nil, bizErr
	}
	return vo.AttachDomainToConsole(ctx, plcd), nil
}
