// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	"syscall"
	"testing"

	vc "github.com/kata-containers/runtime/virtcontainers"
	"github.com/kata-containers/runtime/virtcontainers/pkg/vcmock"
	"github.com/stretchr/testify/assert"
)

var (
	testKillContainerFuncReturnNil = func(podID, containerID string, signal syscall.Signal, all bool) error {
		return nil
	}

	testStopContainerFuncReturnNil = func(podID, containerID string) (vc.VCContainer, error) {
		return &vcmock.Container{}, nil
	}
)

func TestProcessSignal(t *testing.T) {
	tests := []struct {
		signal string
		valid  bool
		signum syscall.Signal
	}{
		{"SIGDCKBY", false, 0}, //invalid signal
		{"DCKBY", false, 0},    //invalid signal
		{"99999", false, 0},    //invalid signal
		{"SIGTERM", true, syscall.SIGTERM},
		{"TERM", true, syscall.SIGTERM},
		{"15", true, syscall.SIGTERM},
	}

	for _, test := range tests {
		signum, err := processSignal(test.signal)
		if signum != test.signum {
			t.Fatalf("signal received: %d expected signal: %d\n", signum, test.signum)
		}
		if test.valid && err != nil {
			t.Fatalf("signal %s is a valid but a error was received: %s\n", test.signal, err)
		}
		if !test.valid && err == nil {
			t.Fatalf("signal %s is not a valid signal and no error was reported\n", test.signal)
		}
	}
}

func testKillCLIFunctionTerminationSignalSuccessful(t *testing.T, sig string) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StateRunning,
	}

	testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
	testingImpl.StopContainerFunc = testStopContainerFuncReturnNil
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID, sig})

	execCLICommandFunc(assert, killCLICommand, set, false)
}

func TestKillCLIFunctionSigkillSuccessful(t *testing.T) {
	testKillCLIFunctionTerminationSignalSuccessful(t, "SIGKILL")
}

func TestKillCLIFunctionSigtermSuccessful(t *testing.T) {
	testKillCLIFunctionTerminationSignalSuccessful(t, "SIGTERM")
}

func TestKillCLIFunctionNotTerminationSignalSuccessful(t *testing.T) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StateRunning,
	}

	testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID, "SIGUSR1"})

	execCLICommandFunc(assert, killCLICommand, set, false)
}

func TestKillCLIFunctionNoSignalSuccessful(t *testing.T) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StateRunning,
	}

	testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID})

	execCLICommandFunc(assert, killCLICommand, set, false)
}

func TestKillCLIFunctionEnableAllSuccessful(t *testing.T) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StateRunning,
	}

	testingImpl.KillContainerFunc = func(podID, containerID string, signal syscall.Signal, all bool) error {
		if !all {
			return fmt.Errorf("Expecting -all flag = true, Got false")
		}

		return nil
	}
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Bool("all", true, "")
	set.Parse([]string{testContainerID})

	execCLICommandFunc(assert, killCLICommand, set, false)
}

func TestKillCLIFunctionNoContainerIDFailure(t *testing.T) {
	assert := assert.New(t)

	set := flag.NewFlagSet("", 0)

	execCLICommandFunc(assert, killCLICommand, set, true)
}

func TestKillCLIFunctionContainerNotExistFailure(t *testing.T) {
	assert := assert.New(t)

	testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return []vc.PodStatus{}, nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID})

	execCLICommandFunc(assert, killCLICommand, set, true)
}

func TestKillCLIFunctionInvalidSignalFailure(t *testing.T) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StateRunning,
	}

	testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID, "SIGINVALID"})

	execCLICommandFunc(assert, killCLICommand, set, true)
}

func TestKillCLIFunctionInvalidStatePausedFailure(t *testing.T) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StatePaused,
	}

	testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID})

	execCLICommandFunc(assert, killCLICommand, set, true)
}

func TestKillCLIFunctionInvalidStateStoppedFailure(t *testing.T) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StateStopped,
	}

	testingImpl.KillContainerFunc = testKillContainerFuncReturnNil
	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.KillContainerFunc = nil
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID})

	execCLICommandFunc(assert, killCLICommand, set, true)
}

func TestKillCLIFunctionKillContainerFailure(t *testing.T) {
	assert := assert.New(t)

	state := vc.State{
		State: vc.StateRunning,
	}

	testingImpl.ListPodFunc = func() ([]vc.PodStatus, error) {
		return newSingleContainerPodStatusList(testPodID, testContainerID, state, state, map[string]string{}), nil
	}
	defer func() {
		testingImpl.ListPodFunc = nil
	}()

	set := flag.NewFlagSet("", 0)
	set.Parse([]string{testContainerID})

	execCLICommandFunc(assert, killCLICommand, set, true)
}
