package executor

import (
	"log"
	"os"
	"strings"
	"testing"
	"time"

	docker "github.com/fsouza/go-dockerclient"

	cstructs "github.com/hashicorp/nomad/client/driver/structs"
	"github.com/hashicorp/nomad/client/testutil"
)

func TestExecScriptCheckNoIsolation(t *testing.T) {
	check := &ExecScriptCheck{
		id:          "foo",
		cmd:         "/bin/echo",
		args:        []string{"hello", "world"},
		taskDir:     "/tmp",
		FSIsolation: false,
	}

	res := check.Run()
	expectedOutput := "hello world"
	expectedExitCode := 0
	if res.Err != nil {
		t.Fatalf("err: %v", res.Err)
	}
	if strings.TrimSpace(res.Output) != expectedOutput {
		t.Fatalf("output expected: %v, actual: %v", expectedOutput, res.Output)
	}

	if res.ExitCode != expectedExitCode {
		t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode)
	}
}

func TestExecScriptCheckWithIsolation(t *testing.T) {
	testutil.ExecCompatible(t)

	execCmd := ExecCommand{Cmd: "/bin/echo", Args: []string{"hello world"}}
	ctx := testExecutorContext(t)
	defer ctx.AllocDir.Destroy()

	execCmd.FSIsolation = true
	execCmd.ResourceLimits = true
	execCmd.User = cstructs.DefaultUnpriviledgedUser

	executor := NewExecutor(log.New(os.Stdout, "", log.LstdFlags))
	_, err := executor.LaunchCmd(&execCmd, ctx)
	if err != nil {
		t.Fatalf("error in launching command: %v", err)
	}

	check := &ExecScriptCheck{
		id:          "foo",
		cmd:         "/bin/echo",
		args:        []string{"hello", "world"},
		taskDir:     ctx.AllocDir.TaskDirs["web"],
		FSIsolation: true,
	}

	res := check.Run()
	expectedOutput := "hello world"
	expectedExitCode := 0
	if res.Err != nil {
		t.Fatalf("err: %v", res.Err)
	}
	if strings.TrimSpace(res.Output) != expectedOutput {
		t.Fatalf("output expected: %v, actual: %v", expectedOutput, res.Output)
	}

	if res.ExitCode != expectedExitCode {
		t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode)
	}
}

func TestDockerScriptCheck(t *testing.T) {
	if !testutil.DockerIsConnected(t) {
		return
	}
	client, err := docker.NewClientFromEnv()
	if err != nil {
		t.Fatalf("error creating docker client: %v", err)
	}

	if err := client.PullImage(docker.PullImageOptions{Repository: "busybox", Tag: "latest"},
		docker.AuthConfiguration{}); err != nil {
		t.Fatalf("error pulling redis: %v", err)
	}

	container, err := client.CreateContainer(docker.CreateContainerOptions{
		Config: &docker.Config{
			Image: "busybox",
			Cmd:   []string{"/bin/sleep", "1000"},
		},
	})
	if err != nil {
		t.Fatalf("error creating container: %v", err)
	}
	defer removeContainer(client, container.ID)

	if err := client.StartContainer(container.ID, &docker.HostConfig{}); err != nil {
		t.Fatalf("error starting container", err)
	}

	check := &DockerScriptCheck{
		id:          "1",
		interval:    5 * time.Second,
		containerID: container.ID,
		logger:      log.New(os.Stdout, "", log.LstdFlags),
		cmd:         "/bin/echo",
		args:        []string{"hello", "world"},
	}

	res := check.Run()
	expectedOutput := "hello world"
	expectedExitCode := 0
	if res.Err != nil {
		t.Fatalf("err: %v", res.Err)
	}
	if strings.TrimSpace(res.Output) != expectedOutput {
		t.Fatalf("output expected: %v, actual: %v", expectedOutput, res.Output)
	}

	if res.ExitCode != expectedExitCode {
		t.Fatalf("exitcode expected: %v, actual: %v", expectedExitCode, res.ExitCode)
	}
}

// removeContainer kills and removes a container
func removeContainer(client *docker.Client, containerID string) {
	client.KillContainer(docker.KillContainerOptions{ID: containerID})
	client.RemoveContainer(docker.RemoveContainerOptions{ID: containerID, Force: true})
}
