package mgmt

import (
	"context"
	"strconv"
	"sync"

	"github.com/tidbcloud/ecosystem-service/config"
	"github.com/tidbcloud/ecosystem-service/internal/service/grpc/mgmt/client"
	"github.com/tidbcloud/ecosystem-service/internal/utils"
	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/tracing"
	globalserverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	mgmtapi "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.opentelemetry.io/otel"
)

var Tracer = otel.Tracer("grpc-mgmt")

type MgmtSvc interface {
	GetCluster(ctx context.Context, clusterId uint64, view mgmtapi.ClusterFilter_ClusterView) (*mgmtapi.Cluster, error)
	GetClusterIncludeDedicated(ctx context.Context, clusterId uint64, view mgmtapi.ClusterFilter_ClusterView) (*mgmtapi.Cluster, error)
	ListClusters(ctx context.Context, clusterIds []uint64, view mgmtapi.ClusterFilter_ClusterView) (map[uint64]*mgmtapi.Cluster, error)
	CreateServerlessCluster(ctx context.Context, orgID, projectID uint64, email, name, provider, region string) (*mgmtapi.Cluster, error)
	CreateSqlUser(ctx context.Context, clusterId uint64, operatorUser, operatorCredential string, username, password string) error
}

type mgmtSvcImpl struct {
	mgmtClient   mgmtapi.ClusterServiceClient
	globalClient globalserverlessv1.SqlUserServiceClient
	encryptor    utils.Encryptor
}

var (
	mgmtSvcOnce sync.Once
	mgmtSvc     MgmtSvc
)

func NewMgmtSvc(conf *config.ConfigWrapper, encryptor utils.Encryptor) MgmtSvc {
	mgmtSvcOnce.Do(func() {
		mgmtSvc = &mgmtSvcImpl{
			mgmtClient:   client.GetMgmtSvcClient(conf),
			globalClient: client.GetServerlessGlobalClient(conf),
			encryptor:    encryptor,
		}

	})
	return mgmtSvc
}

func (m *mgmtSvcImpl) GetCluster(ctx context.Context, clusterId uint64, view mgmtapi.ClusterFilter_ClusterView) (cluster *mgmtapi.Cluster, err error) {
	ctx, span := Tracer.Start(ctx, "mgmtSvcImpl.GetCluster")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	req := &mgmtapi.ListClustersRequest{
		Filter: &mgmtapi.ClusterFilter{
			ClusterIds:  []uint64{clusterId},
			View:        view,
			ClusterType: mgmtapi.DeployType_DEPLOY_TYPE_SERVERLESS.Enum(),
		},
	}
	resp, err := m.mgmtClient.ListClusters(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	if len(resp.Clusters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster %d is not found", clusterId)
	}
	return resp.Clusters[0], nil
}

func (m *mgmtSvcImpl) GetClusterIncludeDedicated(ctx context.Context, clusterId uint64, view mgmtapi.ClusterFilter_ClusterView) (cluster *mgmtapi.Cluster, err error) {
	ctx, span := Tracer.Start(ctx, "mgmtSvcImpl.GetClusterIncludeDedicated")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	req := &mgmtapi.ListClustersRequest{
		Filter: &mgmtapi.ClusterFilter{
			ClusterIds: []uint64{clusterId},
			View:       view,
		},
	}
	resp, err := m.mgmtClient.ListClusters(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	if len(resp.Clusters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster %d is not found", clusterId)
	}
	return resp.Clusters[0], nil
}

func (m *mgmtSvcImpl) ListClusters(ctx context.Context, clusterIds []uint64, view mgmtapi.ClusterFilter_ClusterView) (r map[uint64]*mgmtapi.Cluster, err error) {
	ctx, span := Tracer.Start(ctx, "mgmtSvcImpl.ListClusters")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	req := &mgmtapi.ListClustersRequest{
		Filter: &mgmtapi.ClusterFilter{
			ClusterIds:  clusterIds,
			View:        view,
			ClusterType: mgmtapi.DeployType_DEPLOY_TYPE_SERVERLESS.Enum(),
		},
	}
	resp, err := m.mgmtClient.ListClusters(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	clusterMap := make(map[uint64]*mgmtapi.Cluster)
	for _, cluster := range resp.Clusters {
		clusterMap[cluster.ClusterId] = cluster
	}
	return clusterMap, nil
}

func (m *mgmtSvcImpl) CreateServerlessCluster(ctx context.Context, orgID, projectID uint64, email, name, provider, region string) (cluster *mgmtapi.Cluster, err error) {
	ctx, span := Tracer.Start(ctx, "mgmtSvcImpl.CreateServerlessCluster")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	req := &mgmtapi.CreateServerlessClusterRequest{
		OrganizationId: orgID,
		ProjectId:      projectID,
		ClusterName:    name,
		CloudProvider:  provider,
		Region:         region,
		CreatedBy:      email,
	}
	resp, err := m.mgmtClient.CreateServerlessCluster(ctx, req)
	if err != nil {
		return nil, utils.WrapGrpcError(err)
	}
	return resp, nil
}

func (m *mgmtSvcImpl) CreateSqlUser(ctx context.Context, clusterId uint64, operatorUser, operatorCredential string, username, password string) (err error) {
	ctx, span := Tracer.Start(ctx, "mgmtSvcImpl.CreateSqlUser")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	encPassword, err := m.encryptor.EncryptBase64(password)
	if err != nil {
		return err
	}

	req := &globalserverlessv1.SqlUserServiceCreateSqlUserRequest{
		Operator: &globalserverlessv1.SqlUserOperator{
			Username:   operatorUser,
			AuthMethod: globalserverlessv1.SqlUser_AUTH_METHOD_TOKEN,
			Credential: operatorCredential,
		},
		ClusterId:   strconv.FormatUint(clusterId, 10),
		UserType:    globalserverlessv1.SqlUser_TYPE_USER_DEFINED,
		AuthMethod:  globalserverlessv1.SqlUser_AUTH_METHOD_PASSWORD,
		Username:    username,
		Credential:  &encPassword,
		BuiltinRole: globalserverlessv1.SqlUser_BUILTIN_ROLE_USER_ADMIN,
	}

	_, err = m.globalClient.CreateSqlUser(ctx, req)
	if err != nil {
		return utils.WrapGrpcError(err)
	}
	return nil
}
