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
}