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

func TestSetDMComponentConfigOrVersion_NoDMClusters(t *testing.T) {
	mockDMClusterRepo := repomocks.NewDMClusterRepo(t)
	mockDMClusterRepo.On("List", mock.Anything, mock.Anything).Return([]*entity.DMCluster{}, nil)

	server := &serverImpl{
		dmClusterRepo:  mockDMClusterRepo,
		sharedPoolRepo: availableSharedPoolRepoForMutationTest(),
	}

	err := server.SetDMComponentConfigOrVersion(
		context.Background(),
		"test-shared-pool",
		[]*entity.TargetComponent{{ComponentType: apiv1.ComponentType_DATA_MIGRATION}},
		apiv1.SetSharedPoolComponentConfigVersionRequest_VERSION,
		false,
	)
	require.NoError(t, err)
}

func TestSetDMComponentConfigOrVersion_InvalidComponentType(t *testing.T) {
	server := &serverImpl{}

	err := server.SetDMComponentConfigOrVersion(
		context.Background(),
		"test-shared-pool",
		[]*entity.TargetComponent{{ComponentType: apiv1.ComponentType_TIKV}},
		apiv1.SetSharedPoolComponentConfigVersionRequest_VERSION,
		false,
	)
	require.Error(t, err)
	assertInvalidArgument(t, err, "invalid component type")
}

func TestSetDMComponentConfigOrVersion_SubmitsApplyDAGWithUpdateImageArg(t *testing.T) {
	tests := []struct {
		name            string
		scope           entity.SetVersionConfigScope
		wantUpdateImage bool
	}{
		{
			name:            "config does not update image",
			scope:           apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
			wantUpdateImage: false,
		},
		{
			name:            "version updates image",
			scope:           apiv1.SetSharedPoolComponentConfigVersionRequest_VERSION,
			wantUpdateImage: true,
		},
		{
			name:            "all updates image",
			scope:           apiv1.SetSharedPoolComponentConfigVersionRequest_ALL,
			wantUpdateImage: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			dmCluster := &entity.DMCluster{
				Id:           "dm-1",
				SharedPoolId: "test-shared-pool",
				Phase:        apiv1.DMCluster_AVAILABLE,
				MasterNodeQuota: &apiv1.NodeQuota{
					NodeSpec:  &apiv1.NodeSpec{Cpu: 4, MemoryGi: 8},
					NodeCount: 1,
				},
				WorkerNodeQuota: &apiv1.NodeQuota{
					NodeSpec:  &apiv1.NodeSpec{Cpu: 4, MemoryGi: 8},
					NodeCount: 1,
				},
			}

			mockDMClusterRepo := repomocks.NewDMClusterRepo(t)
			mockDMClusterRepo.On("List", mock.Anything, mock.Anything).Return([]*entity.DMCluster{dmCluster}, nil)
			mockDMClusterRepo.On("Load", mock.Anything, "dm-1", false).Return(dmCluster, nil)
			mockDMClusterRepo.On("Save", mock.Anything, mock.MatchedBy(func(cluster *entity.DMCluster) bool {
				return cluster.GetPhase() == apiv1.DMCluster_MODIFYING
			})).Return(nil)
			mockDMClusterRepo.On("GetBackend", mock.Anything, "dm-1", false).Return(entity.DMBackendOperatorV2, nil)

			mockDagExecutor := executormocks.NewRemoteExecutor(t)
			mockDagExecutor.On("QueryRunnings", mock.Anything, dag.PlanApplyDMCluster, mock.Anything).
				Return(executor.NewExecTaskIdStatus(), nil).
				Once()
			mockDagExecutor.On("Submit", mock.Anything, dag.PlanApplyDMCluster, mock.MatchedBy(func(args actions.ActionArgs) bool {
				updateImage, err := args.GetBool(workflow.DMUpdateImageKey)
				return err == nil && updateImage == tt.wantUpdateImage
			}), mock.Anything).
				Return(nil).
				Once()

			server := &serverImpl{
				dmClusterRepo:  mockDMClusterRepo,
				sharedPoolRepo: availableSharedPoolRepoForMutationTest(),
				dagExecutor:    mockDagExecutor,
				transactioner:  &mockConfigVersionTransactioner{},
			}

			err := server.SetDMComponentConfigOrVersion(
				context.Background(),
				"test-shared-pool",
				[]*entity.TargetComponent{{ComponentType: apiv1.ComponentType_DATA_MIGRATION}},
				tt.scope,
				false,
			)
			require.NoError(t, err)
			mockDagExecutor.AssertExpectations(t)
		})
	}
}

func assertInvalidArgument(t *testing.T, err error, msg string) {
	t.Helper()
	require.Error(t, err)
	require.Contains(t, err.Error(), msg)
	statusErr, ok := status.FromError(err)
	require.True(t, ok)
	require.Equal(t, codes.InvalidArgument, statusErr.Code())
}
