package server

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	bractionmocks "github.com/tidbcloud/cluster-service-ng/domain/workflow/action/br/mocks"
	executormocks "github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	repomnocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestListTiDBClusters(t *testing.T) {
	tests := []struct {
		name          string
		sharedPoolId  string
		tenantId      string
		setupMocks    func(*repomnocks.TiDBClusterRepo, *repomnocks.SharedPoolRepo, *repomnocks.TiDBGroupRepo, *executormocks.RemoteExecutor, *bractionmocks.ScaleManager)
		expectedError string
		expectedCode  codes.Code
		validate      func(*testing.T, []*entity.TiDBCluster, error)
	}{
		{
			name:     "successful list with clusters",
			tenantId: "tenant-1",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				clusters := []*entity.TiDBCluster{
					{
						Id:           "cluster-1",
						TenantId:     "tenant-1",
						SharedPoolId: "shared-pool-1",
						Phase:        apiv1.Cluster_AVAILABLE,
					},
					{
						Id:           "cluster-2",
						TenantId:     "tenant-1",
						SharedPoolId: "shared-pool-2",
						Phase:        apiv1.Cluster_AVAILABLE,
					},
				}
				expectedFilter := &repo.TiDBClusterListFilter{
					TenantIds:     []string{"tenant-1"},
					SharedPoolIds: []string{},
					Deleted:       false,
				}
				tidbClusterRepo.On("List", mock.Anything, expectedFilter).Return(clusters, nil)

				// Mock setTiDBClusterStatus dependencies for each cluster
				for _, cluster := range clusters {
					tidbGroups := []*entity.TiDBGroup{
						{
							TiDBGroup: apiv1.TiDBGroup{
								Id:    "testtidbgroup",
								Phase: apiv1.TiDBGroup_AVAILABLE,
							},
						},
					}
					tidbClusterRepo.On("Load", mock.Anything, cluster.GetId(), false).Return(cluster, nil)
					tidbGroupRepo.On("List", mock.Anything, mock.Anything, mock.Anything, []string{cluster.GetId()}, mock.Anything, false).Return(tidbGroups, nil)
					dagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
				}
			},
			validate: func(t *testing.T, result []*entity.TiDBCluster, err error) {
				t.Helper()
				require.NoError(t, err)
				require.Len(t, result, 2)
				assert.Equal(t, "cluster-1", result[0].GetId())
				assert.Equal(t, "cluster-2", result[1].GetId())
				assert.Equal(t, "tenant-1", result[0].GetTenantId())
			},
		},
		{
			name:         "successful list with shared pool filter",
			tenantId:     "tenant-1",
			sharedPoolId: "shared-pool-1",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				clusters := []*entity.TiDBCluster{
					{
						Id:           "cluster-1",
						TenantId:     "tenant-1",
						SharedPoolId: "shared-pool-1",
						Phase:        apiv1.Cluster_AVAILABLE,
					},
				}
				expectedFilter := &repo.TiDBClusterListFilter{
					TenantIds:     []string{"tenant-1"},
					SharedPoolIds: []string{"shared-pool-1"},
					Deleted:       false,
				}
				tidbClusterRepo.On("List", mock.Anything, expectedFilter).Return(clusters, nil)

				// Mock setTiDBClusterStatus dependencies
				tidbGroups := []*entity.TiDBGroup{
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id:    "testtidbgroup",
							Phase: apiv1.TiDBGroup_AVAILABLE,
						},
					},
				}
				tidbClusterRepo.On("Load", mock.Anything, "cluster-1", false).Return(clusters[0], nil)
				tidbGroupRepo.On("List", mock.Anything, mock.Anything, mock.Anything, []string{"cluster-1"}, mock.Anything, false).Return(tidbGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
			},
			validate: func(t *testing.T, result []*entity.TiDBCluster, err error) {
				t.Helper()
				require.NoError(t, err)
				require.Len(t, result, 1)
				assert.Equal(t, "cluster-1", result[0].GetId())
				assert.Equal(t, "shared-pool-1", result[0].GetSharedPoolId())
			},
		},
		{
			name:     "empty list",
			tenantId: "tenant-1",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				expectedFilter := &repo.TiDBClusterListFilter{
					TenantIds:     []string{"tenant-1"},
					SharedPoolIds: []string{},
					Deleted:       false,
				}
				tidbClusterRepo.On("List", mock.Anything, expectedFilter).Return([]*entity.TiDBCluster{}, nil)
			},
			validate: func(t *testing.T, result []*entity.TiDBCluster, err error) {
				t.Helper()
				require.NoError(t, err)
				require.Empty(t, result)
			},
		},
		{
			name:     "repo list error",
			tenantId: "tenant-1",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				expectedFilter := &repo.TiDBClusterListFilter{
					TenantIds:     []string{"tenant-1"},
					SharedPoolIds: []string{},
					Deleted:       false,
				}
				tidbClusterRepo.On("List", mock.Anything, expectedFilter).Return(nil, status.Error(codes.Internal, "database error"))
			},
			expectedError: "failed to list tidb clusters:",
			expectedCode:  codes.Internal,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mocks
			tidbClusterRepo := repomnocks.NewTiDBClusterRepo(t)
			sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
			tidbGroupRepo := repomnocks.NewTiDBGroupRepo(t)
			dagExecutor := executormocks.NewRemoteExecutor(t)
			scaleManager := bractionmocks.NewScaleManager(t)
			// Setup mocks
			tt.setupMocks(tidbClusterRepo, sharedPoolRepo, tidbGroupRepo, dagExecutor, scaleManager)

			// Create server implementation
			server := &serverImpl{
				tidbClusterRepo: tidbClusterRepo,
				sharedPoolRepo:  sharedPoolRepo,
				tidbGroupRepo:   tidbGroupRepo,
				dagExecutor:     dagExecutor,
				scaleManager:    scaleManager,
			}

			// Execute test
			result, err := server.ListTiDBClusters(context.Background(), tt.sharedPoolId, tt.tenantId)

			switch {
			case tt.expectedError != "":
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
				if tt.expectedCode != codes.OK {
					assert.Equal(t, tt.expectedCode, status.Code(err))
				}
				assert.Nil(t, result)

			case tt.validate != nil:
				tt.validate(t, result, err)

			default:
				require.NoError(t, err)
				require.NotNil(t, result)
			}
		})
	}
}
