package cmd

import (
	"encoding/json"
	"testing"

	deployapi "github.com/openshift/origin/pkg/deploy/api"
	unidlingapi "github.com/openshift/origin/pkg/unidling/api"

	kapi "k8s.io/kubernetes/pkg/api"
	kerrors "k8s.io/kubernetes/pkg/api/errors"
	kunversioned "k8s.io/kubernetes/pkg/api/unversioned"
	kruntime "k8s.io/kubernetes/pkg/runtime"
	ktypes "k8s.io/kubernetes/pkg/types"

	// install all APIs
	_ "github.com/openshift/origin/pkg/api/install"
	_ "k8s.io/kubernetes/pkg/api/install"
)

func makePod(name, rcName string, t *testing.T) kapi.Pod {
	// this snippet is from kube's code to set the created-by annotation
	// (which itself does not do quite what we want here)

	codec := kapi.Codecs.LegacyCodec(kunversioned.GroupVersion{Group: kapi.GroupName, Version: "v1"})

	createdByRefJson, err := kruntime.Encode(codec, &kapi.SerializedReference{
		Reference: kapi.ObjectReference{
			Kind:      "ReplicationController",
			Name:      rcName,
			Namespace: "somens",
		},
	})

	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	return kapi.Pod{
		ObjectMeta: kapi.ObjectMeta{
			Name:      name,
			Namespace: "somens",
			Annotations: map[string]string{
				kapi.CreatedByAnnotation: string(createdByRefJson),
			},
		},
	}
}

func makeRC(name, dcName, createdByDCName string, t *testing.T) *kapi.ReplicationController {
	rc := kapi.ReplicationController{
		ObjectMeta: kapi.ObjectMeta{
			Name:        name,
			Namespace:   "somens",
			Annotations: make(map[string]string),
		},
	}

	if createdByDCName != "" {
		codec := kapi.Codecs.LegacyCodec(kunversioned.GroupVersion{Group: kapi.GroupName, Version: "v1"})
		createdByRefJson, err := kruntime.Encode(codec, &kapi.SerializedReference{
			Reference: kapi.ObjectReference{
				Kind:      "DeploymentConfig",
				Name:      createdByDCName,
				Namespace: "somens",
			},
		})

		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}

		rc.Annotations[kapi.CreatedByAnnotation] = string(createdByRefJson)
	}

	if dcName != "" {
		rc.Annotations[deployapi.DeploymentConfigAnnotation] = dcName
	}

	return &rc
}

func makePodRef(name string) *kapi.ObjectReference {
	return &kapi.ObjectReference{
		Kind:      "Pod",
		Name:      name,
		Namespace: "somens",
	}
}

func makeRCRef(name string) *kapi.ObjectReference {
	return &kapi.ObjectReference{
		Kind:      "ReplicationController",
		Name:      name,
		Namespace: "somens",
	}
}

func TestFindIdlablesForEndpoints(t *testing.T) {
	endpoints := &kapi.Endpoints{
		Subsets: []kapi.EndpointSubset{
			{
				Addresses: []kapi.EndpointAddress{
					{
						TargetRef: makePodRef("somepod1"),
					},
					{
						TargetRef: makePodRef("somepod2"),
					},
					{
						TargetRef: &kapi.ObjectReference{
							Kind:      "Cheese",
							Name:      "cheddar",
							Namespace: "somens",
						},
					},
				},
			},
			{
				Addresses: []kapi.EndpointAddress{
					{},
					{
						TargetRef: makePodRef("somepod3"),
					},
					{
						TargetRef: makePodRef("somepod4"),
					},
					{
						TargetRef: makePodRef("somepod5"),
					},
					{
						TargetRef: makePodRef("missingpod"),
					},
				},
			},
		},
	}

	pods := map[kapi.ObjectReference]kapi.Pod{
		*makePodRef("somepod1"): makePod("somepod1", "somerc1", t),
		*makePodRef("somepod2"): makePod("somepod2", "somerc2", t),
		*makePodRef("somepod3"): makePod("somepod3", "somerc1", t),
		*makePodRef("somepod4"): makePod("somepod4", "somerc3", t),
		*makePodRef("somepod5"): makePod("somepod5", "somerc4", t),
	}

	getPod := func(ref kapi.ObjectReference) (*kapi.Pod, error) {
		if pod, ok := pods[ref]; ok {
			return &pod, nil
		}
		return nil, kerrors.NewNotFound(kunversioned.GroupResource{Group: kapi.GroupName, Resource: "Pod"}, ref.Name)
	}

	controllers := map[kapi.ObjectReference]kruntime.Object{
		// prefer CreatedByAnnotation to DeploymentConfigAnnotation
		*makeRCRef("somerc1"): makeRC("somerc1", "nonsense-value", "somedc1", t),
		*makeRCRef("somerc2"): makeRC("somerc2", "", "", t),
		*makeRCRef("somerc3"): makeRC("somerc3", "somedc2", "", t),
		*makeRCRef("somerc4"): makeRC("somerc4", "", "somedc2", t),
	}

	getController := func(ref kapi.ObjectReference) (kruntime.Object, error) {
		if controller, ok := controllers[ref]; ok {
			return controller, nil
		}

		// NB: this GroupResource declaration plays fast and loose with various distinctions
		// but is good enough for being an error in a test
		return nil, kerrors.NewNotFound(kunversioned.GroupResource{Group: kapi.GroupName, Resource: ref.Kind}, ref.Name)

	}

	codec := kapi.Codecs.LegacyCodec(kunversioned.GroupVersion{Group: kapi.GroupName, Version: "v1"})
	refSet, err := findScalableResourcesForEndpoints(endpoints, codec, getPod, getController)

	if err != nil {
		t.Fatalf("Unexpected error while finding idlables: %v", err)
	}

	expectedRefs := []unidlingapi.CrossGroupObjectReference{
		{
			Kind: "DeploymentConfig",
			Name: "somedc1",
		},
		{
			Kind: "DeploymentConfig",
			Name: "somedc2",
		},
		{
			Kind: "ReplicationController",
			Name: "somerc2",
		},
	}

	if len(refSet) != len(expectedRefs) {
		t.Errorf("Expected to get somedc1, somedc2, somerc2, instead got %#v", refSet)
	}

	for _, ref := range expectedRefs {
		if _, ok := refSet[ref]; !ok {
			t.Errorf("expected ReplicationController %q to be present, but was not", ref.Name)
		}
	}
}

func TestPairScalesWithIdlables(t *testing.T) {
	oldScaleRefs := []unidlingapi.RecordedScaleReference{
		{
			CrossGroupObjectReference: unidlingapi.CrossGroupObjectReference{
				Kind: "ReplicationController",
				Name: "somerc1",
			},
			Replicas: 5,
		},
		{
			CrossGroupObjectReference: unidlingapi.CrossGroupObjectReference{
				Kind: "DeploymentConfig",
				Name: "somedc1",
			},
			Replicas: 3,
		},
	}

	oldScaleRefBytes, err := json.Marshal(oldScaleRefs)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}
	oldAnnotations := map[string]string{
		unidlingapi.UnidleTargetAnnotation: string(oldScaleRefBytes),
	}

	newRawRefs := map[unidlingapi.CrossGroupObjectReference]struct{}{
		{
			Kind: "ReplicationController",
			Name: "somerc1",
		}: {},
		{
			Kind: "ReplicationController",
			Name: "somerc2",
		}: {},
		{
			Kind: "DeploymentConfig",
			Name: "somedc1",
		}: {},
		{
			Kind: "DeploymentConfig",
			Name: "somedc2",
		}: {},
	}

	scales := map[unidlingapi.CrossGroupObjectReference]int32{
		{
			Kind: "ReplicationController",
			Name: "somerc1",
		}: 2,
		{
			Kind: "ReplicationController",
			Name: "somerc2",
		}: 5,
		{
			Kind: "DeploymentConfig",
			Name: "somedc1",
		}: 0,
		{
			Kind: "DeploymentConfig",
			Name: "somedc2",
		}: 0,
	}

	newScaleRefs, err := pairScalesWithScaleRefs(ktypes.NamespacedName{Name: "somesvc"}, oldAnnotations, newRawRefs, scales)

	expectedScaleRefs := map[unidlingapi.RecordedScaleReference]struct{}{
		{
			CrossGroupObjectReference: unidlingapi.CrossGroupObjectReference{
				Kind: "ReplicationController",
				Name: "somerc1",
			},
			Replicas: 2,
		}: {},
		{
			CrossGroupObjectReference: unidlingapi.CrossGroupObjectReference{
				Kind: "ReplicationController",
				Name: "somerc2",
			},
			Replicas: 5,
		}: {},
		{
			CrossGroupObjectReference: unidlingapi.CrossGroupObjectReference{
				Kind: "DeploymentConfig",
				Name: "somedc1",
			},
			Replicas: 3,
		}: {},
		{
			CrossGroupObjectReference: unidlingapi.CrossGroupObjectReference{
				Kind: "DeploymentConfig",
				Name: "somedc2",
			},
			Replicas: 1,
		}: {},
	}

	if err != nil {
		t.Fatalf("Unexpected error while generating new annotation value: %v", err)
	}

	if len(newScaleRefs) != len(expectedScaleRefs) {
		t.Fatalf("Expected new recorded scale references of %#v, got %#v", expectedScaleRefs, newScaleRefs)
	}

	for _, scaleRef := range newScaleRefs {
		if _, wasPresent := expectedScaleRefs[scaleRef]; !wasPresent {
			t.Errorf("Unexpected recorded scale reference %#v found in the output", scaleRef)
		}
	}
}