package privateendpoint

import (
	"context"
	"errors"
	"fmt"
	"strconv"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"gorm.io/gorm"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/constant"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s *serverImpl) ListPrivateEndpointConnections(
	ctx context.Context,
	req *openapingv1beta2.ListPrivateEndpointConnectionsRequest,
) (*openapingv1beta2.ListPrivateEndpointConnectionsResponse, error) {
	ctx, span := tracer.Start(ctx, "ListPrivateEndpointConnections")
	defer span.End()

	// Extract request meta.
	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	tidbId, err := strconv.ParseUint(req.GetTidbId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to parse tidb ID")
	}
	cluster, err := s.nextgenCli.GetCluster(ctx, &globalngv1.GetClusterRequest{
		ClusterId: tidbId,
	})
	if err != nil {
		return nil, err
	}

	connections, err := s.nextgenCli.ListPrivateLinkConnections(ctx, &globalngv1.ListPrivateLinkConnectionsRequest{
		ClusterId: tidbId,
	})
	if err != nil {
		return nil, err
	}

	openapiConnections := make(
		[]*openapingv1beta2.PrivateEndpointConnection,
		0,
		len(connections.GetPrivateLinkConnections()),
	)
	for _, connection := range connections.GetPrivateLinkConnections() {
		out, err := privateEndpointConnectionFromGlobalToOpenApi(connection, cluster)
		if err != nil {
			return nil, err
		}
		openapiConnections = append(openapiConnections, out)
	}

	// Paginate the results locally.
	pagedConns, nextPageToken, err := common.LocalPagination(
		openapiConnections,
		req,
		common.ListDefaultPageSize,
		common.ListMaxPageSize,
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to paginate private link connections")
	}

	return &openapingv1beta2.ListPrivateEndpointConnectionsResponse{
		PrivateEndpointConnections: pagedConns,
		TotalSize:                  int32(len(openapiConnections)),
		NextPageToken:              nextPageToken,
	}, nil
}

func (s *serverImpl) GetPrivateEndpointConnection(
	ctx context.Context,
	req *openapingv1beta2.GetPrivateEndpointConnectionRequest,
) (*openapingv1beta2.PrivateEndpointConnection, error) {
	ctx, span := tracer.Start(ctx, "GetPrivateEndpointConnection")
	defer span.End()

	// Extract request meta.
	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	tidbId, err := strconv.ParseUint(req.GetTidbId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to parse tidb ID")
	}
	cluster, err := s.nextgenCli.GetCluster(ctx, &globalngv1.GetClusterRequest{
		ClusterId: tidbId,
	})
	if err != nil {
		return nil, err
	}

	if req.GetPrivateEndpointConnectionId() == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "private endpoint connection ID is required")
	}

	connection, err := s.nextgenCli.GetPrivateLinkConnection(ctx, &globalngv1.GetPrivateLinkConnectionRequest{
		PrivateLinkConnectionId: req.GetPrivateEndpointConnectionId(),
	})
	if err != nil {
		return nil, err
	}

	return privateEndpointConnectionFromGlobalToOpenApi(connection, cluster)
}

func (s *serverImpl) DeletePrivateEndpointConnection(
	ctx context.Context,
	req *openapingv1beta2.DeletePrivateEndpointConnectionRequest,
) (*openapingv1beta2.PrivateEndpointConnection, error) {
	ctx, span := tracer.Start(ctx, "DeletePrivateEndpointConnection")
	defer span.End()

	var (
		err        error
		reqMeta    *metadata.RequestMeta
		connection *globalngv1.PrivateLinkConnection
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypePremium)
		details[auditlog.KeyEndpoint] = connection.GetEndpointId()
		return details
	}
	defer func() {
		if !req.GetValidateOnly() {
			s.auditLogSender.SubmitAuditLogForNextgen(ctx, auditlog.EventDeletePrivateEndpoint, reqMeta, detailsFunc, err)
		}
	}()

	// Extract request meta.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	if req.GetPrivateEndpointConnectionId() == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "private endpoint connection ID is required")
	}

	tidbId, err := strconv.ParseUint(req.GetTidbId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to parse tidb ID")
	}
	cluster, err := s.nextgenCli.GetCluster(ctx, &globalngv1.GetClusterRequest{
		ClusterId: tidbId,
	})
	if err != nil {
		return nil, err
	}

	connection, err = s.nextgenCli.DeletePrivateLinkConnection(
		ctx,
		&globalngv1.DeletePrivateLinkConnectionRequest{
			PrivateLinkConnectionId: req.GetPrivateEndpointConnectionId(),
		},
	)
	if err != nil {
		return nil, err
	}

	return privateEndpointConnectionFromGlobalToOpenApi(connection, cluster)
}

func (s *serverImpl) CreatePrivateEndpointConnection(
	ctx context.Context,
	req *openapingv1beta2.CreatePrivateEndpointConnectionRequest,
) (*openapingv1beta2.PrivateEndpointConnection, error) {
	ctx, span := tracer.Start(ctx, "CreatePrivateEndpointConnection")
	defer span.End()

	var (
		err     error
		reqMeta *metadata.RequestMeta
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypePremium)
		details[auditlog.KeyEndpoint] = req.GetPrivateEndpointConnection().GetEndpointId()
		return details
	}
	defer func() {
		if !req.GetValidateOnly() {
			s.auditLogSender.SubmitAuditLogForNextgen(ctx, auditlog.EventCreatePrivateEndpoint, reqMeta, detailsFunc, err)
		}
	}()

	// Extract request meta.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	tidbId, err := strconv.ParseUint(req.GetTidbId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to parse tidb ID")
	}
	// check ClusterAction_CLUSTER_ACTION_CREATE_PRIVATE_LINK mutex
	isMutex, status, err := s.customStatusRepo.IsMutexClusterAction(
		ctx,
		tidbId,
		tidbmgmtservicev1.ClusterAction_CLUSTER_ACTION_CREATE_PRIVATE_LINK,
	)
	if err != nil {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			err = fmt.Errorf("failed to check mutex cluster action: %w", err)
			return nil, bizerror.NewInternal(err, err.Error())
		}
	}

	if isMutex {
		tilog.Error(
			ctx,
			"mutex custom status is exist",
			zap.Uint64("cluster_id", tidbId),
			zap.Any("custom status", status),
		)
		return nil, bizerror.NewForbiddenForClusterCustomStatus(
			constant.ErrClusterCustomStatusForbidden,
			status.StatusType.String(),
		)
	}

	cluster, err := s.nextgenCli.GetCluster(ctx, &globalngv1.GetClusterRequest{
		ClusterId: tidbId,
	})
	if err != nil {
		return nil, err
	}

	output, err := s.nextgenCli.CreatePrivateLinkConnection(
		ctx,
		buildGlobalCreatePrivateLinkConnectionRequest(tidbId, req),
	)
	if err != nil {
		return nil, err
	}

	return privateEndpointConnectionFromGlobalToOpenApi(output, cluster)
}

func privateEndpointConnectionFromGlobalToOpenApi(
	connection *globalngv1.PrivateLinkConnection,
	cluster *globalngv1.Cluster,
) (*openapingv1beta2.PrivateEndpointConnection, bizerr.BizError) {
	_, regionId, cloudProvider, err := common.BuildOpenApiRegionName(
		cluster.GetCloudProviderId().String(),
		cluster.GetRegionId().String(),
	)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to build region ID")
	}

	return &openapingv1beta2.PrivateEndpointConnection{
		Name: fmt.Sprintf(
			"tidbs/%d/privateEndpointConnections/%s",
			connection.GetClusterId(),
			connection.GetPrivateLinkConnectionId(),
		),
		Id:                          connection.GetPrivateLinkConnectionId(),
		TidbId:                      strconv.FormatUint(connection.GetClusterId(), 10),
		PrivateEndpointConnectionId: connection.GetPrivateLinkConnectionId(),
		RegionId:                    regionId,
		RegionDisplayName:           cluster.GetRegionDisplayName(),
		CloudProvider:               cloudProvider,
		EndpointId:                  connection.GetEndpointId(),
		EndpointState:               privateEndpointConnectionStateFromGlobalToOpenApi(connection.GetState()),
		Message:                     connection.GetMessage(),
		PrivateLinkService:          connection.GetPrivateLinkService(),
		PrivateLinkServiceState:     privateLinkServiceStateToOpenapi(connection.GetPrivateLinkServiceState()),
		PrivateDomainName:           connection.GetPrivateDomainName(),
		PrivateIpAddress:            connection.GetPrivateIpAddress(),
		Host:                        connection.GetHost(),
		Port:                        connection.GetPort(),
	}, nil
}

func buildGlobalCreatePrivateLinkConnectionRequest(
	tidbID uint64,
	req *openapingv1beta2.CreatePrivateEndpointConnectionRequest,
) *globalngv1.CreatePrivateLinkConnectionRequest {
	return &globalngv1.CreatePrivateLinkConnectionRequest{
		ClusterId: tidbID,
		PrivateLinkConnection: &globalngv1.PrivateLinkConnection{
			EndpointId:        req.GetPrivateEndpointConnection().GetEndpointId(),
			EndpointType:      globalngv1.PrivateLinkConnection_CUSTOMER,
			PrivateIpAddress:  req.GetPrivateEndpointConnection().GetPrivateIpAddress(),
			PrivateDomainName: req.GetPrivateEndpointConnection().GetPrivateDomainName(),
		},
	}
}

func privateEndpointConnectionStateFromGlobalToOpenApi(
	state globalngv1.PrivateLinkConnection_State,
) openapingv1beta2.PrivateEndpointConnection_EndpointState {
	switch state {
	case globalngv1.PrivateLinkConnection_PENDING:
		return openapingv1beta2.PrivateEndpointConnection_PENDING
	case globalngv1.PrivateLinkConnection_ACTIVE:
		return openapingv1beta2.PrivateEndpointConnection_ACTIVE
	case globalngv1.PrivateLinkConnection_DELETING:
		return openapingv1beta2.PrivateEndpointConnection_DELETING
	case globalngv1.PrivateLinkConnection_FAILED:
		return openapingv1beta2.PrivateEndpointConnection_FAILED
	default:
		return openapingv1beta2.PrivateEndpointConnection_ENDPOINT_STATE_UNSPECIFIED
	}
}
