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"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/dag"
	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 TestDeleteTiDBCluster(t *testing.T) {
	testCases := []struct {
		name          string
		tidbClusterId string
		setupMocks    func(*repomnocks.TiDBClusterRepo, *repomnocks.SharedPoolRepo, *repomnocks.TiDBGroupRepo, *executormocks.RemoteExecutor)
		validate      func(*testing.T, *entity.TiDBCluster, error)
		expectedError string
		expectedCode  codes.Code
	}{
		{
			name:          "empty tidb cluster id",
			tidbClusterId: "",
			setupMocks: func(*repomnocks.TiDBClusterRepo, *repomnocks.SharedPoolRepo, *repomnocks.TiDBGroupRepo, *executormocks.RemoteExecutor) {
			},
			expectedError: "tidbClusterId is empty",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:          "tidb cluster not found",
			tidbClusterId: "nonexistingcluster",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, dagExecutor *executormocks.RemoteExecutor) {
				// Mock cluster not found
				tidbClusterRepo.On("Load", mock.Anything, "nonexistingcluster", false).Return(nil, status.Error(codes.NotFound, "not found"))
			},
			expectedError: "not found",
			expectedCode:  codes.NotFound,
		},
		{
			name:          "tidb cluster has associated tidb groups",
			tidbClusterId: "clusterwithtidbs",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, dagExecutor *executormocks.RemoteExecutor) {
				// Mock cluster found
				tidbCluster := &entity.TiDBCluster{
					Id:           "clusterwithtidbs",
					TenantId:     "testtenant",
					SharedPoolId: "testsharedpool",
					Phase:        apiv1.Cluster_AVAILABLE,
				}
				tidbClusterRepo.On("Load", mock.Anything, "clusterwithtidbs", false).Return(tidbCluster, nil)

				// Mock tidb groups found
				tidbGroups := []*entity.TiDBGroup{
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id: "tidbgroup1",
						},
					},
				}
				tidbGroupRepo.On("List", mock.Anything, mock.Anything, mock.Anything, []string{"clusterwithtidbs"}, mock.Anything, false).Return(tidbGroups, nil)
			},
			expectedError: "tidbCluster clusterwithtidbs still has 1 associated tidb groups, please delete them first",
			expectedCode:  codes.FailedPrecondition,
		},
		{
			name:          "shared pool not found",
			tidbClusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, dagExecutor *executormocks.RemoteExecutor) {
				// Mock cluster found
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					TenantId:     "testtenant",
					SharedPoolId: "nonexistingpool",
					Phase:        apiv1.Cluster_AVAILABLE,
				}
				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)

				// Mock no tidb groups
				tidbGroupRepo.On("List", mock.Anything, mock.Anything, mock.Anything, []string{"testclusterid"}, mock.Anything, false).Return([]*entity.TiDBGroup{}, nil)

				// Mock no running delete tidbgroup DAGs
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.Anything).Return(nil, nil)

				// Mock shared pool repository error
				sharedPoolRepo.On("Load", mock.Anything, "nonexistingpool", false).Return(nil, status.Error(codes.NotFound, "not found"))
			},
			expectedError: "sharedPool nonexistingpool not found",
			expectedCode:  codes.NotFound,
		},
	}

	for _, tt := range testCases {
		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)

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

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

			// Execute test
			result, err := server.DeleteTiDBCluster(context.Background(), tt.tidbClusterId)

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