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"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func availableSharedPoolRepoForMutationTest() *repomocks.SharedPoolRepo {
	sharedPoolRepo := &repomocks.SharedPoolRepo{}
	sharedPoolRepo.On("Load", mock.Anything, mock.Anything, false).
		Return(func(_ context.Context, id string, _ bool) *entity.SharedPool {
			return &entity.SharedPool{SharedPool: apiv1.SharedPool{Id: id, Phase: apiv1.SharedPool_AVAILABLE}}
		}, nil)
	return sharedPoolRepo
}

func availableSharedPoolRepoForCleanupMutationTest() *repomocks.SharedPoolRepo {
	sharedPoolRepo := &repomocks.SharedPoolRepo{}
	sharedPoolRepo.On("Load", mock.Anything, mock.Anything, true).
		Return(func(_ context.Context, id string, _ bool) *entity.SharedPool {
			return &entity.SharedPool{SharedPool: apiv1.SharedPool{Id: id, Phase: apiv1.SharedPool_AVAILABLE}}
		}, nil)
	return sharedPoolRepo
}

func TestGuardSharedPoolMutation(t *testing.T) {
	tests := []struct {
		name     string
		state    entity.PauseResumeState
		desired  bool
		rule     sharedPoolMutationRule
		wantCode codes.Code
	}{
		{name: "available permits ordinary mutation", rule: sharedPoolMutationBlockedWhilePaused},
		{name: "pausing blocks ordinary mutation", state: entity.PauseResumeStatePausing, rule: sharedPoolMutationBlockedWhilePaused, wantCode: codes.FailedPrecondition},
		{name: "pausing permits delete takeover", state: entity.PauseResumeStatePausing, rule: sharedPoolMutationDeletePool},
		{name: "resuming blocks mutation", state: entity.PauseResumeStateResuming, rule: sharedPoolMutationUpdateTags, wantCode: codes.FailedPrecondition},
		{name: "resuming permits create", state: entity.PauseResumeStateResuming, rule: sharedPoolMutationCreateTiDBCluster},
		{name: "pausing blocks create", state: entity.PauseResumeStatePausing, rule: sharedPoolMutationCreateTiDBCluster, wantCode: codes.FailedPrecondition},
		{name: "paused blocks create", state: entity.PauseResumeStatePaused, desired: true, rule: sharedPoolMutationCreateTiDBCluster, wantCode: codes.FailedPrecondition},
		{name: "paused blocks ordinary mutation", state: entity.PauseResumeStatePaused, desired: true, rule: sharedPoolMutationBlockedWhilePaused, wantCode: codes.FailedPrecondition},
		{name: "paused permits vcpu limit", state: entity.PauseResumeStatePaused, desired: true, rule: sharedPoolMutationUpdateVCPUsLimit},
		{name: "paused permits tags", state: entity.PauseResumeStatePaused, desired: true, rule: sharedPoolMutationUpdateTags},
		{name: "paused permits instance delete", state: entity.PauseResumeStatePaused, desired: true, rule: sharedPoolMutationDeleteInstance},
		{name: "paused permits pool delete", state: entity.PauseResumeStatePaused, desired: true, rule: sharedPoolMutationDeletePool},
		{name: "invalid combination fails closed", desired: true, rule: sharedPoolMutationUpdateTags, wantCode: codes.FailedPrecondition},
		{name: "unspecified rule fails closed", rule: sharedPoolMutationUnspecified, wantCode: codes.Internal},
		{name: "unknown rule fails closed", rule: sharedPoolMutationRule(255), wantCode: codes.Internal},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sharedPool := &entity.SharedPool{
				SharedPool:            apiv1.SharedPool{Phase: apiv1.SharedPool_AVAILABLE},
				PauseResumeState:      tt.state,
				SuspendComputeDesired: tt.desired,
			}
			err := guardSharedPoolMutation(sharedPool, tt.rule)
			require.Equal(t, tt.wantCode, status.Code(err))
		})
	}
}

func TestGuardSharedPoolMutationNilPoolReturnsInternal(t *testing.T) {
	err := guardSharedPoolMutation(nil, sharedPoolMutationBlockedWhilePaused)
	require.Equal(t, codes.Internal, status.Code(err))
}
