// Package worker_privatelink add by blacknc <blacknc@163.com> at 2023/09/22 17:47:28
package worker_privatelink

import (
	"context"
	"dataservices/commons/contextvar"
	"dataservices/commons/grpc"
	"dataservices/commons/http"
	"dataservices/commons/logger"
	"dataservices/ddb"
	"dataservices/internal/dto/common"
	"dataservices/internal/entity"
	"dataservices/internal/repository_ddb"
	service "dataservices/internal/service/central"
	commonSrv "dataservices/internal/service/common"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	"github.com/tidbcloud/tiinf/errs"
	"go.uber.org/zap"
)

func PrivatelinkTaskHandler(ctx context.Context, ct *entity.CommonTask) (err error) {
	ctx = contextvar.WithLoggerField(ctx, zap.Uint64("task_id", ct.ID), zap.Uint64("cluster_id", ct.EventID))
	startTime := time.Now()
	logger.Info(ctx, "privatelink task start...")
	defer func() {
		logger.LogWithError(ctx, err, "privatelink task completed", zap.Duration("cost", time.Since(startTime)))
	}()

	// Analyze Cluster & Privatelink Status
	// load cluster
	clusterID := ct.EventID
	var content common.PrivatelinkTaskContent
	if err = json.Unmarshal([]byte(ct.Content), &content); err != nil {
		return errs.NewInternalServerError("failed to parse privatelink task content", err)
	}
	isDeleteCluster := content.IsDeleteCluster
	projectID := content.ProjectID
	defer func() {
		contentStr, _ := json.Marshal(content)
		ct.Content = string(contentStr)
	}()

	client := ddb.Client()
	userCluster, err := repository_ddb.GetUserClusterInfo(ctx, client, uint64(projectID), clusterID)
	if err != nil && !errs.IsError(err, errs.ErrNotFound) {
		return err
	}

	// is cluste exists
	clusterInfo, err := grpc.GetClusterWithoutPermCheck(ctx, clusterID)
	if err != nil {
		if !errs.IsError(err, errs.ErrNotFound) {
			return err
		}
		// need delete private link
		content.Action = "delete"
		return deletePrivateLink(ctx, userCluster, true)
	}

	// is aws dedicated cluster
	if !service.IsAWSDedicatedCluster(clusterInfo) {
		return nil
	}

	// is support shadow account
	if !service.IsSupportShadowAccount(clusterInfo) {
		content.Action = "delete"
		return deletePrivateLink(ctx, userCluster, isDeleteCluster)
	}

	// is hit org whitelist
	if ok, err := commonSrv.InDedicatedOrgWhitelist(ctx, clusterInfo.TenantId); err != nil {
		return err
	} else if !ok {
		content.Action = "delete"
		return deletePrivateLink(ctx, userCluster, isDeleteCluster)
	}

	// check private link service status
	linkServiceResp, err := grpc.GetPrivateLinkService(ctx, &core.GetPrivateLinkServiceReq{
		OrgId:     clusterInfo.TenantId,
		ProjectId: clusterInfo.ProjectId,
		ClusterId: clusterInfo.Id,
	})
	if err != nil {
		logger.LogWithError(ctx, err, "GetPrivateLinkService failed", zap.Uint64("cluster_id", clusterID))
		return err
	}
	if linkServiceResp.PrivatelinkService == nil {
		logger.Error(ctx, "GetPrivateLinkService got empty service endpoint", zap.Uint64("cluster_id", clusterID))
		return nil
	}
	if linkServiceResp.PrivatelinkService.Status != core.PrivateLinkService_ACTIVE {
		switch linkServiceResp.PrivatelinkService.Status {
		case core.PrivateLinkService_CREATING:
			errmsg := fmt.Sprintf("private link service is creating, will try again later, cluster_id:%d", clusterID)
			logger.Warn(ctx, errmsg)
			return errs.NewBadRequest(errmsg, nil)
		case core.PrivateLinkService_DELETING:
			logger.Info(ctx, "private link service is deleting, will delete private link endpoint", zap.Uint64("cluster_id", clusterID))
			content.Action = "delete"
			return deletePrivateLink(ctx, userCluster, isDeleteCluster)
		case core.PrivateLinkService_NOT_CONFIGURE:
			logger.Info(ctx, "private link service is not configured, will delete private link endpoint", zap.Uint64("cluster_id", clusterID))
			content.Action = "delete"
			return deletePrivateLink(ctx, userCluster, isDeleteCluster)
		default:
			errmsg := fmt.Sprintf("unsupported private link service status, will try again later, cluster_id:%d, status:%d", clusterID, linkServiceResp.PrivatelinkService.Status)
			logger.Warn(ctx, errmsg)
			return errs.NewInternalServerError(errmsg, nil)
		}
	}
	serviceName := linkServiceResp.PrivatelinkService.Name

	if userCluster == nil {
		userCluster = &entity.UserClusterInfo{}
		userCluster.OrgId = clusterInfo.TenantId
		userCluster.ProjectId = clusterInfo.ProjectId
		userCluster.ClusterId = clusterInfo.Id
		userCluster.Cloud = clusterInfo.CloudProviderName
		userCluster.Region = clusterInfo.RegionName
		userCluster.IsDedicate = true
	}
	userCluster.EndpointService = linkServiceResp.PrivatelinkService.Name
	userCluster.AZ = strings.Join(linkServiceResp.PrivatelinkService.AzIds, ",")
	userCluster.Port = uint64(linkServiceResp.PrivatelinkService.Port)
	if err := repository_ddb.PutUserClusterInfo(ctx, client, userCluster); err != nil {
		return errs.NewInternalServerError("failed to put user cluster info", err)
	}
	// wait PutUserClusterInfo sync data to region complete
	time.Sleep(time.Second * 3)

	content.Action = "create"
	return createPrivateLink(ctx, clusterInfo, serviceName)
}

func createPrivateLink(ctx context.Context, clusterInfo *core.Cluster, serviceName string) (err error) {
	logger.Info(ctx, "createPrivateLink begin", zap.String("service_name", serviceName))
	defer func() {
		logger.LogWithError(ctx, err, "createPrivateLink complete")
	}()
	var (
		orgID      = clusterInfo.TenantId
		projectID  = clusterInfo.ProjectId
		clusterID  = clusterInfo.Id
		regionName = clusterInfo.RegionName
	)
	resp, err := http.CreatePrivateLink(ctx, projectID, clusterID, regionName, serviceName)
	if err != nil {
		return err
	}

	// accept dns
	if resp.PrivateDNS == "" {
		if _, err := grpc.CreatePrivateLinkEndpoint(ctx, &core.CreatePrivateLinkEndpointReq{
			OrgId:     orgID,
			ProjectId: projectID,
			ClusterId: clusterID,
			PrivatelinkEndpoint: &core.PrivateLinkEndpoint{
				EndpointId: resp.VpcEndpointId,
				Type:       core.PrivateLinkEndpoint_TYPE_DBAAS,
			},
		}); err != nil {
			logger.Error(ctx, "CreatePrivateLinkEndpoint failed", zap.Uint64("cluster_id", clusterID), zap.Error(err))
			return err
		}
	}
	return nil
}

func deletePrivateLink(ctx context.Context, userCluster *entity.UserClusterInfo, deleteCluster bool) (err error) {
	logger.Info(ctx, "deletePrivateLink begin", zap.Bool("dryrun", userCluster == nil), zap.Bool("delete_cluster", deleteCluster))
	defer logger.LogWithError(ctx, err, "deletePrivateLink complete")
	if userCluster == nil {
		return nil
	}
	var (
		projectID  = userCluster.ProjectId
		clusterID  = userCluster.ClusterId
		regionName = userCluster.Region
	)
	return http.DeletePrivateLink(ctx, projectID, clusterID, regionName, deleteCluster)
}
