package mgmt

import (
	"context"
	"fmt"
	"strings"
	"sync"
	"time"

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

	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
	"github.com/tidbcloud/tidb-management-service/tempo-wrapper/internal/config"
	grpcutil "github.com/tidbcloud/tidb-management-service/utilities/grpc"
)

var (
	mgmtDedicatedClusterSvcOnce sync.Once
	mgmtDedicatedClusterSvcCli  openapidedicatedv1beta1.ClusterServiceClient

	mgmtDedicatedRegionSvcOnce sync.Once
	mgmtDedicatedRegionSvcCli  openapidedicatedv1beta1.RegionServiceClient
)

func getMgmtDedicatedClusterSvcClient() openapidedicatedv1beta1.ClusterServiceClient {
	mgmtDedicatedClusterSvcOnce.Do(func() {
		timeout := time.Duration(config.Config().Grpc.Timeout) * time.Second
		ctx, cancel := context.WithTimeout(context.Background(), timeout)
		defer cancel()

		addr := config.Config().Grpc.MgmtSvcAddr
		interceptors := []grpc.UnaryClientInterceptor{
			grpcutil.TimeoutInterceptor(timeout),
			grpcutil.ClientPayloadLogInterceptor,
		}
		opts := []tigrpc.Option{
			tigrpc.WithDefault(),
			tigrpc.WithUnaryInterceptors(interceptors...),
		}
		if strings.HasPrefix(addr, "kubernetes://") {
			kuberesolver.RegisterInCluster()
			opts = append(opts, tigrpc.WithLoadBalance())
		}

		conn, err := tigrpc.NewClientContext(ctx, addr, opts...)
		if err != nil {
			panic(fmt.Sprintf("dial mgmt-Dedicated-svc: %s", err))
		}
		mgmtDedicatedClusterSvcCli = openapidedicatedv1beta1.NewClusterServiceClient(conn)
	})

	return mgmtDedicatedClusterSvcCli
}

func getMgmtDedicatedRegionSvcClient() openapidedicatedv1beta1.RegionServiceClient {
	mgmtDedicatedRegionSvcOnce.Do(func() {
		timeout := time.Duration(config.Config().Grpc.Timeout) * time.Second
		ctx, cancel := context.WithTimeout(context.Background(), timeout)
		defer cancel()

		addr := config.Config().Grpc.MgmtSvcAddr
		interceptors := []grpc.UnaryClientInterceptor{
			grpcutil.TimeoutInterceptor(timeout),
			grpcutil.ClientPayloadLogInterceptor,
		}
		opts := []tigrpc.Option{
			tigrpc.WithDefault(),
			tigrpc.WithUnaryInterceptors(interceptors...),
		}
		if strings.HasPrefix(addr, "kubernetes://") {
			kuberesolver.RegisterInCluster()
			opts = append(opts, tigrpc.WithLoadBalance())
		}

		conn, err := tigrpc.NewClientContext(ctx, addr, opts...)
		if err != nil {
			panic(fmt.Sprintf("dial mgmt-Dedicated-svc: %s", err))
		}
		mgmtDedicatedRegionSvcCli = openapidedicatedv1beta1.NewRegionServiceClient(conn)
	})

	return mgmtDedicatedRegionSvcCli
}

func ListDedicatedRegions(
	ctx context.Context,
	req *openapidedicatedv1beta1.ListRegionsRequest,
) (*openapidedicatedv1beta1.ListRegionsResponse, error) {
	client := getMgmtDedicatedRegionSvcClient()

	req.PageSize = defaultListClusterPageSize
	resp, err := client.ListRegions(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func ListNodeSpecs(
	ctx context.Context,
	req *openapidedicatedv1beta1.ListNodeSpecsRequest,
) (*openapidedicatedv1beta1.ListNodeSpecsResponse, error) {
	client := getMgmtDedicatedRegionSvcClient()

	// need to set default page size
	req.PageSize = defaultListClusterPageSize
	resp, err := client.ListNodeSpecs(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func ListDedicatedClusters(
	ctx context.Context,
	req *openapidedicatedv1beta1.ListClustersRequest,
) (*openapidedicatedv1beta1.ListClustersResponse, error) {
	client := getMgmtDedicatedClusterSvcClient()

	// wrapper need list all clusters for internal pagination
	// need to set default page size
	req.PageSize = defaultListClusterPageSize
	resp, err := client.ListClusters(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func GetDedicatedCluster(
	ctx context.Context,
	req *openapidedicatedv1beta1.GetClusterRequest,
) (*openapidedicatedv1beta1.Cluster, error) {
	client := getMgmtDedicatedClusterSvcClient()

	resp, err := client.GetCluster(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func DeleteDedicatedCluster(
	ctx context.Context,
	req *openapidedicatedv1beta1.DeleteClusterRequest,
) (*openapidedicatedv1beta1.Cluster, error) {
	client := getMgmtDedicatedClusterSvcClient()
	resp, err := client.DeleteCluster(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func CreateDedicatedCluster(
	ctx context.Context,
	req *openapidedicatedv1beta1.CreateClusterRequest,
) (*openapidedicatedv1beta1.Cluster, error) {
	client := getMgmtDedicatedClusterSvcClient()
	resp, err := client.CreateCluster(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func PauseDedicatedCluster(
	ctx context.Context,
	req *openapidedicatedv1beta1.PauseClusterRequest,
) (*openapidedicatedv1beta1.PauseClusterResponse, error) {
	client := getMgmtDedicatedClusterSvcClient()
	resp, err := client.PauseCluster(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func ResumeDedicatedCluster(
	ctx context.Context,
	req *openapidedicatedv1beta1.ResumeClusterRequest,
) (*openapidedicatedv1beta1.ResumeClusterResponse, error) {
	client := getMgmtDedicatedClusterSvcClient()
	resp, err := client.ResumeCluster(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func UpdateDedicatedCluster(
	ctx context.Context,
	req *openapidedicatedv1beta1.UpdateClusterRequest,
) (*openapidedicatedv1beta1.Cluster, error) {
	client := getMgmtDedicatedClusterSvcClient()
	resp, err := client.UpdateCluster(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func GetDedicatedClusterNodeInfo(
	ctx context.Context,
	req *openapidedicatedv1beta1.ListNodeInstancesRequest,
) (*openapidedicatedv1beta1.ListNodeInstancesResponse, error) {
	client := getMgmtDedicatedClusterSvcClient()
	resp, err := client.ListNodeInstances(ctx, req)
	// need to set default page size
	req.PageSize = defaultListClusterPageSize
	if err != nil {
		return resp, err
	}
	return resp, nil
}

func SetDedicatedRootPassword(
	ctx context.Context,
	req *openapidedicatedv1beta1.ResetRootPasswordRequest,
) (*openapidedicatedv1beta1.ResetRootPasswordResponse, error) {
	client := getMgmtDedicatedClusterSvcClient()
	resp, err := client.ResetRootPassword(ctx, req)
	if err != nil {
		return resp, err
	}
	return resp, nil
}
