package privatelink

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

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	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/domain/entity"
	"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) CreatePrivateEndpointConnection(
	ctx context.Context,
	req *openapidedicatedv1beta1.CreatePrivateEndpointConnectionRequest,
) (*openapidedicatedv1beta1.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.TierTypeDedicated)
		details[auditlog.KeyTidbNodeGroupId] = req.GetPrivateEndpointConnection().GetTidbNodeGroupId()
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventCreatePrivateEndpointService, 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")
	}

	// Parse cluster ID.
	clusterId, err := strconv.ParseUint(req.GetClusterId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to parse cluster ID")
	}

	// check ClusterAction_CLUSTER_ACTION_CREATE_PRIVATE_LINK mutex
	isMutex, status, err := s.customStatusRepo.IsMutexClusterAction(
		ctx,
		clusterId,
		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", clusterId),
			zap.Any("custom status", status),
		)
		return nil, bizerror.NewForbiddenForClusterCustomStatus(
			constant.ErrClusterCustomStatusForbidden,
			status.StatusType.String(),
		)
	}

	// Parse tidb node group ID.
	tidbGroupId, err := strconv.ParseUint(req.GetPrivateEndpointConnection().GetTidbNodeGroupId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to parse TiDB group ID")
	}

	// Get the cluster.
	cluster, err := s.clusterSvc.GetCluster(ctx, clusterId, entity.ClusterViewClusterViewBasic, false, false)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get cluster")
	}

	_, regionId, cloudProvider, err := common.BuildOpenApiRegionName(cluster.CloudProvider.String(), cluster.Region)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get region")
	}

	// Get the region.
	globalRegion, err := regionCodeToGlobal(cluster.Region)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get region")
	}

	region, err := s.dedicatedCli.SelectRegionByOrgId(ctx, &globaldedicatedv1.SelectRegionByOrgIdRequest{
		CloudProviderId: common.OpenApiProviderToGlobal(cluster.CloudProvider),
		RegionId:        globalRegion,
		OrganizationId:  reqMeta.OrgID,
	})
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get region")
	}

	// Get the TiDB group.
	tidbGroupResp, err := s.dedicatedCli.ListTidbNodeGroups(ctx, &globaldedicatedv1.ListTidbNodeGroupsRequest{
		ClusterIds: []uint64{clusterId},
		Ids:        []uint64{tidbGroupId},
	})
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to query TiDB node groups")
	}
	if len(tidbGroupResp.GetTidbNodeGroups()) == 0 {
		return nil, bizerror.NewInternal(fmt.Errorf("no tidb node group found"), "no tidb node group found")
	}
	if len(tidbGroupResp.GetTidbNodeGroups()) > 1 {
		return nil, bizerror.NewInternal(
			fmt.Errorf("multiple tidb node groups found"),
			"multiple tidb node groups found",
		)
	}

	globalResp, err := s.dedicatedCli.CreatePrivateLinkEndpoint(
		ctx,
		&globaldedicatedv1.CreatePrivateLinkEndpointRequest{
			OrgId:     reqMeta.OrgID,
			ProjectId: cluster.ProjectId,
			ClusterId: cluster.ClusterId,
			PrivateLinkEndpoint: &globaldedicatedv1.PrivateLinkEndpoint{
				EndpointName:     req.GetPrivateEndpointConnection().GetEndpointId(),
				Type:             globaldedicatedv1.PrivateLinkEndpoint_TYPE_CUSTOMER,
				PrivateIpAddress: req.GetPrivateEndpointConnection().GetPrivateIpAddress(),
			},
			TidbGroupId:  tidbGroupId,
			ValidateOnly: req.GetValidateOnly(),
		},
	)
	if err != nil {
		return nil, err
	}

	afterCreateResp, err := s.dedicatedCli.ListTidbNodeGroupPrivateLinkEndpoints(
		ctx,
		&globaldedicatedv1.ListTidbNodeGroupPrivateLinkEndpointsRequest{
			ClusterId:   cluster.ClusterId,
			TidbGroupId: tidbGroupId,
		},
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to get private link service")
	}

	res, err := buildConnection(
		tidbGroupResp.GetTidbNodeGroups()[0],
		globalResp.GetPrivateLinkEndpoint(),
		afterCreateResp.GetPrivateLinkService(),
		cluster,
		regionId,
		region.GetDisplayName(),
		cloudProvider,
	)
	if err != nil {
		return nil, bizerror.NewInternal(err, "failed to convert connection")
	}

	return res, nil
}

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

	var (
		err     error
		reqMeta *metadata.RequestMeta
	)

	detailsFunc := func() auditlog.Details {
		details := eventbus.NewAuditLogDetails(eventbus.TierTypeDedicated)
		details[auditlog.KeyTidbNodeGroupId] = req.GetTidbNodeGroupId()
		details[auditlog.KeyEndpointService] = req.GetPrivateEndpointConnectionId()
		return details
	}
	defer func() {
		s.auditLogSender.SubmitAuditLog(ctx, auditlog.EventDeletePrivateEndpointService, 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")
	}

	var clusterId uint64
	clusterId, err = strconv.ParseUint(req.GetClusterId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cluster id")
	}

	var tidbGroupId uint64
	tidbGroupId, err = strconv.ParseUint(req.GetTidbNodeGroupId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid tidb group id")
	}

	_, err = s.dedicatedCli.DeletePrivateLinkEndpoint(ctx, &globaldedicatedv1.DeletePrivateLinkEndpointRequest{
		EndpointId:  req.GetPrivateEndpointConnectionId(),
		ClusterId:   clusterId,
		TidbGroupId: tidbGroupId,
	})
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}
