package controller

import (
	"testing"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	deployapi "github.com/openshift/origin/pkg/deploy/api"
	deploytest "github.com/openshift/origin/pkg/deploy/controller/test"
)

type testDcDeploymentInterface struct {
	UpdateDeploymentFunc func(deployment *deployapi.Deployment) (*deployapi.Deployment, error)
}

func (i *testDcDeploymentInterface) UpdateDeployment(ctx kapi.Context, deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
	return i.UpdateDeploymentFunc(deployment)
}

type testDcPodInterface struct {
	CreatePodFunc func(pod *kapi.Pod) (*kapi.Pod, error)
	DeletePodFunc func(id string) error
}

func (i *testDcPodInterface) CreatePod(ctx kapi.Context, pod *kapi.Pod) (*kapi.Pod, error) {
	return i.CreatePodFunc(pod)
}

func (i *testDcPodInterface) DeletePod(ctx kapi.Context, id string) error {
	return i.DeletePodFunc(id)
}

func TestHandleNew(t *testing.T) {
	var (
		updatedDeployment *deployapi.Deployment
		createdPod        *kapi.Pod
	)

	controller := &CustomPodDeploymentController{
		DeploymentInterface: &testDcDeploymentInterface{
			UpdateDeploymentFunc: func(deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
				updatedDeployment = deployment
				return deployment, nil
			},
		},
		PodInterface: &testDcPodInterface{
			CreatePodFunc: func(pod *kapi.Pod) (*kapi.Pod, error) {
				createdPod = pod
				return pod, nil
			},
		},
		NextDeployment: func() *deployapi.Deployment {
			deployment := customPodDeployment()
			deployment.Status = deployapi.DeploymentStatusNew
			return deployment
		},
	}

	// Verify pending -> running now that the pod is running
	controller.HandleDeployment()

	if updatedDeployment == nil {
		t.Fatalf("expected an updated deployment")
	}

	if e, a := deployapi.DeploymentStatusPending, updatedDeployment.Status; e != a {
		t.Fatalf("expected updated deployment status %s, got %s", e, a)
	}

	if createdPod == nil {
		t.Fatalf("expected a pod to be created")
	}
}

func TestHandleNewDeploymentWrongType(t *testing.T) {
	controller := &CustomPodDeploymentController{
		DeploymentInterface: &testDcDeploymentInterface{
			UpdateDeploymentFunc: func(deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
				t.Fatalf("Unexpected call to updateDeployment")
				return nil, nil
			},
		},
		PodInterface: &testDcPodInterface{
			CreatePodFunc: func(pod *kapi.Pod) (*kapi.Pod, error) {
				t.Fatalf("Unexpected call to createPod")
				return nil, nil
			},
		},
		NextDeployment: func() *deployapi.Deployment {
			deployment := basicDeployment()
			deployment.Status = deployapi.DeploymentStatusNew
			return deployment
		},
	}

	controller.HandleDeployment()
}

func TestHandlePodRunning(t *testing.T) {
	var updatedDeployment *deployapi.Deployment

	controller := &CustomPodDeploymentController{
		DeploymentInterface: &testDcDeploymentInterface{
			UpdateDeploymentFunc: func(deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
				updatedDeployment = deployment
				return deployment, nil
			},
		},
		PodInterface: &testDcPodInterface{},
		NextDeployment: func() *deployapi.Deployment {
			return nil
		},
		NextPod:         func() *kapi.Pod { return runningPod() },
		DeploymentStore: deploytest.NewFakeDeploymentStore(pendingDeployment()),
	}

	controller.HandlePod()

	if updatedDeployment == nil {
		t.Fatalf("Expected a deployment to be updated")
	}

	if e, a := deployapi.DeploymentStatusRunning, updatedDeployment.Status; e != a {
		t.Fatalf("expected updated deployment status %s, got %s", e, a)
	}
}

func TestHandlePodTerminatedOk(t *testing.T) {
	var updatedDeployment *deployapi.Deployment
	var deletedPodId string

	controller := &CustomPodDeploymentController{
		DeploymentInterface: &testDcDeploymentInterface{
			UpdateDeploymentFunc: func(deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
				updatedDeployment = deployment
				return deployment, nil
			},
		},
		PodInterface: &testDcPodInterface{
			DeletePodFunc: func(id string) error {
				deletedPodId = id
				return nil
			},
		},
		NextDeployment:  func() *deployapi.Deployment { return nil },
		NextPod:         func() *kapi.Pod { return terminatedPod(0) },
		DeploymentStore: deploytest.NewFakeDeploymentStore(runningDeployment()),
	}

	controller.HandlePod()

	if updatedDeployment == nil {
		t.Fatalf("Expected a deployment to be updated")
	}

	if e, a := deployapi.DeploymentStatusComplete, updatedDeployment.Status; e != a {
		t.Fatalf("expected updated deployment status %s, got %s", e, a)
	}

	if len(deletedPodId) == 0 {
		t.Fatalf("expected pod to be deleted")
	}
}

func TestHandlePodTerminatedNotOk(t *testing.T) {
	var updatedDeployment *deployapi.Deployment

	controller := &CustomPodDeploymentController{
		DeploymentInterface: &testDcDeploymentInterface{
			UpdateDeploymentFunc: func(deployment *deployapi.Deployment) (*deployapi.Deployment, error) {
				updatedDeployment = deployment
				return deployment, nil
			},
		},
		PodInterface: &testDcPodInterface{
			DeletePodFunc: func(id string) error {
				t.Fatalf("unexpected delete of pod %s", id)
				return nil
			},
		},
		NextDeployment:  func() *deployapi.Deployment { return nil },
		NextPod:         func() *kapi.Pod { return terminatedPod(1) },
		DeploymentStore: deploytest.NewFakeDeploymentStore(runningDeployment()),
	}

	controller.HandlePod()

	if updatedDeployment == nil {
		t.Fatalf("Expected a deployment to be updated")
	}

	if e, a := deployapi.DeploymentStatusFailed, updatedDeployment.Status; e != a {
		t.Fatalf("expected updated deployment status %s, got %s", e, a)
	}
}

func basicDeployment() *deployapi.Deployment {
	return &deployapi.Deployment{
		TypeMeta: kapi.TypeMeta{ID: "deploy1"},
		Status:   deployapi.DeploymentStatusNew,
		Strategy: deployapi.DeploymentStrategy{
			Type: deployapi.DeploymentStrategyTypeBasic,
		},
		ControllerTemplate: kapi.ReplicationControllerState{
			PodTemplate: kapi.PodTemplate{
				DesiredState: kapi.PodState{
					Manifest: kapi.ContainerManifest{
						Containers: []kapi.Container{
							{
								Name:  "container1",
								Image: "registry:8080/repo1:ref1",
							},
						},
					},
				},
			},
		},
	}
}

func customPodDeployment() *deployapi.Deployment {
	d := basicDeployment()
	d.Strategy = deployapi.DeploymentStrategy{
		Type: deployapi.DeploymentStrategyTypeCustomPod,
		CustomPod: &deployapi.CustomPodDeploymentStrategy{
			Image:       "registry:8080/repo1:ref1",
			Environment: []kapi.EnvVar{},
		},
	}

	return d
}

func pendingDeployment() *deployapi.Deployment {
	d := customPodDeployment()
	d.Status = deployapi.DeploymentStatusPending
	return d
}

func runningDeployment() *deployapi.Deployment {
	d := customPodDeployment()
	d.Status = deployapi.DeploymentStatusRunning
	return d
}

func basicPod() *kapi.Pod {
	return &kapi.Pod{
		CurrentState: kapi.PodState{
			Info: kapi.PodInfo{
				"container1": kapi.ContainerStatus{},
			},
		},
		Labels: map[string]string{
			"deployment": "1234",
		},
	}
}

func terminatedPod(exitCode int) *kapi.Pod {
	p := basicPod()
	p.CurrentState.Status = kapi.PodTerminated
	p.CurrentState.Info["container1"] = kapi.ContainerStatus{
		State: kapi.ContainerState{
			Termination: &kapi.ContainerStateTerminated{
				ExitCode: exitCode,
			},
		},
	}

	return p
}

func runningPod() *kapi.Pod {
	p := basicPod()
	p.CurrentState.Status = kapi.PodRunning
	return p
}