package server

import (
	"context"
	"testing"

	"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"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/dag"
	clustergatewaymocks "github.com/tidbcloud/cluster-service-ng/pkg/clustergateway/mocks"
	dataplanemocks "github.com/tidbcloud/cluster-service-ng/pkg/dataplanemgr/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor"
	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 TestApplyTiDBGroup(t *testing.T) {
	tests := []struct {
		name          string
		tidbClusterId string
		tidbGroupId   string
		force         bool
		setupMocks    func(*repomnocks.TiDBGroupRepo, *repomnocks.SharedPoolRepo, *repomnocks.LockRepo, *executormocks.RemoteExecutor, *bractionmocks.ScaleManager)
		expectedError string
		expectedCode  codes.Code
	}{
		{
			name:          "successful apply tidb group",
			tidbClusterId: "testcluster",
			tidbGroupId:   "testgroup",
			force:         false,
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, lockRepo *repomnocks.LockRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				// Mock tidb group load
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:           "testgroup",
						ClusterId:    "testcluster",
						SharedPoolId: "testsharedpool",
						Phase:        apiv1.TiDBGroup_AVAILABLE,
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)

				// Mock shared pool load
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id:    "testsharedpool",
						Phase: apiv1.SharedPool_AVAILABLE,
						ComponentGroups: []*entity.Component{
							{
								Id:            "testgroup",
								ComponentType: apiv1.ComponentType_TIDB,
								Phase:         apiv1.Component_AVAILABLE,
							},
						},
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "testsharedpool", false).Return(sharedPool, nil)

				// Mock lock operations
				lockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)

				// Mock running task check for mergeSharedPoolStatusFromDagTask
				dagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
				// Mock no restore
				scaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)

				// Mock DAG execution
				dagExecutor.On("Submit", mock.Anything, dag.PlanApplyComponent, mock.Anything, mock.Anything).Return(nil)
			},
		},
		{
			name:          "missing tidb cluster id",
			tidbClusterId: "",
			tidbGroupId:   "testgroup",
			force:         false,
			setupMocks: func(*repomnocks.TiDBGroupRepo, *repomnocks.SharedPoolRepo, *repomnocks.LockRepo, *executormocks.RemoteExecutor, *bractionmocks.ScaleManager) {
			},
			expectedError: "tidbCluster id is required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:          "missing tidb group id",
			tidbClusterId: "testcluster",
			tidbGroupId:   "",
			force:         false,
			setupMocks: func(*repomnocks.TiDBGroupRepo, *repomnocks.SharedPoolRepo, *repomnocks.LockRepo, *executormocks.RemoteExecutor, *bractionmocks.ScaleManager) {
			},
			expectedError: "tidbGroup id is required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:          "tidb group not found",
			tidbClusterId: "testcluster",
			tidbGroupId:   "nonexistent",
			force:         false,
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, lockRepo *repomnocks.LockRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				// Mock tidb group not found
				tidbGroupRepo.On("Load", mock.Anything, "nonexistent", false).Return(nil, status.Error(codes.NotFound, "not found"))
			},
			expectedError: "tidbGroup nonexistent not found",
			expectedCode:  codes.NotFound,
		},
		{
			name:          "tidb group belongs to different cluster",
			tidbClusterId: "testcluster",
			tidbGroupId:   "testgroup",
			force:         false,
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, lockRepo *repomnocks.LockRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				// Mock tidb group with different cluster id
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:           "testgroup",
						ClusterId:    "differentcluster",
						SharedPoolId: "testsharedpool",
						Phase:        apiv1.TiDBGroup_AVAILABLE,
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)
			},
			expectedError: "tidbGroup testgroup is not belong to tidbCluster testcluster",
			expectedCode:  codes.FailedPrecondition,
		},
		{
			name:          "component not available force false",
			tidbClusterId: "testcluster",
			tidbGroupId:   "testgroup",
			force:         false,
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, lockRepo *repomnocks.LockRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				// Mock tidb group load
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:           "testgroup",
						ClusterId:    "testcluster",
						SharedPoolId: "testsharedpool",
						Phase:        apiv1.TiDBGroup_AVAILABLE,
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)

				// Mock shared pool with unavailable component
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id:    "testsharedpool",
						Phase: apiv1.SharedPool_AVAILABLE,
						ComponentGroups: []*entity.Component{
							{
								Id:            "testgroup",
								ComponentType: apiv1.ComponentType_TIDB,
								Phase:         apiv1.Component_CREATING,
							},
						},
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "testsharedpool", false).Return(sharedPool, nil)

				// Mock lock operations
				lockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)

				// Mock running task check for mergeSharedPoolStatusFromDagTask
				dagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
				// Mock no restore
				scaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)
			},
			expectedError: "target component tidb testgroup is not available",
			expectedCode:  codes.FailedPrecondition,
		},
		{
			name:          "component not available force true",
			tidbClusterId: "testcluster",
			tidbGroupId:   "testgroup",
			force:         true,
			setupMocks: func(tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, lockRepo *repomnocks.LockRepo, dagExecutor *executormocks.RemoteExecutor, scaleManager *bractionmocks.ScaleManager) {
				// Mock tidb group load
				tidbGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:           "testgroup",
						ClusterId:    "testcluster",
						SharedPoolId: "testsharedpool",
						Phase:        apiv1.TiDBGroup_AVAILABLE,
					},
				}
				tidbGroupRepo.On("Load", mock.Anything, "testgroup", false).Return(tidbGroup, nil)

				// Mock shared pool with unavailable component
				sharedPool := &entity.SharedPool{
					SharedPool: apiv1.SharedPool{
						Id:    "testsharedpool",
						Phase: apiv1.SharedPool_AVAILABLE,
						ComponentGroups: []*entity.Component{
							{
								Id:            "testgroup",
								ComponentType: apiv1.ComponentType_TIDB,
								Phase:         apiv1.Component_CREATING,
							},
						},
					},
				}
				sharedPoolRepo.On("Load", mock.Anything, "testsharedpool", false).Return(sharedPool, nil)

				// Mock lock operations
				lockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)

				// Mock running task check for mergeSharedPoolStatusFromDagTask
				dagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
				// Mock no restore
				scaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)

				// Mock DAG execution
				dagExecutor.On("Submit", mock.Anything, dag.PlanApplyComponent, mock.Anything, mock.Anything).Return(nil)
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mocks
			tidbGroupRepo := repomnocks.NewTiDBGroupRepo(t)
			sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
			lockRepo := repomnocks.NewLockRepo(t)
			dagExecutor := executormocks.NewRemoteExecutor(t)
			dataplaneClient := dataplanemocks.NewClient(t)
			clusterGatewayClient := clustergatewaymocks.NewClient(t)
			scaleManager := bractionmocks.NewScaleManager(t)
			// Setup mocks
			tt.setupMocks(tidbGroupRepo, sharedPoolRepo, lockRepo, dagExecutor, scaleManager)

			// Create server implementation
			server := &serverImpl{
				tidbGroupRepo:     tidbGroupRepo,
				sharedPoolRepo:    sharedPoolRepo,
				lockRepo:          lockRepo,
				dagExecutor:       dagExecutor,
				transactioner:     &mockTransactioner{},
				dataplaneClient:   dataplaneClient,
				clustergatewayCli: clusterGatewayClient,
				scaleManager:      scaleManager,
			}

			// Execute test
			err := server.ApplyTiDBGroup(context.Background(), tt.tidbClusterId, tt.tidbGroupId, tt.force)

			// Validate results
			if tt.expectedError != "" {
				require.Error(t, err)
				require.Contains(t, err.Error(), tt.expectedError)
				if tt.expectedCode != codes.OK {
					require.Equal(t, tt.expectedCode, status.Code(err))
				}
			} else {
				require.NoError(t, err)
			}
		})
	}
}
