package server

import (
	"context"
	"errors"
	"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"
	executormocks "github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor/mocks"
	repomnocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func TestGetProviderRole(t *testing.T) {
	tests := []struct {
		name           string
		tidbCustomerId string
		setupMocks     func(*repomnocks.TiDBClusterRepo, *repomnocks.SharedPoolRepo, *executormocks.RemoteExecutor)
		expectedError  string
		expectedCode   codes.Code
		validate       func(*testing.T, *entity.ProviderRole, error)
	}{
		{
			name:           "successful get provider role",
			tidbCustomerId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool-id",
				}
				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)

				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id: "testsharedpool-id",
						Provider: &apiv1.Provider{
							IdentityId: "test-identity-id",
							NodeRole:   "test-node-role",
						},
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "testsharedpool-id", false).Return(sharedPool, nil)
			},
			validate: func(t *testing.T, result *entity.ProviderRole, err error) {
				t.Helper()
				require.NoError(t, err)
				require.NotNil(t, result)
				assert.Equal(t, "test-identity-id", result.GetCloudIamIdentity())
				assert.Equal(t, "test-node-role", result.GetNodeRole())
				assert.Equal(t, "testclusterid", result.GetExternalId())
			},
		},
		{
			name:           "empty tidb customer id",
			tidbCustomerId: "",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				// No mocks needed for this test case
			},
			expectedError: "tidbCluster id is required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:           "tidb cluster not found in repo and no creating DAG",
			tidbCustomerId: "nonexistentid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbClusterRepo.On("Load", mock.Anything, "nonexistentid", false).Return(nil, status.Error(codes.NotFound, "not found"))
			},
			expectedError: "tidbCluster nonexistentid not found",
			expectedCode:  codes.NotFound,
		},
		{
			name:           "tidb cluster repository load error",
			tidbCustomerId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(nil, errors.New("database error"))
			},
			expectedError: "failed to load tidbCluster: database error",
			expectedCode:  codes.Internal,
		},
		{
			name:           "shared pool not found in repo and no creating DAG",
			tidbCustomerId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "non-existent-pool-id",
				}
				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)

				sharedPoolRepo.On("Load", mock.Anything, "non-existent-pool-id", false).Return(nil, status.Error(codes.NotFound, "not found"))
			},
			expectedError: "sharedPool non-existent-pool-id not found",
			expectedCode:  codes.NotFound,
		},
		{
			name:           "shared pool repository load error",
			tidbCustomerId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool-id",
				}
				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)

				sharedPoolRepo.On("Load", mock.Anything, "testsharedpool-id", false).Return(nil, errors.New("database error"))
			},
			expectedError: "failed to load sharedPool: database error",
			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)
			dagExecutor := executormocks.NewRemoteExecutor(t)

			// Setup mocks
			tt.setupMocks(tidbClusterRepo, sharedPoolRepo, dagExecutor)

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

			// Execute test
			result, err := server.GetProviderRole(context.Background(), tt.tidbCustomerId)

			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)
			}
		})
	}
}
