package globaldedicatedcli

import (
	"errors"
	"strings"
	"sync"
	"time"

	"github.com/sercand/kuberesolver/v5"
	tigrpcclient "github.com/tidbcloud/tiinf/transport/tigrpc/client"
	"google.golang.org/grpc"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/dedicatedglobal"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/config"
	grpcutil "github.com/tidbcloud/tidb-management-service/utilities/grpc"
)

type TimeoutCallOption struct {
	grpc.EmptyCallOption
}

func NewClient(cfg *config.DedicatedGlobalService) (dedicatedglobal.DedicatedGlobal, error) {
	if cfg == nil {
		return nil, errors.New("config is nil")
	}

	if cfg.Local {
		return &fakeImpl{
			clusters: make(map[uint64]*globaldedicatedv1.Cluster),
			lock:     &sync.RWMutex{},
		}, nil
	}
	var (
		conn *grpc.ClientConn
		err  error
	)

	interceptors := tigrpcclient.WithUnaryInterceptors(
		grpcutil.AppendRequestMetaInterceptor,
		grpcutil.ClientPayloadLogInterceptor,
		grpcutil.TimeoutInterceptor(time.Duration(cfg.WaitTimeoutInMilliSec)*time.Millisecond),
		// Some API are old core API. They don't return biz err as gRPC status error.
		// We need to convert them to grpc status error.
		grpcutil.CoreClientBaseRespToGrpcStatusErrorInterceptor,
	)

	opts := []tigrpcclient.Option{tigrpcclient.WithPrometheus(), tigrpcclient.WithTracing(), interceptors}
	if strings.HasPrefix(cfg.Url, "kubernetes://") {
		kuberesolver.RegisterInCluster()
		opts = append(opts, tigrpcclient.WithLoadBalance())
	}

	// More about timeout:
	// We have a server option to set a timeout on connection establishment: https://pkg.go.dev/google.golang.org/grpc#ConnectionTimeout
	//
	// We have ways to configure keepalive parameters on the server: https://pkg.go.dev/google.golang.org/grpc@v1.43.0/keepalive#ServerParameters
	//
	// And if we wish to set timeouts on a per-RPC level, you could register a server interceptor using https://pkg.go.dev/google.golang.org/grpc#UnaryInterceptor and create a new context in there with a different timeout and pass it to the handler.

	if conn, err = tigrpcclient.NewClientConn(cfg.Url, opts...); err != nil {
		return nil, err
	}

	clusterCli := globaldedicatedv1.NewClusterServiceClient(conn)
	vpcCli := globaldedicatedv1.NewVpcServiceClient(conn)
	spuCli := globaldedicatedv1.NewSpuServiceClient(conn)
	cmekCli := globaldedicatedv1.NewCustomerManagedEncryptionServiceClient(conn)
	regionCli := globaldedicatedv1.NewRegionServiceClient(conn)
	auditlogCli := globaldedicatedv1.NewDatabaseAuditLogServiceClient(conn)
	privateLinkCli := globaldedicatedv1.NewPrivateLinkConnectionServiceClient(conn)
	tidbGroupCli := globaldedicatedv1.NewTidbNodeGroupServiceClient(conn)
	compatCli := globaldedicatedv1.NewDataPlatformCompatServiceClient(conn)
	meteringCli := globaldedicatedv1.NewMeteringServiceClient(conn)
	privateLinkServiceMigrationCli := globaldedicatedv1.NewPrivateLinkServiceMigrationServiceClient(conn)
	migrationPrecheckCli := globaldedicatedv1.NewMigrationPrecheckServiceClient(conn)

	c := clientImpl{
		clusterCli,
		vpcCli,
		spuCli,
		cmekCli,
		regionCli,
		auditlogCli,
		privateLinkCli,
		tidbGroupCli,
		compatCli,
		meteringCli,
		privateLinkServiceMigrationCli,
		migrationPrecheckCli,
	}
	return &c, nil
}
