package grpcli

import (
	"context"
	"strconv"

	"github.com/samber/lo"
	"github.com/tidbcloud/config-center/proto/config_center"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.uber.org/zap"
	"google.golang.org/grpc/status"
)

/*
ConfigCenterGrpcClient is a wrapper for config center grpc request

Config center has 3 main concepts: component, configuration file, cluster, and they all have real entity in the service.

Component represents a business component which associates a kernel tool, and it has version and corresponding image.
For example, backup component contains br tool, and cdc component contains cdc tool,
and they all have version and image.

Configuration file represents a configuration file that a kernel tool needs,
and a kernel tool can have multiple configuration files. Configuration file and component has no direct relationship.

Cluster represents a business cluster, like tidb cluster and ticdc cluster. It can contain multiple configuration files
and multiple components, depending on business requirements. For example, we can call GetClusterConfigurationFile to
add a configuration file to a cluster and call UpdateClusterComponent with AllowMissing=true to add a component to
a cluster.

There is another concept cluster meta. It's just a wrapper that can make business to update configuration files and
components of a cluster simultaneously.
*/
type ConfigCenterGrpcClient interface {
	GetClusterMeta(
		ctx context.Context, req *config_center.GetClusterMetaRequest,
	) (*config_center.ClusterMeta, bizerr.BizError)
	UpdateClusterMeta(
		ctx context.Context, req *config_center.UpdateClusterMetaRequest,
	) (*config_center.UpdateClusterMetaResponse, bizerr.BizError)
	GetClusterConfigurationFile(
		ctx context.Context, req *config_center.GetClusterConfigurationFileRequest,
	) (*config_center.ConfigurationFile, bizerr.BizError)
	GetClusterComponent(
		ctx context.Context, req *config_center.GetClusterComponentRequest,
	) (*config_center.ClusterComponent, bizerr.BizError)
	ListClusterComponents(
		ctx context.Context, clusterId uint64,
	) (map[string]*config_center.ClusterComponent, bizerr.BizError)
	CreateClusterConfigurationFile(
		ctx context.Context, req *config_center.CreateClusterConfigurationFileRequest,
	) (*config_center.ConfigurationFile, bizerr.BizError)
	UpdateClusterConfigurationFile(
		ctx context.Context, req *config_center.UpdateClusterConfigurationFileRequest,
	) (*config_center.ConfigurationFile, bizerr.BizError)
	UpdateClusterComponent(
		ctx context.Context, req *config_center.UpdateClusterComponentRequest,
	) (*config_center.ClusterComponent, bizerr.BizError)
	SearchDefaultConfigurationTemplate(
		ctx context.Context, req *config_center.SearchDefaultConfigurationTemplateRequest,
	) (*config_center.ConfigurationTemplate, bizerr.BizError)
	GetTenantInitialCustomParams(
		ctx context.Context, req *config_center.GetTenantInitialCustomParamsRequest,
	) (*config_center.TenantInitialCustomPatams, bizerr.BizError)
	GetDefaultVersion(
		ctx context.Context, in *config_center.GetDefaultVersionRequest,
	) (*config_center.DefaultVersion, bizerr.BizError)
	GetComponentImage(
		ctx context.Context, req *config_center.GetComponentImageRequest,
	) (*config_center.ComponentImage, bizerr.BizError)
}

type configCenterGrpcClientImpl struct {
	configCli config_center.ConfigCenterServiceClient
}

func NewConfigCenterGrpcClient(ctx context.Context, addr string) ConfigCenterGrpcClient {
	logger := log.GetLogger(ctx)
	logger.Info("initializing config-center client", zap.String("addr", addr))

	conn := MustBuildGrpcClientConnection(ctx, addr)
	configCli := config_center.NewConfigCenterServiceClient(conn)
	return &configCenterGrpcClientImpl{
		configCli: configCli,
	}
}

func (m *configCenterGrpcClientImpl) GetClusterMeta(
	ctx context.Context, req *config_center.GetClusterMetaRequest,
) (*config_center.ClusterMeta, bizerr.BizError) {
	clusterMeta, err := m.configCli.GetClusterMeta(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("get cluster meta successfully",
		zap.Any("req", req), zap.Any("resp", clusterMeta))
	return clusterMeta, nil
}

func (m *configCenterGrpcClientImpl) UpdateClusterMeta(
	ctx context.Context, req *config_center.UpdateClusterMetaRequest,
) (*config_center.UpdateClusterMetaResponse, bizerr.BizError) {
	resp, err := m.configCli.UpdateClusterMeta(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("update cluster meta successfully",
		zap.Any("req", req), zap.Any("resp", resp))
	return resp, nil
}

func (m *configCenterGrpcClientImpl) GetClusterConfigurationFile(
	ctx context.Context, req *config_center.GetClusterConfigurationFileRequest,
) (*config_center.ConfigurationFile, bizerr.BizError) {
	config, err := m.configCli.GetClusterConfigurationFile(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("get cluster configuration file successfully",
		zap.Any("req", req), zap.Any("resp", config))
	return config, nil
}

func (m *configCenterGrpcClientImpl) GetClusterComponent(
	ctx context.Context, req *config_center.GetClusterComponentRequest,
) (*config_center.ClusterComponent, bizerr.BizError) {
	component, err := m.configCli.GetClusterComponent(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("get cluster component successfully",
		zap.Any("req", req), zap.Any("resp", component))
	return component, nil
}

func (cc *configCenterGrpcClientImpl) ListClusterComponents(
	ctx context.Context, clusterId uint64,
) (map[string]*config_center.ClusterComponent, bizerr.BizError) {
	resp, err := cc.configCli.ListClusterComponents(ctx, &config_center.ListClusterComponentsRequest{
		ClusterType: config_center.ClusterType_DEDICATED_TIDB,
		ClusterId:   strconv.FormatUint(clusterId, 10),
	})
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}

	return lo.SliceToMap(resp.Components, func(each *config_center.ClusterComponent) (string, *config_center.ClusterComponent) {
		return each.ComponentName, each
	}), nil
}

func (m *configCenterGrpcClientImpl) CreateClusterConfigurationFile(
	ctx context.Context, req *config_center.CreateClusterConfigurationFileRequest,
) (*config_center.ConfigurationFile, bizerr.BizError) {
	config, err := m.configCli.CreateClusterConfigurationFile(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("create cluster configuration file successfully",
		zap.Any("req", req), zap.Any("resp", config))
	return config, nil
}

func (m *configCenterGrpcClientImpl) UpdateClusterConfigurationFile(
	ctx context.Context, req *config_center.UpdateClusterConfigurationFileRequest,
) (*config_center.ConfigurationFile, bizerr.BizError) {
	config, err := m.configCli.UpdateClusterConfigurationFile(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("update cluster configuration file successfully",
		zap.Any("req", req), zap.Any("resp", config))
	return config, nil
}

func (m *configCenterGrpcClientImpl) UpdateClusterComponent(
	ctx context.Context, req *config_center.UpdateClusterComponentRequest,
) (*config_center.ClusterComponent, bizerr.BizError) {
	component, err := m.configCli.UpdateClusterComponent(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("update cluster component successfully",
		zap.Any("req", req), zap.Any("resp", component))
	return component, nil
}

func (m *configCenterGrpcClientImpl) SearchDefaultConfigurationTemplate(
	ctx context.Context, req *config_center.SearchDefaultConfigurationTemplateRequest,
) (*config_center.ConfigurationTemplate, bizerr.BizError) {
	template, err := m.configCli.SearchDefaultConfigurationTemplate(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("search default configuration template successfully",
		zap.Any("req", req), zap.Any("resp", template))
	return template, nil
}

func (m *configCenterGrpcClientImpl) GetTenantInitialCustomParams(
	ctx context.Context, req *config_center.GetTenantInitialCustomParamsRequest,
) (*config_center.TenantInitialCustomPatams, bizerr.BizError) {
	params, err := m.configCli.GetTenantInitialCustomParams(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("get tenant initial custom params successfully",
		zap.Any("req", req), zap.Any("resp", params))
	return params, nil
}

func (m *configCenterGrpcClientImpl) GetDefaultVersion(
	ctx context.Context, in *config_center.GetDefaultVersionRequest,
) (*config_center.DefaultVersion, bizerr.BizError) {
	res, err := m.configCli.GetDefaultVersion(ctx, in)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("get default version successfully",
		zap.Any("req", in), zap.Any("resp", res))
	return res, nil
}

func (m *configCenterGrpcClientImpl) GetComponentImage(
	ctx context.Context, req *config_center.GetComponentImageRequest,
) (*config_center.ComponentImage, bizerr.BizError) {
	res, err := m.configCli.GetComponentImage(ctx, req)
	if err != nil {
		return nil, convertConfigCenterBizError(err)
	}
	log.GetLogger(ctx).Info("get component image successfully",
		zap.Any("req", req), zap.Any("resp", res))
	return res, nil
}

func convertConfigCenterBizError(err error) bizerr.BizError {
	if s, ok := status.FromError(err); ok {
		return GrpcStatus2BizError(s)
	} else {
		return bizerr.New(bizerr.ErrCodeGRPC, err.Error())
	}
}
