package server

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	dataplanemocks "github.com/tidbcloud/cluster-service-ng/pkg/dataplane/mocks"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestComponentOperationsRejectMissingCoprocessorWorker(t *testing.T) {
	tests := []struct {
		name string
		run  func(context.Context, *serverImpl) error
	}{
		{
			name: "set hpa spec",
			run: func(ctx context.Context, s *serverImpl) error {
				return s.SetHPASpec(ctx, "workeronly", "", apiv1.ComponentType_COPROCESSOR_WORKER, &apiv1.HorizontalPodAutoscalerSpec{})
			},
		},
		{
			name: "set hpa scale behavior",
			run: func(ctx context.Context, s *serverImpl) error {
				enabled := true
				return s.SetHPAScaleBehavior(ctx, "workeronly", "", apiv1.ComponentType_COPROCESSOR_WORKER, &enabled, nil)
			},
		},
		{
			name: "set scale out config",
			run: func(ctx context.Context, s *serverImpl) error {
				return s.SetScaleOutConfig(ctx, "workeronly", "", apiv1.ComponentType_COPROCESSOR_WORKER, &apiv1.ScaleOutConfig{
					MinCount: 1,
					MaxCount: 2,
				})
			},
		},
		{
			name: "check pods",
			run: func(ctx context.Context, s *serverImpl) error {
				_, err := s.CheckPods(ctx, "workeronly", "cluster-1", apiv1.ComponentType_COPROCESSOR_WORKER, "0")
				return err
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
			lockRepo := repomocks.NewLockRepo(t)
			sharedPoolRepo.EXPECT().Load(ctx, "workeronly", false).Return(workerOnlySharedPoolForComponentOperationTest(), nil)
			lockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil).Maybe()
			s := &serverImpl{sharedPoolRepo: sharedPoolRepo, lockRepo: lockRepo}

			err := tt.run(ctx, s)

			assert.Equal(t, codes.NotFound, status.Code(err))
			assert.Contains(t, err.Error(), "component COPROCESSOR_WORKER not found in shared pool workeronly")
		})
	}
}

func TestHPAOperationsKeepUnsupportedComponentErrorWhenComponentMissing(t *testing.T) {
	tests := []struct {
		name               string
		expectDataplaneHPA bool
		run                func(context.Context, *serverImpl) error
	}{
		{
			name:               "set hpa spec",
			expectDataplaneHPA: true,
			run: func(ctx context.Context, s *serverImpl) error {
				return s.SetHPASpec(ctx, "monolithic", "", apiv1.ComponentType_PD_TSO, &apiv1.HorizontalPodAutoscalerSpec{})
			},
		},
		{
			name: "set hpa scale behavior",
			run: func(ctx context.Context, s *serverImpl) error {
				enabled := true
				return s.SetHPAScaleBehavior(ctx, "monolithic", "", apiv1.ComponentType_PD_TSO, &enabled, nil)
			},
		},
		{
			name:               "set scale out config",
			expectDataplaneHPA: true,
			run: func(ctx context.Context, s *serverImpl) error {
				return s.SetScaleOutConfig(ctx, "monolithic", "", apiv1.ComponentType_PD_TSO, &apiv1.ScaleOutConfig{
					MinCount: 1,
					MaxCount: 2,
				})
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
			dataplaneManager := dataplanemocks.NewManager(t)
			if tt.expectDataplaneHPA {
				sharedPoolRepo.EXPECT().Load(ctx, "monolithic", false).Return(workerOnlySharedPoolForComponentOperationTest(), nil)
				dataplaneManager.EXPECT().GetClient("test-dataplane").Return(fake.NewClientBuilder().Build(), nil)
			}
			s := &serverImpl{
				sharedPoolRepo:   sharedPoolRepo,
				dataplaneManager: dataplaneManager,
			}

			err := tt.run(ctx, s)

			assert.Equal(t, codes.Unimplemented, status.Code(err))
			assert.Contains(t, err.Error(), "PD_TSO does not support hpa")
		})
	}
}

func TestEnsureCoprocessorWorkerComponentExistsSkipsNilComponents(t *testing.T) {
	sharedPool := workerOnlySharedPoolForComponentOperationTest()
	sharedPool.ComponentGroups = append([]*apiv1.Component{nil}, sharedPool.ComponentGroups...)

	err := ensureCoprocessorWorkerComponentExists(sharedPool)

	assert.Equal(t, codes.NotFound, status.Code(err))
}

func workerOnlySharedPoolForComponentOperationTest() *entity.SharedPool {
	return &entity.SharedPool{SharedPool: apiv1.SharedPool{
		Id:            "workeronly",
		DataplaneName: "test-dataplane",
		ComponentGroups: []*apiv1.Component{
			{
				Id:            "default",
				ComponentType: apiv1.ComponentType_TIKV_WORKER,
			},
		},
	}}
}
