package mgmt

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/aisupport/packages/sdk-go/infra/utils/object"
	dedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	serverlessv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/serverless/v1"
	commonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"google.golang.org/grpc"
)

type recordingClusterServiceClient struct {
	mgmtv1.ClusterServiceClient
	request *mgmtv1.ListClustersRequest
}

func (c *recordingClusterServiceClient) ListClusters(
	_ context.Context,
	request *mgmtv1.ListClustersRequest,
	_ ...grpc.CallOption,
) (*mgmtv1.ListClustersResponse, error) {
	c.request = request
	return &mgmtv1.ListClustersResponse{
		Clusters: []*mgmtv1.Cluster{{ClusterId: 123, ClusterName: "deleted-cluster"}},
	}, nil
}

func TestReadClusters(t *testing.T) {
	t.Parallel()

	require.Equal(t, []Cluster{
		{
			ID:             "123",
			Name:           "prod",
			Status:         "AVAILABLE",
			ServicePlan:    "Dedicated",
			TiDBCloudOrgID: "456",
		},
	}, readClusters([]*mgmtv1.Cluster{
		{
			ClusterId:      123,
			OrganizationId: 456,
			ClusterName:    "prod",
			DeployType:     mgmtv1.DeployType_DEPLOY_TYPE_DEDICATED,
			ClusterInfo: &mgmtv1.Cluster_DedicatedCluster{
				DedicatedCluster: &dedicatedv1.Cluster{
					State: dedicatedv1.Cluster_AVAILABLE,
				},
			},
			ServicePlan: commonv1beta1.ServicePlan_Dedicated,
		},
	}))
}

func TestReadStatusUsesDeployType(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name     string
		cluster  *mgmtv1.Cluster
		expected string
	}{
		{
			name: "dedicated ignores custom status",
			cluster: &mgmtv1.Cluster{
				DeployType: mgmtv1.DeployType_DEPLOY_TYPE_DEDICATED,
				CustomStatuses: []*mgmtv1.ClusterCustomStatus{
					{StatusName: "custom-status"},
				},
				ClusterInfo: &mgmtv1.Cluster_DedicatedCluster{
					DedicatedCluster: &dedicatedv1.Cluster{
						State: dedicatedv1.Cluster_AVAILABLE,
					},
				},
			},
			expected: "AVAILABLE",
		},
		{
			name: "serverless strips user cluster status prefix",
			cluster: &mgmtv1.Cluster{
				DeployType: mgmtv1.DeployType_DEPLOY_TYPE_SERVERLESS,
				ClusterInfo: &mgmtv1.Cluster_ServerlessCluster{
					ServerlessCluster: &serverlessv1.ServerlessServiceCluster{
						UserStatus: serverlessv1.UserClusterStatus_USER_CLUSTER_STATUS_NORMAL,
					},
				},
			},
			expected: "NORMAL",
		},
		{
			name: "nextgen",
			cluster: &mgmtv1.Cluster{
				DeployType: mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN,
				ClusterInfo: &mgmtv1.Cluster_NextGenCluster{
					NextGenCluster: &nextgenv1.Cluster{
						State: nextgenv1.Cluster_AVAILABLE,
					},
				},
			},
			expected: "AVAILABLE",
		},
		{
			name: "does not inspect a different deploy payload",
			cluster: &mgmtv1.Cluster{
				DeployType: mgmtv1.DeployType_DEPLOY_TYPE_DEDICATED,
				ClusterInfo: &mgmtv1.Cluster_NextGenCluster{
					NextGenCluster: &nextgenv1.Cluster{
						State: nextgenv1.Cluster_AVAILABLE,
					},
				},
			},
			expected: "",
		},
		{
			name: "unspecified deploy type",
			cluster: &mgmtv1.Cluster{
				DeployType: mgmtv1.DeployType_DEPLOY_TYPE_UNSPECIFIED,
			},
			expected: "",
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			require.Equal(t, tt.expected, readStatus(tt.cluster))
		})
	}
}

func TestNewClusterFilterAddsClusterID(t *testing.T) {
	t.Parallel()

	filter := (&grpcClient{}).newClusterFilter(123, 456, object.Pointer(false))
	require.Equal(t, []uint64{123}, filter.GetTenantIds())
	require.Equal(t, []uint64{456}, filter.GetClusterIds())
	require.NotNil(t, filter.Deleted)
	require.False(t, filter.GetDeleted())
}

func TestNewClusterFilterIncludesDeletedForClusterOnlyLookup(t *testing.T) {
	t.Parallel()

	filter := (&grpcClient{}).newClusterFilter(0, 456, nil)
	require.Empty(t, filter.GetTenantIds())
	require.Equal(t, []uint64{456}, filter.GetClusterIds())
	require.Nil(t, filter.Deleted)
}

func TestGetClusterByIDRejectsInvalidClusterID(t *testing.T) {
	t.Parallel()

	_, err := (&grpcClient{}).GetClusterByID(context.Background(), "bad")
	require.Error(t, err)

	_, err = (&grpcClient{}).GetClusterByID(context.Background(), "0")
	require.Error(t, err)
}

func TestGetClusterByIDIncludesDeletedClusters(t *testing.T) {
	t.Parallel()

	clusterService := &recordingClusterServiceClient{}
	client := &grpcClient{cluster: clusterService}

	cluster, err := client.GetClusterByID(context.Background(), "123")
	require.NoError(t, err)
	require.Equal(t, "deleted-cluster", cluster.Name)
	require.NotNil(t, clusterService.request)
	require.Nil(t, clusterService.request.GetFilter().Deleted)
}

func TestListClustersRejectsInvalidClusterID(t *testing.T) {
	t.Parallel()

	_, err := (&grpcClient{}).ListClustersByOrgID(context.Background(), "123", ListClustersOptions{
		PageSize:  50,
		ClusterID: "bad",
	})
	require.Error(t, err)
}
