package grpcli

import (
	"context"
	"fmt"
	"sync"

	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
	"github.com/tidbcloud/metrics-server/central/config"
	"github.com/tidbcloud/metrics-server/central/utils"
	"github.com/tidbcloud/metrics-server/idl/pbgen/proto/core"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/logid"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
	"google.golang.org/grpc"
)

var (
	coreCli     core.CoreServiceClient
	coreSvcOnce sync.Once
)

func getCoreSvcClient() core.CoreServiceClient {
	coreSvcOnce.Do(func() {
		conn, err := grpc.DialContext(
			context.Background(),
			config.Global().GrpcConfig.CoreSvcAddr,
			grpc.WithInsecure(),
			grpc.WithChainUnaryInterceptor(
				logid.UnaryClientInterceptor(),
				otelgrpc.UnaryClientInterceptor(),
				grpc_prometheus.UnaryClientInterceptor,
			),
		)
		if err != nil {
			panic(fmt.Sprintf("dial core-svc: %s", err))
		}

		coreCli = core.NewCoreServiceClient(conn)
	})

	return coreCli
}

func CreateClusterSnapshot(ctx context.Context, clusterID uint64) (uint64, bizerr.BizError) {
	client := getCoreSvcClient()

	req := &core.CreateClusterSnapshotReq{
		ClusterId: clusterID,
	}
	resp, e := client.CreateClusterSnapshot(ctx, req)
	if e != nil {
		return 0, bizerr.WrapGrpcError(e, "CreateClusterSnapshot")
	}
	if resp.BaseResp.ErrCode != 0 {
		return 0, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.SnapshotId, nil
}

func ListCloudProviders(ctx context.Context, req *core.ListCloudProvidersReq) (*core.ListCloudProvidersResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.ListCloudProviders(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "ListCloudProviders")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func ListNodeProfiles4Tenant(ctx context.Context, req *core.ListNodeProfiles4TenantReq) (*core.ListNodeProfiles4TenantResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.ListNodeProfiles4Tenant(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "ListNodeProfiles4Tenant")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func CreateCluster(ctx context.Context, req *core.CreateClusterReq) (*core.CreateClusterResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.CreateCluster(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "CreateCluster")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func ScaleCluster(ctx context.Context, req *core.ScaleClusterReq) (*core.ScaleClusterResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.ScaleCluster(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "ScaleCluster")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func CreateClusterConn(ctx context.Context, clusterId uint64, connType core.ClusterConnectionType) bizerr.BizError {
	client := getCoreSvcClient()

	req := &core.CreateClusterConnReq{
		ClusterId: clusterId,
		ConnType:  connType,
	}
	resp, e := client.CreateClusterConn(ctx, req)
	if e != nil {
		return bizerr.WrapGrpcError(e, "CreateClusterConn")
	}
	if resp.BaseResp.ErrCode != 0 {
		return bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return nil
}

func CreateNetworkPeer(ctx context.Context, peering *core.NetworkPeer, creatorUid uint64) (uint64, bizerr.BizError) {
	client := getCoreSvcClient()

	req := &core.CreateNetworkPeerReq{
		Peering:        peering,
		OperatorUserId: creatorUid,
		BaseReq:        nil,
	}
	resp, e := client.CreateNetworkPeer(ctx, req)
	if e != nil {
		return 0, bizerr.WrapGrpcError(e, "CreateNetworkPeer")
	}
	if resp.BaseResp.ErrCode != 0 {
		return 0, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp.PeeringId, nil
}

func DeleteNetworkPeer(ctx context.Context, cloudProvider string, peeringId, userId uint64) bizerr.BizError {
	client := getCoreSvcClient()

	req := &core.DeleteNetworkPeerReq{
		PeeringId:      peeringId,
		CloudProvider:  cloudProvider,
		OperatorUserId: userId,
		BaseReq:        nil,
	}
	resp, e := client.DeleteNetworkPeer(ctx, req)
	if e != nil {
		return bizerr.WrapGrpcError(e, "DeleteNetworkPeer")
	}
	if resp.BaseResp.ErrCode != 0 {
		return bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return nil
}

func GetNetworkPeer(ctx context.Context, cloudProvider string, id uint64) (*core.NetworkPeer, bizerr.BizError) {
	client := getCoreSvcClient()

	req := &core.ListNetworkPeerReq{
		Filter: &core.NetworkPeerFilter{
			CloudProvider: cloudProvider,
			PeeringIds:    []uint64{id},
		},
		Pager:   nil,
		Sorter:  nil,
		BaseReq: nil,
	}
	resp, e := client.ListNetworkPeer(ctx, req)
	if e != nil {
		return nil, bizerr.WrapGrpcError(e, "CreateNetworkPeer")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}

	if len(resp.PeeringList) != 1 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "not found, cloudProvider=%s,id=%d", cloudProvider, id)
	}
	peering := resp.PeeringList[0]
	return peering, nil
}

func ListNetworkPeer(ctx context.Context, filter *core.NetworkPeerFilter, pager *base.Pager, sorter *base.Sorter) ([]*core.NetworkPeer, bizerr.BizError) {
	client := getCoreSvcClient()

	req := &core.ListNetworkPeerReq{
		Filter:  filter,
		Pager:   pager,
		Sorter:  sorter,
		BaseReq: nil,
	}
	resp, e := client.ListNetworkPeer(ctx, req)
	if e != nil {
		return nil, bizerr.WrapGrpcError(e, "ListNetworkPeer")
	}
	if resp.BaseResp.ErrCode != 0 {
		return nil, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}

	return resp.PeeringList, nil
}

func CalcClusterFee(ctx context.Context, req *core.CalcClusterFeeReq) (*core.CalcClusterFeeResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.CalcClusterFee(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "CalcClusterFee")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func RestoreCluster(ctx context.Context, req *core.RestoreClusterReq) (*core.RestoreClusterResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.RestoreCluster(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "RestoreCluster")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func ListClusters(ctx context.Context, req *core.ListClustersReq) (*core.ListClustersResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.ListClusters(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "ListClusters")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func GetCluster(ctx context.Context, tenantId, projectId, clusterId uint64) (*core.Cluster, bizerr.BizError) {
	filter := &core.ClusterFilter{
		TenantIds:       []uint64{tenantId},
		ProjectIds:      []uint64{projectId},
		ClusterIds:      []uint64{clusterId},
		ExcludeStatuses: []core.ClusterStatus{core.ClusterStatus_CLUSTER_STATUS_CLEARED},
		Deleted:         utils.BoolPtr(false),
		IsDevTier:       utils.BoolPtr(false),
	}
	listReq := &core.ListClustersReq{
		Filter:  filter,
		BaseReq: nil,
	}
	listResp, err := ListClusters(ctx, listReq)
	if err != nil {
		return nil, err
	}
	cluster, ok := listResp.Clusters[clusterId]
	if !ok {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster is not found,id=%d", clusterId)
	}
	return cluster, nil
}

func CountClusters(ctx context.Context, req *core.CountClustersReq) (*core.CountClustersResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.CountClusters(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "CountClusters")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func CreateReplication(ctx context.Context, req *core.CreateReplicationReq) (*core.CreateReplicationResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.CreateReplication(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "CreateReplication")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func ListBackupsOfCluster(ctx context.Context, clusterId uint64) (*core.ListBackupsOfClusterResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.ListBackupsOfCluster(ctx, &core.ListBackupsOfClusterReq{ClusterId: clusterId})
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "ListBackupsOfCluster")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}

func ListRegionCIDROfProject(ctx context.Context, req *core.ListRegionCIDROfProjectReq) (*core.ListRegionCIDROfProjectResp, bizerr.BizError) {
	client := getCoreSvcClient()

	resp, e := client.ListRegionCIDROfProject(ctx, req)
	if e != nil {
		return resp, bizerr.WrapGrpcError(e, "ListRegionCIDROfProject")
	}
	if resp.BaseResp.ErrCode != 0 {
		return resp, bizerr.Restore(bizerr.ErrCode(resp.BaseResp.ErrCode), resp.BaseResp.ErrMsg)
	}
	return resp, nil
}
