package privatelink

import (
	"context"
	"slices"
	"strings"

	"go.opentelemetry.io/otel"
	"google.golang.org/protobuf/types/known/emptypb"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/repository"
	plcdomain "github.com/tidbcloud/tidb-management-service/serverless-global-service/domain/service/privatelink"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
)

var Tracer = otel.Tracer("app.privatelink")

type service struct {
	globalserverlessv1.UnimplementedServerlessPrivateLinkConnectionServiceServer

	plcService  plcdomain.Service
	clusterRepo repository.ClusterRepo
	regionRepo  repository.RegionRepo
}

func NewService(
	plcService plcdomain.Service,
	clusterRepo repository.ClusterRepo,
	regionRepo repository.RegionRepo,
) globalserverlessv1.ServerlessPrivateLinkConnectionServiceServer {
	return &service{
		plcService:  plcService,
		clusterRepo: clusterRepo,
		regionRepo:  regionRepo,
	}
}

func (s *service) CreateAWSVpcEndpoint(
	ctx context.Context,
	req *globalserverlessv1.CreateAWSVpcEndpointRequest,
) (*globalserverlessv1.CreateAWSVpcEndpointResponse, error) {
	ctx, span := Tracer.Start(ctx, "CreateAWSVpcEndpoint")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.CreateAWSVpcEndpoint(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) DescribeAWSVpcEndpoint(
	ctx context.Context,
	req *globalserverlessv1.DescribeAWSVpcEndpointRequest,
) (*globalserverlessv1.DescribeAWSVpcEndpointResponse, error) {
	ctx, span := Tracer.Start(ctx, "DescribeAWSVpcEndpoint")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.DescribeAWSVpcEndpoint(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) DeleteAWSVpcEndpoint(
	ctx context.Context,
	req *globalserverlessv1.DeleteAWSVpcEndpointRequest,
) (*emptypb.Empty, error) {
	ctx, span := Tracer.Start(ctx, "DeleteAWSVpcEndpoint")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	if err := s.plcService.DeleteAWSVpcEndpoint(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region); err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *service) CreateAliCloudVpcEndpoint(
	ctx context.Context,
	req *globalserverlessv1.CreateAliCloudVpcEndpointRequest,
) (*globalserverlessv1.CreateAliCloudVpcEndpointResponse, error) {
	ctx, span := Tracer.Start(ctx, "CreateAliCloudVpcEndpoint")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.CreateAliCloudVpcEndpoint(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) DescribeAliCloudVpcEndpoint(
	ctx context.Context,
	req *globalserverlessv1.DescribeAliCloudVpcEndpointRequest,
) (*globalserverlessv1.DescribeAliCloudVpcEndpointResponse, error) {
	ctx, span := Tracer.Start(ctx, "DescribeAliCloudVpcEndpoint")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.DescribeAliCloudVpcEndpoint(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) DeleteAliCloudVpcEndpoint(
	ctx context.Context,
	req *globalserverlessv1.DeleteAliCloudVpcEndpointRequest,
) (*emptypb.Empty, error) {
	ctx, span := Tracer.Start(ctx, "DeleteAliCloudVpcEndpoint")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	if err := s.plcService.DeleteAliCloudVpcEndpoint(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region); err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *service) GetZoneIds(
	ctx context.Context,
	req *globalserverlessv1.GetZoneIdsRequest,
) (*globalserverlessv1.GetZoneIdsResponse, error) {
	ctx, span := Tracer.Start(ctx, "GetZoneIds")
	defer span.End()
	projectID, clusterID, region, err := s.resolveMSKRoute(
		ctx,
		req.GetClusterId(),
		req.GetCloudProviderId(),
		req.GetOrganizationId(),
		req.GetServicePlan(),
		req.GetRegion(),
		req.GetKubernetes(),
	)
	if err != nil {
		return nil, err
	}
	return s.plcService.GetZoneIds(ctx, req, projectID, clusterID, region)
}

func (s *service) ListAliCloudVpcEndpointZones(
	ctx context.Context,
	req *globalserverlessv1.ListAliCloudVpcEndpointZonesRequest,
) (*globalserverlessv1.ListAliCloudVpcEndpointZonesResponse, error) {
	ctx, span := Tracer.Start(ctx, "ListAliCloudVpcEndpointZones")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.ListAliCloudVpcEndpointZones(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) RemoveAliCloudZoneFromVpcEndpoint(
	ctx context.Context,
	req *globalserverlessv1.RemoveAliCloudZoneFromVpcEndpointRequest,
) (*emptypb.Empty, error) {
	ctx, span := Tracer.Start(ctx, "RemoveAliCloudZoneFromVpcEndpoint")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	if err := s.plcService.RemoveAliCloudZoneFromVpcEndpoint(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region); err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *service) CreateAliCloudDNSRecord(
	ctx context.Context,
	req *globalserverlessv1.CreateAliCloudDNSRecordRequest,
) (*globalserverlessv1.CreateAliCloudDNSRecordResponse, error) {
	ctx, span := Tracer.Start(ctx, "CreateAliCloudDNSRecord")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.CreateAliCloudDNSRecord(ctx, req, cluster.Region)
}

func (s *service) DeleteAliCloudDNSRecord(
	ctx context.Context,
	req *globalserverlessv1.DeleteAliCloudDNSRecordRequest,
) (*emptypb.Empty, error) {
	ctx, span := Tracer.Start(ctx, "DeleteAliCloudDNSRecord")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	if err := s.plcService.DeleteAliCloudDNSRecord(ctx, req, cluster.Region); err != nil {
		return nil, err
	}
	return &emptypb.Empty{}, nil
}

func (s *service) CreateAWSRoute53Records(
	ctx context.Context,
	req *globalserverlessv1.CreateAWSRoute53RecordsRequest,
) (*globalserverlessv1.CreateAWSRoute53RecordsResponse, error) {
	ctx, span := Tracer.Start(ctx, "CreateAWSRoute53Records")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.CreateAWSRoute53Records(ctx, req, cluster.Region)
}

func (s *service) DeleteAWSRoute53Records(
	ctx context.Context,
	req *globalserverlessv1.DeleteAWSRoute53RecordsRequest,
) (*globalserverlessv1.DeleteAWSRoute53RecordsResponse, error) {
	ctx, span := Tracer.Start(ctx, "DeleteAWSRoute53Records")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.DeleteAWSRoute53Records(ctx, req, cluster.Region)
}

func (s *service) GetAWSRoute53Change(
	ctx context.Context,
	req *globalserverlessv1.GetAWSRoute53ChangeRequest,
) (*globalserverlessv1.GetAWSRoute53ChangeResponse, error) {
	ctx, span := Tracer.Start(ctx, "GetAWSRoute53Change")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.GetAWSRoute53Change(ctx, req, cluster.Region)
}

func (s *service) CreateAWSRoute53PrivateHostedZone(
	ctx context.Context,
	req *globalserverlessv1.CreateAWSRoute53PrivateHostedZoneRequest,
) (*globalserverlessv1.CreateAWSRoute53PrivateHostedZoneResponse, error) {
	ctx, span := Tracer.Start(ctx, "CreateAWSRoute53PrivateHostedZone")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.CreateAWSRoute53PrivateHostedZone(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) DeleteAWSRoute53PrivateHostedZone(
	ctx context.Context,
	req *globalserverlessv1.DeleteAWSRoute53PrivateHostedZoneRequest,
) (*globalserverlessv1.DeleteAWSRoute53PrivateHostedZoneResponse, error) {
	ctx, span := Tracer.Start(ctx, "DeleteAWSRoute53PrivateHostedZone")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.DeleteAWSRoute53PrivateHostedZone(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) CreateAliCloudDNSPrivateHostedZone(
	ctx context.Context,
	req *globalserverlessv1.CreateAliCloudDNSPrivateHostedZoneRequest,
) (*globalserverlessv1.CreateAliCloudDNSPrivateHostedZoneResponse, error) {
	ctx, span := Tracer.Start(ctx, "CreateAliCloudDNSPrivateHostedZone")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.CreateAliCloudDNSPrivateHostedZone(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) DeleteAliCloudDNSPrivateHostedZone(
	ctx context.Context,
	req *globalserverlessv1.DeleteAliCloudDNSPrivateHostedZoneRequest,
) (*globalserverlessv1.DeleteAliCloudDNSPrivateHostedZoneResponse, error) {
	ctx, span := Tracer.Start(ctx, "DeleteAliCloudDNSPrivateHostedZone")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.DeleteAliCloudDNSPrivateHostedZone(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) AttachVpcToAliCloudDNSPrivateHostedZone(
	ctx context.Context,
	req *globalserverlessv1.AttachVpcToAliCloudDNSPrivateHostedZoneRequest,
) (*globalserverlessv1.AttachVpcToAliCloudDNSPrivateHostedZoneResponse, error) {
	ctx, span := Tracer.Start(ctx, "AttachVpcToAliCloudDNSPrivateHostedZone")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.AttachVpcToAliCloudDNSPrivateHostedZone(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) DetachVpcFromAliCloudDNSPrivateHostedZone(
	ctx context.Context,
	req *globalserverlessv1.DetachVpcFromAliCloudDNSPrivateHostedZoneRequest,
) (*globalserverlessv1.DetachVpcFromAliCloudDNSPrivateHostedZoneResponse, error) {
	ctx, span := Tracer.Start(ctx, "DetachVpcFromAliCloudDNSPrivateHostedZone")
	defer span.End()
	cluster, err := s.clusterRepo.Get(ctx, req.GetClusterId(), false)
	if err != nil {
		return nil, err
	}
	return s.plcService.DetachVpcFromAliCloudDNSPrivateHostedZone(ctx, req, cluster.ProjectId, cluster.ClusterId, cluster.Region)
}

func (s *service) CreateMSKVPCConnection(
	ctx context.Context,
	req *globalserverlessv1.CreateMSKVPCConnectionRequest,
) (*globalserverlessv1.CreateMSKVPCConnectionResponse, error) {
	ctx, span := Tracer.Start(ctx, "CreateMSKVPCConnection")
	defer span.End()
	projectId, clusterId, region, err := s.resolveMSKRoute(
		ctx,
		req.GetClusterId(),
		req.GetCloudProviderId(),
		req.GetOrganizationId(),
		req.GetServicePlan(),
		req.GetRegion(),
		req.GetKubernetes(),
	)
	if err != nil {
		return nil, err
	}
	return s.plcService.CreateMSKVPCConnection(ctx, req, projectId, clusterId, region)
}

func (s *service) DeleteMSKVPCConnection(
	ctx context.Context,
	req *globalserverlessv1.DeleteMSKVPCConnectionRequest,
) (*globalserverlessv1.DeleteMSKVPCConnectionResponse, error) {
	ctx, span := Tracer.Start(ctx, "DeleteMSKVPCConnection")
	defer span.End()
	projectId, clusterId, region, err := s.resolveMSKRoute(
		ctx,
		req.GetClusterId(),
		req.GetCloudProviderId(),
		req.GetOrganizationId(),
		req.GetServicePlan(),
		req.GetRegion(),
		req.GetKubernetes(),
	)
	if err != nil {
		return nil, err
	}
	return s.plcService.DeleteMSKVPCConnection(ctx, req, projectId, clusterId, region)
}

func (s *service) GetMSKVPCConnection(
	ctx context.Context,
	req *globalserverlessv1.GetMSKVPCConnectionRequest,
) (*globalserverlessv1.GetMSKVPCConnectionResponse, error) {
	ctx, span := Tracer.Start(ctx, "GetMSKVPCConnection")
	defer span.End()
	projectId, clusterId, region, err := s.resolveMSKRoute(
		ctx,
		req.GetClusterId(),
		req.GetCloudProviderId(),
		req.GetOrganizationId(),
		req.GetServicePlan(),
		req.GetRegion(),
		req.GetKubernetes(),
	)
	if err != nil {
		return nil, err
	}
	return s.plcService.GetMSKVPCConnection(ctx, req, projectId, clusterId, region)
}

func (s *service) resolveMSKRoute(
	ctx context.Context,
	clusterId string,
	cloudProvider globalcommonv1.CloudProvider_Name,
	organizationId uint64,
	servicePlan globalcommonv1.ServicePlan,
	regionName string,
	kubernetes string,
) (uint64, string, *entity.Region, error) {
	if regionName != "" && kubernetes != "" && cloudProvider != globalcommonv1.CloudProvider_NAME_UNSPECIFIED && organizationId != 0 &&
		servicePlan != globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
		regionId, ok := globalcommonv1.Region_Name_value[strings.ToUpper(regionName)]
		if !ok {
			id, err := enum.RegionFromString(cloudProvider.String(), regionName)
			if err != nil {
				return 0, "", nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid msk region %s/%s", cloudProvider, regionName)
			}
			regionId = int32(id)
		}
		regions, err := s.regionRepo.LoadRegions(ctx, &repository.CloudRegionFilter{
			CloudProviderNames: []globalcommonv1.CloudProvider_Name{cloudProvider},
			Names:              []globalcommonv1.Region_Name{globalcommonv1.Region_Name(regionId)},
			ServicePlans:       []globalcommonv1.ServicePlan{servicePlan},
		})
		if err != nil {
			return 0, "", nil, err
		}

		var publicRegions, privateRegions []*entity.Region
		for _, region := range regions {
			if region.GetState() != globalcommonv1.Region_ACTIVE || strings.TrimSpace(region.GetServiceAddress()) == "" {
				continue
			}
			if !region.GetIsPublic() {
				if slices.Contains(region.GetOpenedTenantIds(), organizationId) {
					privateRegions = append(privateRegions, region)
				}
				continue
			}
			publicRegions = append(publicRegions, region)
		}
		candidates := publicRegions
		if len(privateRegions) > 0 {
			candidates = privateRegions
		}
		if len(candidates) == 0 {
			return 0, "", nil, bizerr.New(
				bizerr.ErrCodeRecordNotFound,
				"regional service for provider %s, region %s, service plan %s is not available for organization %d",
				cloudProvider, regionName, servicePlan, organizationId,
			)
		}
		serviceAddresses := make(map[string]*entity.Region)
		for _, region := range candidates {
			serviceAddresses[strings.TrimSpace(region.GetServiceAddress())] = region
		}
		if len(serviceAddresses) != 1 {
			return 0, "", nil, bizerr.New(
				bizerr.ErrCodeParams,
				"multiple regional service addresses configured for provider %s, region %s, service plan %s",
				cloudProvider, regionName, servicePlan,
			)
		}
		for _, region := range serviceAddresses {
			return 0, "", region, nil
		}
	}

	cluster, err := s.clusterRepo.Get(ctx, clusterId, false)
	if err != nil {
		return 0, "", nil, err
	}
	return cluster.ProjectId, cluster.ClusterId, cluster.Region, nil
}
