package runtime

import (
	"context"
	"strconv"
	"testing"

	"github.com/golang/mock/gomock"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
	"github.com/karmada-io/karmada/pkg/scheduler/framework"
	frameworktesting "github.com/karmada-io/karmada/pkg/scheduler/framework/testing"
)

func Test_frameworkImpl_RunFilterPlugins(t *testing.T) {
	ctx := context.Background()
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	alwaysError := frameworktesting.NewMockFilterPlugin(mockCtrl)
	alwaysError.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().
		Return(framework.NewResult(framework.Error, "foo"))
	alwaysError.EXPECT().Name().AnyTimes().Return("foo")

	alwaysSuccess := frameworktesting.NewMockFilterPlugin(mockCtrl)
	alwaysSuccess.EXPECT().Filter(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().
		Return(framework.NewResult(framework.Success))
	alwaysSuccess.EXPECT().Name().AnyTimes().Return("foo")

	tests := []struct {
		name      string
		plugins   []framework.Plugin
		isSuccess bool
	}{
		{
			name:      "no filter plugin",
			plugins:   []framework.Plugin{},
			isSuccess: true,
		},
		{
			name:      "error filter plugin",
			plugins:   []framework.Plugin{alwaysError},
			isSuccess: false,
		},
		{
			name:      "success filter plugin",
			plugins:   []framework.Plugin{alwaysSuccess},
			isSuccess: true,
		},
		{
			name:      "error and success filter plugins",
			plugins:   []framework.Plugin{alwaysError, alwaysSuccess},
			isSuccess: false,
		},
		{
			name:      "success and error filter plugins",
			plugins:   []framework.Plugin{alwaysSuccess, alwaysError},
			isSuccess: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			registry, err := createAndRegisterFactory(tt.plugins)
			if err != nil {
				t.Errorf("create plugin factory error:%v", err)
			}

			frameWork, err := NewFramework(registry)
			if err != nil {
				t.Errorf("create frame work error:%v", err)
			}

			result := frameWork.RunFilterPlugins(ctx, nil, nil, nil)
			if result.IsSuccess() != tt.isSuccess {
				t.Errorf("want %v, but get:%v", tt.isSuccess, result.IsSuccess())
			}
		})
	}
}

func createAndRegisterFactory(plugins []framework.Plugin) (Registry, error) {
	registry := Registry{}
	for i, plugin := range plugins {
		pluginCopy := plugin
		filterPluginFactory := func() (framework.Plugin, error) {
			return pluginCopy, nil
		}
		if err := registry.Register("foo"+strconv.Itoa(i), filterPluginFactory); err != nil {
			return nil, err
		}
	}

	return registry, nil
}

func Test_frameworkImpl_RunScorePlugins(t *testing.T) {
	ctx := context.Background()
	mockCtrl := gomock.NewController(t)
	defer mockCtrl.Finish()

	clusters := []*clusterv1alpha1.Cluster{
		{ObjectMeta: metav1.ObjectMeta{Name: "c1"}},
	}
	tests := []struct {
		name      string
		mockFunc  func(mockScorePlugin *frameworktesting.MockScorePlugin, mockScoreExtension *frameworktesting.MockScoreExtensions)
		isSuccess bool
	}{
		{
			name: "Test score ok",
			mockFunc: func(mockScorePlugin *frameworktesting.MockScorePlugin, mockScoreExtension *frameworktesting.MockScoreExtensions) {
				mockScorePlugin.EXPECT().Score(gomock.Any(), gomock.Any(), gomock.Any()).
					Return(int64(60), framework.NewResult(framework.Success))
				mockScorePlugin.EXPECT().ScoreExtensions().Times(2).Return(mockScoreExtension)
				mockScorePlugin.EXPECT().Name().AnyTimes().Return("foo")

				mockScoreExtension.EXPECT().NormalizeScore(gomock.Any(), gomock.Any()).
					Return(framework.NewResult(framework.Success))
			},
			isSuccess: true,
		},
		{
			name: "Test score func error",
			mockFunc: func(mockScorePlugin *frameworktesting.MockScorePlugin, mockScoreExtension *frameworktesting.MockScoreExtensions) {
				mockScorePlugin.EXPECT().Score(gomock.Any(), gomock.Any(), gomock.Any()).
					Return(int64(-1), framework.NewResult(framework.Error, "foo"))
				mockScorePlugin.EXPECT().Name().AnyTimes().Return("foo")
			},
			isSuccess: false,
		},
		{
			name: "Test normalize score error",
			mockFunc: func(mockScorePlugin *frameworktesting.MockScorePlugin, mockScoreExtension *frameworktesting.MockScoreExtensions) {
				mockScorePlugin.EXPECT().Score(gomock.Any(), gomock.Any(), gomock.Any()).
					Return(int64(60), framework.NewResult(framework.Success))
				mockScorePlugin.EXPECT().ScoreExtensions().Times(2).Return(mockScoreExtension)
				mockScorePlugin.EXPECT().Name().AnyTimes().Return("foo")

				mockScoreExtension.EXPECT().NormalizeScore(gomock.Any(), gomock.Any()).
					Return(framework.NewResult(framework.Error, "foo"))
			},
			isSuccess: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			mockScorePlugin := frameworktesting.NewMockScorePlugin(mockCtrl)
			mockScoreExtension := frameworktesting.NewMockScoreExtensions(mockCtrl)

			registry, err := createAndRegisterFactory([]framework.Plugin{mockScorePlugin})
			if err != nil {
				t.Errorf("create plugin factory error:%v", err)
			}

			frameWork, err := NewFramework(registry)
			if err != nil {
				t.Errorf("create frame work error:%v", err)
			}

			tt.mockFunc(mockScorePlugin, mockScoreExtension)
			_, result := frameWork.RunScorePlugins(ctx, nil, clusters)
			if result.IsSuccess() != tt.isSuccess {
				t.Errorf("want %v, but get:%v", tt.isSuccess, result.IsSuccess())
			}
		})
	}
}
