package server

import (
	"context"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	dplanemgrv1 "github.com/tidbcloud/cluster-service-ng/api/dataplane-manager/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func (s *serverImpl) CreatePrivateLinkService(ctx context.Context, tidbClusterId string, privateLinkService *entity.PrivateLinkService) (*entity.PrivateLinkService, error) {

	if tidbClusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbClusterId are required")
	}

	if privateLinkService.GetId() == "" {
		return nil, status.Errorf(codes.InvalidArgument, "privateLinkServiceId are required")
	}

	if privateLinkService.GetTidbGroupId() == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbGroupId are required")
	}

	tidbGroup, err := s.tidbGroupRepo.Load(ctx, privateLinkService.GetTidbGroupId(), false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load tidbGroup: %s", err)
		}

		// not found, check if it's in creating
		return nil, status.Errorf(codes.NotFound, "tidbGroup %s not found", privateLinkService.GetTidbGroupId())
	}

	if tidbGroup.GetClusterId() != tidbClusterId {
		tilog.Warn(ctx, "tidb group does not belong to the specified cluster",
			zap.String("tidbGroupId", tidbGroup.GetId()),
			zap.String("tidbClusterId", tidbClusterId),
			zap.String("tidbGroupClusterId", tidbGroup.GetClusterId()),
		)
		return nil, status.Errorf(codes.FailedPrecondition, "tidbGroup does not belong to the specified cluster")
	}
	if err := s.guardSharedPoolMutationByID(ctx, tidbGroup.GetSharedPoolId(), sharedPoolMutationBlockedWhilePaused); err != nil {
		return nil, err
	}

	if tidbGroup.GetPhase() != apiv1.TiDBGroup_AVAILABLE && tidbGroup.GetPhase() != apiv1.TiDBGroup_MODIFYING {
		return nil, status.Errorf(codes.FailedPrecondition, "tidbGroup is not in a valid state for creating a private link service: %s", tidbGroup.GetPhase())
	}

	if !tidbGroup.GetSqlEndpoint().GetPrivateLb().GetEnabled() {
		return nil, status.Errorf(codes.FailedPrecondition, "tidbGroup does not have private load balance enabled")
	}

	dplanePLS := &dplanemgrv1.PrivateLinkService{
		Id:               privateLinkService.GetId(),
		LoadBalancerName: entity.GetPrivateSvcName(tidbGroup.GetId()),
		Tags:             entity.BuildTiDBGroupTags(tidbGroup),
		Connections:      cvtPrivateLinkConnectionsToDPlane(privateLinkService.GetConnections()),
	}
	if aws := privateLinkService.GetAws(); aws != nil && aws.ServiceName != "" {
		dplanePLS.Info = &dplanemgrv1.PrivateLinkService_Aws_{
			Aws: &dplanemgrv1.PrivateLinkService_Aws{
				ServiceName: aws.ServiceName,
			},
		}
	}
	resp, err := s.dataplaneClient.CreatePrivateLinkService(ctx, &dplanemgrv1.CreatePrivateLinkServiceRequest{
		DataPlaneId:        tidbGroup.GetDataplaneName(),
		Id:                 privateLinkService.GetId(),
		PrivateLinkService: dplanePLS,
	})

	if err != nil {
		return nil, err
	}

	return cvtPrivateLinkServiceToEntity(resp, tidbGroup), nil
}

func (s *serverImpl) GetPrivateLinkService(ctx context.Context, tidbClusterId, tidbGroupId, privateLinkServiceId string) (*entity.PrivateLinkService, error) {
	if tidbClusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbClusterId are required")
	}

	if tidbGroupId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbGroupId are required")
	}

	if privateLinkServiceId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "privateLinkServiceId is required")
	}

	tidbGroup, err := s.tidbGroupRepo.Load(ctx, tidbGroupId, false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load tidbGroup: %s", err)
		}

		// not found, check if it's in creating
		return nil, status.Errorf(codes.NotFound, "tidbGroup %s not found", tidbGroupId)
	}

	if tidbGroup.GetClusterId() != tidbClusterId {
		tilog.Warn(ctx, "tidb group does not belong to the specified cluster",
			zap.String("tidbGroupId", tidbGroup.GetId()),
			zap.String("tidbClusterId", tidbClusterId),
			zap.String("tidbGroupClusterId", tidbGroup.GetClusterId()),
		)
		return nil, status.Errorf(codes.FailedPrecondition, "tidbGroup does not belong to the specified cluster")
	}

	resp, err := s.dataplaneClient.GetPrivateLinkService(ctx, &dplanemgrv1.GetPrivateLinkServiceRequest{
		Id:          privateLinkServiceId,
		DataPlaneId: tidbGroup.GetDataplaneName(),
	})
	if err != nil {
		return nil, err
	}

	return cvtPrivateLinkServiceToEntity(resp, tidbGroup), nil
}

func (s *serverImpl) UpdatePrivateLinkService(ctx context.Context, tidbClusterId, tidbGroupId, privateLinkServiceId string, privateLinkConnections []*entity.PrivateLinkConnection) (*entity.PrivateLinkService, error) {
	if tidbClusterId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbClusterId are required")
	}

	if tidbGroupId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidbGroupId are required")
	}

	if privateLinkServiceId == "" {
		return nil, status.Errorf(codes.InvalidArgument, "privateLinkServiceId is required")
	}

	tidbGroup, err := s.tidbGroupRepo.Load(ctx, tidbGroupId, false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return nil, status.Errorf(codes.Internal, "failed to load tidbGroup: %s", err)
		}

		// not found, check if it's in creating
		return nil, status.Errorf(codes.NotFound, "tidbGroup %s not found", tidbGroupId)
	}

	if tidbGroup.GetClusterId() != tidbClusterId {
		tilog.Warn(ctx, "tidb group does not belong to the specified cluster",
			zap.String("tidbGroupId", tidbGroup.GetId()),
			zap.String("tidbClusterId", tidbClusterId),
			zap.String("tidbGroupClusterId", tidbGroup.GetClusterId()),
		)
		return nil, status.Errorf(codes.FailedPrecondition, "tidbGroup does not belong to the specified cluster")
	}
	if err := s.guardSharedPoolMutationByID(ctx, tidbGroup.GetSharedPoolId(), sharedPoolMutationBlockedWhilePaused); err != nil {
		return nil, err
	}

	pls, err := s.dataplaneClient.GetPrivateLinkService(ctx, &dplanemgrv1.GetPrivateLinkServiceRequest{
		Id:          privateLinkServiceId,
		DataPlaneId: tidbGroup.GetDataplaneName(),
	})
	if err != nil {
		return nil, err
	}

	if pls.GetStatus() != dplanemgrv1.PrivateLinkService_ACTIVE {
		return nil, status.Errorf(codes.FailedPrecondition, "private link service is not active, current status: %s", pls.GetStatus().String())
	}

	if len(privateLinkConnections) == 0 {
		tilog.Warn(ctx, "UpdatePrivateLinkService called with empty private link connections",
			zap.String("tidbGroupId", tidbGroupId),
			zap.String("privateLinkServiceId", privateLinkServiceId),
		)
		return cvtPrivateLinkServiceToEntity(pls, tidbGroup), nil
	}

	resp, err := s.dataplaneClient.UpdatePrivateLinkService(ctx, &dplanemgrv1.UpdatePrivateLinkServiceRequest{
		Id:                     privateLinkServiceId,
		DataPlaneId:            tidbGroup.GetDataplaneName(),
		PrivateLinkConnections: cvtPrivateLinkConnectionsToDPlane(privateLinkConnections),
	})

	if err != nil {
		return nil, err
	}

	return cvtPrivateLinkServiceToEntity(resp, tidbGroup), nil
}

func cvtPrivateLinkConnectionsToDPlane(privateLinkConnections []*entity.PrivateLinkConnection) []*dplanemgrv1.PrivateLinkService_Connection {
	connections := make([]*dplanemgrv1.PrivateLinkService_Connection, 0, len(privateLinkConnections))

	for _, conn := range privateLinkConnections {
		if conn == nil {
			continue
		}
		connection := &dplanemgrv1.PrivateLinkService_Connection{}
		switch {
		case conn.GetAws() != nil:
			connection.Info = &dplanemgrv1.PrivateLinkService_Connection_Aws_{
				Aws: &dplanemgrv1.PrivateLinkService_Connection_Aws{
					VpcEndpointId: conn.GetAws().GetVpcEndpointId(),
				},
			}
		case conn.GetGcp() != nil:
			connection.Info = &dplanemgrv1.PrivateLinkService_Connection_Gcp_{
				Gcp: &dplanemgrv1.PrivateLinkService_Connection_Gcp{
					PrivateServiceConnectEndpoint: conn.GetGcp().GetPrivateServiceConnectEndpoint(),
					Host:                          conn.GetGcp().GetHost(),
				},
			}
		case conn.GetAzure() != nil:
			connection.Info = &dplanemgrv1.PrivateLinkService_Connection_Azure_{
				Azure: &dplanemgrv1.PrivateLinkService_Connection_Azure{
					PrivateEndpointResourceId: conn.GetAzure().GetPrivateEndpointResourceId(),
					PrivateIpAddress:          conn.GetAzure().GetPrivateIpAddress(),
					Host:                      conn.GetAzure().GetHost(),
				},
			}
		case conn.GetAliCloud() != nil:
			connection.Info = &dplanemgrv1.PrivateLinkService_Connection_AliCloud_{
				AliCloud: &dplanemgrv1.PrivateLinkService_Connection_AliCloud{
					VpcEndpointId:     conn.GetAliCloud().GetVpcEndpointId(),
					DefaultDomainName: conn.GetAliCloud().GetDefaultDomainName(),
					Host:              conn.GetAliCloud().GetHost(),
				},
			}
		}
		connections = append(connections, connection)
	}

	return connections
}

func (s *serverImpl) DeletePrivateLinkService(ctx context.Context, tidbClusterId, tidbGroupId, privateLinkServiceId string) error {
	if tidbClusterId == "" {
		return status.Errorf(codes.InvalidArgument, "tidbClusterId are required")
	}

	if tidbGroupId == "" {
		return status.Errorf(codes.InvalidArgument, "tidbGroupId are required")
	}

	if privateLinkServiceId == "" {
		return status.Errorf(codes.InvalidArgument, "privateLinkServiceId is required")
	}

	tidbGroup, err := s.tidbGroupRepo.Load(ctx, tidbGroupId, false)
	if err != nil {
		if status.Code(err) != codes.NotFound {
			return status.Errorf(codes.Internal, "failed to load tidbGroup: %s", err)
		}
		// not found, check if it's in creating
		return status.Errorf(codes.NotFound, "tidbGroup %s not found", tidbGroupId)
	}

	if tidbGroup.GetClusterId() != tidbClusterId {
		tilog.Warn(ctx, "tidb group does not belong to the specified cluster",
			zap.String("tidbGroupId", tidbGroup.GetId()),
			zap.String("tidbClusterId", tidbClusterId),
			zap.String("tidbGroupClusterId", tidbGroup.GetClusterId()),
		)
		return status.Errorf(codes.FailedPrecondition, "tidbGroup does not belong to the specified cluster")
	}
	if err := s.guardSharedPoolMutationByID(ctx, tidbGroup.GetSharedPoolId(), sharedPoolMutationBlockedWhilePaused); err != nil {
		return err
	}

	_, err = s.dataplaneClient.DeletePrivateLinkService(ctx, &dplanemgrv1.DeletePrivateLinkServiceRequest{
		Id:          privateLinkServiceId,
		DataPlaneId: tidbGroup.GetDataplaneName(),
	})
	if err != nil {
		if status.Code(err) == codes.NotFound {
			return nil // If the service is not found, we can consider it deleted.
		}
		return err
	}

	return nil
}

func cvtPrivateLinkServiceToEntity(pls *dplanemgrv1.PrivateLinkService, tidbGroup *entity.TiDBGroup) *entity.PrivateLinkService {
	if pls == nil {
		return nil
	}

	plsEntity := &entity.PrivateLinkService{
		Id:             pls.GetId(),
		TidbGroupId:    tidbGroup.GetId(),
		SharedPoolId:   tidbGroup.GetSharedPoolId(),
		ServiceDnsName: pls.GetServiceDnsName(),
		Connections:    make([]*entity.PrivateLinkConnection, 0, len(pls.GetConnections())),
	}

	switch {
	case pls.GetAws() != nil:
		plsEntity.Info = &apiv1.PrivateLinkService_Aws_{
			Aws: &apiv1.PrivateLinkService_Aws{
				AzIds:       pls.GetAws().GetAzIds(),
				ServiceName: pls.GetAws().GetServiceName(),
			},
		}
	case pls.GetGcp() != nil:
		plsEntity.Info = &apiv1.PrivateLinkService_Gcp_{
			Gcp: &apiv1.PrivateLinkService_Gcp{
				ServiceAttachment: pls.GetGcp().GetServiceAttachment(),
			},
		}
	case pls.GetAzure() != nil:
		plsEntity.Info = &apiv1.PrivateLinkService_Azure_{
			Azure: &apiv1.PrivateLinkService_Azure{
				ServiceResourceId: pls.GetAzure().GetServiceResourceId(),
			},
		}
	case pls.GetAliCloud() != nil:
		plsEntity.Info = &apiv1.PrivateLinkService_AliCloud_{
			AliCloud: &apiv1.PrivateLinkService_AliCloud{
				AzIds:       pls.GetAliCloud().GetAzIds(),
				ServiceName: pls.GetAliCloud().GetServiceName(),
			},
		}
	}

	switch pls.GetStatus() {
	case dplanemgrv1.PrivateLinkService_STATE_UNSPECIFIED:
		plsEntity.Status = apiv1.PrivateLinkService_STATE_UNSPECIFIED
	case dplanemgrv1.PrivateLinkService_CREATING:
		plsEntity.Status = apiv1.PrivateLinkService_CREATING
	case dplanemgrv1.PrivateLinkService_ACTIVE:
		plsEntity.Status = apiv1.PrivateLinkService_ACTIVE
	case dplanemgrv1.PrivateLinkService_DELETING:
		plsEntity.Status = apiv1.PrivateLinkService_DELETING
	}

	for _, conn := range pls.GetConnections() {
		connection := &entity.PrivateLinkConnection{
			Message: conn.GetMessage(),
		}

		switch {
		case conn.GetAws() != nil:
			connection.Info = &apiv1.PrivateLinkService_Connection_Aws_{
				Aws: &apiv1.PrivateLinkService_Connection_Aws{
					VpcEndpointId: conn.GetAws().GetVpcEndpointId(),
				},
			}
		case conn.GetGcp() != nil:
			connection.Info = &apiv1.PrivateLinkService_Connection_Gcp_{
				Gcp: &apiv1.PrivateLinkService_Connection_Gcp{
					PrivateServiceConnectEndpoint: conn.GetGcp().GetPrivateServiceConnectEndpoint(),
					Host:                          conn.GetGcp().GetHost(),
				},
			}
		case conn.GetAzure() != nil:
			connection.Info = &apiv1.PrivateLinkService_Connection_Azure_{
				Azure: &apiv1.PrivateLinkService_Connection_Azure{
					PrivateEndpointResourceId: conn.GetAzure().GetPrivateEndpointResourceId(),
					PrivateIpAddress:          conn.GetAzure().GetPrivateIpAddress(),
					Host:                      conn.GetAzure().GetHost(),
				},
			}
		case conn.GetAliCloud() != nil:
			connection.Info = &apiv1.PrivateLinkService_Connection_AliCloud_{
				AliCloud: &apiv1.PrivateLinkService_Connection_AliCloud{
					VpcEndpointId:     conn.GetAliCloud().GetVpcEndpointId(),
					DefaultDomainName: conn.GetAliCloud().GetDefaultDomainName(),
					Host:              conn.GetAliCloud().GetHost(),
				},
			}
		}

		switch conn.GetStatus() {
		case dplanemgrv1.PrivateLinkService_Connection_STATE_UNSPECIFIED,
			dplanemgrv1.PrivateLinkService_Connection_PENDING:
			connection.Status = apiv1.PrivateLinkService_Connection_PENDING
		case dplanemgrv1.PrivateLinkService_Connection_ACTIVE:
			connection.Status = apiv1.PrivateLinkService_Connection_ACTIVE
		case dplanemgrv1.PrivateLinkService_Connection_DELETING:
			connection.Status = apiv1.PrivateLinkService_Connection_DELETING
		case dplanemgrv1.PrivateLinkService_Connection_FAILED:
			connection.Status = apiv1.PrivateLinkService_Connection_FAILED
		}

		plsEntity.Connections = append(plsEntity.Connections, connection)
	}

	return plsEntity
}
