package grpcli

import (
	"context"

	"github.com/tidbcloud/dataflow-service-ng/pkg/log"
	"github.com/tidbcloud/pkgv2/bizerr"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	serverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/regional/serverless/v1"
	v1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	mgmt "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	tigrpc "github.com/tidbcloud/tiinf/transport/tigrpc/client"
	"go.uber.org/zap"
	"google.golang.org/grpc"
)

var DeletedClusterStatus = []v1beta1.ClusterState{
	v1beta1.ClusterState_DELETED, v1beta1.ClusterState_DELETING,
}

type MgmtSvcGrpcClient interface {
	ExportClient() ExportGrpcClient

	GetServerlessRegional(ctx context.Context, clusterId uint64) (*serverlessv1.ServerlessRegionalServiceClusterInfo, bizerr.BizError)

	UpdateServerlessRegionalAnnotations(ctx context.Context, clusterId uint64, annotations map[string]string) bizerr.BizError

	GetClusterByID(ctx context.Context, clusterID uint64) (*mgmt.Cluster, bizerr.BizError)

	RestoreDedicatedCluster(ctx context.Context, req *mgmt.RestoreDedicatedClusterRequest) (*mgmt.Cluster, bizerr.BizError)

	ListClusters(ctx context.Context, in *mgmt.ListClustersRequest, opts ...grpc.CallOption) (*mgmt.ListClustersResponse, bizerr.BizError)

	SetClusterCustomStatus(ctx context.Context, in *mgmt.SetClusterCustomStatusRequest, opts ...grpc.CallOption) (*mgmt.ClusterCustomStatus, bizerr.BizError)

	DeleteClusterCustomStatus(ctx context.Context, in *mgmt.DeleteClusterCustomStatusRequest, opts ...grpc.CallOption) bizerr.BizError

	IsClusterCustomStatusExisted(ctx context.Context, in *mgmt.IsClusterCustomStatusExistedRequest, opts ...grpc.CallOption) (*mgmt.IsClusterCustomStatusExistedResponse, bizerr.BizError)

	GetResourcePoolByID(ctx context.Context, resourcePoolID uint64) (*globalngv1.ResourcePool, bizerr.BizError)
	GetResourcePoolIncludingDeleted(ctx context.Context, resourcePoolID uint64) (*globalngv1.ResourcePool, bizerr.BizError)
}

type mgmtSvcGrpcClient struct {
	MgmtSvcGrpcClient
	addr                string
	client              mgmt.ClusterServiceClient
	regionClient        mgmt.RegionServiceClient
	clusterStatusClient mgmt.ClusterCustomStatusServiceClient
	resourcePoolClient  mgmt.ResourcePoolServiceClient
	exportClient        ExportGrpcClient
}

func NewMgmtSvcGrpcClient(ctx context.Context, addr string) MgmtSvcGrpcClient {
	logger := log.GetLogger(ctx)
	conn, err := tigrpc.NewClientConn(addr, tigrpc.WithDefault())
	logger.Info("Initializing mgmt service client", zap.Any("err", err), zap.String("addr", addr))
	if err != nil {
		logger.Fatal("initialize client failed", zap.String("addr", addr), zap.Error(err))
	}
	return newMgmtSvcGrpcClient(addr, conn)
}

func newMgmtSvcGrpcClient(addr string, conn grpc.ClientConnInterface) MgmtSvcGrpcClient {
	return &mgmtSvcGrpcClient{
		addr:                addr,
		client:              mgmt.NewClusterServiceClient(conn),
		regionClient:        mgmt.NewRegionServiceClient(conn),
		clusterStatusClient: mgmt.NewClusterCustomStatusServiceClient(conn),
		resourcePoolClient:  mgmt.NewResourcePoolServiceClient(conn),
		exportClient:        newExportGrpcClient(newManagementExportServiceClient(conn)),
	}
}

func (m *mgmtSvcGrpcClient) ExportClient() ExportGrpcClient {
	return m.exportClient
}

func (m *mgmtSvcGrpcClient) GetServerlessRegional(ctx context.Context, clusterId uint64) (*serverlessv1.ServerlessRegionalServiceClusterInfo, bizerr.BizError) {
	req := &mgmt.ListClustersRequest{
		Filter: &mgmt.ClusterFilter{
			ClusterIds:    []uint64{clusterId},
			ExcludeStates: DeletedClusterStatus,
			ClusterType:   mgmt.DeployType_DEPLOY_TYPE_SERVERLESS.Enum(),
		},
	}
	listResp, err := m.client.ListClusters(ctx, req)
	if err != nil {
		return nil, bizerr.WrapError(err, "GetServerlessClusterFullView failed")
	}
	if len(listResp.Clusters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster %d is not found", clusterId)
	}
	if listResp.Clusters[0].GetServerlessCluster().GetCluster() == nil {
		return nil, bizerr.New(bizerr.ErrCodeUnknown, "cluster %d regional info is empty", clusterId)

	}
	return listResp.Clusters[0].GetServerlessCluster().GetCluster(), nil
}

func (m *mgmtSvcGrpcClient) GetClusterByID(ctx context.Context, clusterID uint64) (*mgmt.Cluster, bizerr.BizError) {
	req := &mgmt.ListClustersRequest{
		Filter: &mgmt.ClusterFilter{
			ClusterIds:    []uint64{clusterID},
			ExcludeStates: DeletedClusterStatus,
			View:          mgmt.ClusterFilter_CLUSTER_VIEW_FULL,
		},
	}
	listResp, err := m.client.ListClusters(ctx, req)
	if err != nil {
		return nil, bizerr.WrapError(err, "GetServerlessClusterFullView failed")
	}
	if len(listResp.Clusters) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "cluster %d is not found", clusterID)
	}
	return listResp.Clusters[0], nil
}

func (m *mgmtSvcGrpcClient) UpdateServerlessRegionalAnnotations(ctx context.Context, clusterId uint64, annotations map[string]string) bizerr.BizError {
	_, err := m.client.UpdateServerlessRegionalClusterAnnotations(ctx, &mgmt.UpdateServerlessRegionalClusterAnnotationsRequest{
		ClusterId:   clusterId,
		Annotations: annotations,
	})
	if err != nil {
		return bizerr.WrapError(err, "UpdateServerlessRegionalAnnotations failed")
	}
	return nil
}

func (m *mgmtSvcGrpcClient) RestoreDedicatedCluster(ctx context.Context, req *mgmt.RestoreDedicatedClusterRequest) (*mgmt.Cluster, bizerr.BizError) {
	cluster, err := m.client.RestoreDedicatedCluster(ctx, req)
	if err != nil {
		return nil, bizerr.WrapError(err, "RestoreDedicatedCluster failed")
	}
	return cluster, nil
}

func (m *mgmtSvcGrpcClient) ListClusters(ctx context.Context, in *mgmt.ListClustersRequest, opts ...grpc.CallOption) (*mgmt.ListClustersResponse, bizerr.BizError) {
	clusters, err := m.client.ListClusters(ctx, in, opts...)
	if err != nil {
		return nil, bizerr.WrapError(err, "List clusters failed")
	}
	return clusters, nil
}

func (m *mgmtSvcGrpcClient) SetClusterCustomStatus(ctx context.Context, in *mgmt.SetClusterCustomStatusRequest, opts ...grpc.CallOption) (*mgmt.ClusterCustomStatus, bizerr.BizError) {
	status, err := m.clusterStatusClient.SetClusterCustomStatus(ctx, in, opts...)
	if err != nil {
		return nil, bizerr.WrapError(err, "SetClusterCustomStatus failed")
	}
	return status, nil
}

func (m *mgmtSvcGrpcClient) DeleteClusterCustomStatus(ctx context.Context, in *mgmt.DeleteClusterCustomStatusRequest, opts ...grpc.CallOption) bizerr.BizError {
	_, err := m.clusterStatusClient.DeleteClusterCustomStatus(ctx, in, opts...)
	if err != nil {
		return bizerr.WrapError(err, "DeleteClusterCustomStatus failed")
	}
	return nil
}

func (m *mgmtSvcGrpcClient) IsClusterCustomStatusExisted(ctx context.Context, in *mgmt.IsClusterCustomStatusExistedRequest, opts ...grpc.CallOption) (*mgmt.IsClusterCustomStatusExistedResponse, bizerr.BizError) {
	status, err := m.clusterStatusClient.IsClusterCustomStatusExisted(ctx, in, opts...)
	if err != nil {
		return nil, bizerr.WrapError(err, "DeleteClusterCustomStatus failed")
	}
	return status, nil
}

func (m *mgmtSvcGrpcClient) GetResourcePoolByID(ctx context.Context, resourcePoolID uint64) (*globalngv1.ResourcePool, bizerr.BizError) {
	req := &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: resourcePoolID,
	}
	resp, err := m.resourcePoolClient.GetResourcePool(ctx, req)
	if err != nil {
		if bizerr.ParseGormErr2code(err) == bizerr.ErrCodeRecordNotFound {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "resource pool %d is not found", resourcePoolID)
		}

		return nil, bizerr.WrapError(err, "GetResourcePoolByID failed")
	}
	return resp.ResourcePool, nil
}

func (m *mgmtSvcGrpcClient) GetResourcePoolIncludingDeleted(
	ctx context.Context, resourcePoolID uint64,
) (*globalngv1.ResourcePool, bizerr.BizError) {
	resp, err := m.resourcePoolClient.GetResourcePool(ctx, &globalngv1.GetResourcePoolRequest{
		ResourcePoolId: resourcePoolID,
		ShowDeleted:    true,
	})
	if err != nil {
		if bizerr.ParseGormErr2code(err) == bizerr.ErrCodeRecordNotFound {
			return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "resource pool %d is not found", resourcePoolID)
		}
		return nil, bizerr.WrapError(err, "get resource pool %d including deleted failed", resourcePoolID)
	}
	return resp.GetResourcePool(), nil
}
