package resource

import (
	"context"
	"testing"

	apiequality "k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/runtime"

	clusterapiv1 "open-cluster-management.io/api/cluster/v1"
	clusterapiv1beta1 "open-cluster-management.io/api/cluster/v1beta1"

	testinghelpers "open-cluster-management.io/ocm/pkg/placement/helpers/testing"
)

func TestScoreClusterWithResource(t *testing.T) {
	cases := []struct {
		name              string
		resource          clusterapiv1.ResourceName
		algorithm         string
		placement         *clusterapiv1beta1.Placement
		clusters          []*clusterapiv1.ManagedCluster
		existingDecisions []runtime.Object
		expectedScores    map[string]int64
	}{
		{
			name:      "scores of ResourceAllocatableMemory",
			resource:  clusterapiv1.ResourceMemory,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "20", "100").Build(),
				testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "60", "100").Build(),
				testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "100", "100").Build(),
			},
			expectedScores: map[string]int64{"cluster1": -100, "cluster2": 0, "cluster3": 100},
		},
		{
			name:      "scores of ResourceAllocatableMemory with same resource value",
			resource:  clusterapiv1.ResourceMemory,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(),
				testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(),
				testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "50", "100").Build(),
			},
			expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100},
		},
		{
			name:      "scores of ResourceAllocatableMemory with zero resource value",
			resource:  clusterapiv1.ResourceMemory,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(),
				testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(),
				testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceMemory, "0", "100").Build(),
			},
			expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100},
		},
		{
			name:      "scores of ResourceAllocatableMemory with no cluster resource",
			resource:  clusterapiv1.ResourceMemory,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").Build(),
				testinghelpers.NewManagedCluster("cluster2").Build(),
				testinghelpers.NewManagedCluster("cluster3").Build(),
			},
			expectedScores: map[string]int64{},
		},
		{
			name:      "scores of ResourceAllocatableCPU",
			resource:  clusterapiv1.ResourceCPU,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "10", "10").Build(),
				testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "6", "10").Build(),
				testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "2", "10").Build(),
			},
			expectedScores: map[string]int64{"cluster1": 100, "cluster2": 0, "cluster3": -100},
		},
		{
			name:      "scores of ResourceAllocatableCPU with same resource value",
			resource:  clusterapiv1.ResourceCPU,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(),
				testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(),
				testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "5", "10").Build(),
			},
			expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100},
		},
		{
			name:      "scores of ResourceAllocatableCPU with zero resource value",
			resource:  clusterapiv1.ResourceCPU,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(),
				testinghelpers.NewManagedCluster("cluster2").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(),
				testinghelpers.NewManagedCluster("cluster3").WithResource(clusterapiv1.ResourceCPU, "0", "10").Build(),
			},
			expectedScores: map[string]int64{"cluster1": 100, "cluster2": 100, "cluster3": 100},
		},
		{
			name:      "scores of ResourceAllocatableCPU with no cluster resource",
			resource:  clusterapiv1.ResourceCPU,
			algorithm: "Allocatable",
			placement: testinghelpers.NewPlacement("test", "test").Build(),
			clusters: []*clusterapiv1.ManagedCluster{
				testinghelpers.NewManagedCluster("cluster1").Build(),
				testinghelpers.NewManagedCluster("cluster2").Build(),
				testinghelpers.NewManagedCluster("cluster3").Build(),
			},
			expectedScores: map[string]int64{},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			resource := &ResourcePrioritizer{
				handle:    testinghelpers.NewFakePluginHandle(t, nil, c.existingDecisions...),
				resource:  c.resource,
				algorithm: c.algorithm,
			}

			scoreResult, status := resource.Score(context.TODO(), c.placement, c.clusters)
			scores := scoreResult.Scores
			err := status.AsError()

			if err != nil {
				t.Errorf("Expect no error, but got %v", err)
			}

			if !apiequality.Semantic.DeepEqual(scores, c.expectedScores) {
				t.Errorf("Expect score %v, but got %v", c.expectedScores, scores)
			}
		})
	}
}
