//go:build windows

package main

import (
	"context"
	"math/rand"
	"strconv"
	"testing"
	"time"

	"github.com/containerd/errdefs"
)

func setupTestHcsTask(t *testing.T) (*hcsTask, *testShimExec, *testShimExec) {
	t.Helper()
	initExec := newTestShimExec(t.Name(), t.Name(), int(rand.Int31()))
	lt := &hcsTask{
		events: newFakePublisher(),
		id:     t.Name(),
		init:   initExec,
		closed: make(chan struct{}),
	}
	secondExecID := strconv.Itoa(rand.Int())
	secondExec := newTestShimExec(t.Name(), secondExecID, int(rand.Int31()))
	lt.execs.Store(secondExecID, secondExec)
	return lt, initExec, secondExec
}

func Test_hcsTask_ID(t *testing.T) {
	lt, _, _ := setupTestHcsTask(t)

	if lt.ID() != t.Name() {
		t.Fatalf("expect ID: '%s', got: '%s'", t.Name(), lt.ID())
	}
}

func Test_hcsTask_GetExec_Empty_Success(t *testing.T) {
	lt, i, _ := setupTestHcsTask(t)

	e, err := lt.GetExec("")
	if err != nil {
		t.Fatalf("should not have failed with error: %v", err)
	}
	if i != e {
		t.Fatal("should of returned the init exec on empty")
	}
}

func Test_hcsTask_GetExec_UnknownExecID_Error(t *testing.T) {
	lt, _, _ := setupTestHcsTask(t)

	e, err := lt.GetExec("shouldnotmatch")

	verifyExpectedError(t, e, err, errdefs.ErrNotFound)
}

func Test_hcsTask_GetExec_2ndID_Success(t *testing.T) {
	lt, _, second := setupTestHcsTask(t)

	e, err := lt.GetExec(second.id)
	if err != nil {
		t.Fatalf("should not have failed with error: %v", err)
	}
	if second != e {
		t.Fatal("should of returned the second exec")
	}
}

func Test_hcsTask_KillExec_UnknownExecID_Error(t *testing.T) {
	lt, _, _ := setupTestHcsTask(t)

	err := lt.KillExec(context.TODO(), "thisshouldnotmatch", 0xf, false)

	verifyExpectedError(t, nil, err, errdefs.ErrNotFound)
}

func Test_hcsTask_KillExec_InitExecID_Unexited2ndExec_Success(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	err := lt.KillExec(context.TODO(), "", 0xf, false)
	if err != nil {
		t.Fatalf("should not have failed, got: %v", err)
	}
	if init.state != shimExecStateExited {
		t.Fatalf("init should be in exited state got: %v", init.state)
	}
	// A real platform would take this down when the pid namespace or silo goes
	// down. For the test verify the shim did not issue the signal.
	if second.state != shimExecStateCreated {
		t.Fatalf("2nd exec should be in created state, got: %v", second.state)
	}
}

func Test_hcsTask_KillExec_InitExecID_All_Success(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	err := lt.KillExec(context.TODO(), "", 0xf, true)
	if err != nil {
		t.Fatalf("should not have failed, got: %v", err)
	}
	if init.state != shimExecStateExited {
		t.Fatalf("init should be in exited state got: %v", init.state)
	}
	if second.state != shimExecStateExited {
		t.Fatalf("2nd exec should be in exited state got: %v", second.state)
	}
}

func Test_hcsTask_KillExec_2ndExecID_Success(t *testing.T) {
	lt, _, second := setupTestHcsTask(t)

	err := lt.KillExec(context.TODO(), second.id, 0xf, false)
	if err != nil {
		t.Fatalf("should not have failed, got: %v", err)
	}
	if second.state != shimExecStateExited {
		t.Fatalf("2nd exec should be in exited state got: %v", second.state)
	}
}

func Test_hcsTask_KillExec_2ndExecID_All_Error(t *testing.T) {
	lt, _, second := setupTestHcsTask(t)

	err := lt.KillExec(context.TODO(), second.id, 0xf, true)

	verifyExpectedError(t, nil, err, errdefs.ErrFailedPrecondition)
}

func verifyDeleteFailureValues(t *testing.T, pid int, status uint32, at time.Time) {
	t.Helper()
	if pid != 0 {
		t.Fatalf("pid expected '0' got: '%d'", pid)
	}
	if status != 0 {
		t.Fatalf("status expected '0' got: '%d'", status)
	}
	if !at.IsZero() {
		t.Fatalf("at expected 'zero' got: '%v'", at)
	}
}

func verifyDeleteSuccessValues(t *testing.T, pid int, status uint32, at time.Time, e *testShimExec) {
	t.Helper()
	if pid != e.pid {
		t.Fatalf("pid expected '%d' got: '%d'", e.pid, pid)
	}
	if status != e.status {
		t.Fatalf("status expected '%d' got: '%d'", e.status, status)
	}
	if !at.Equal(e.at) {
		t.Fatalf("at expected '%v' got: '%v'", e.at, at)
	}
}

func Test_hcsTask_DeleteExec_UnknownExecID_Error(t *testing.T) {
	lt, _, _ := setupTestHcsTask(t)

	pid, status, at, err := lt.DeleteExec(context.TODO(), "thisshouldnotmatch")
	verifyExpectedError(t, nil, err, errdefs.ErrNotFound)
	verifyDeleteFailureValues(t, pid, status, at)
}

func Test_hcsTask_DeleteExec_InitExecID_CreatedState_Success(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)
	// remove the 2nd exec so we just check without it.
	lt.execs.Delete(second.id)

	// Simulate waitInitExit() closing the host
	close(lt.closed)
	// try to delete the init exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), "")

	if err != nil {
		t.Fatalf("expected nil err got: %v", err)
	}
	verifyDeleteSuccessValues(t, pid, status, at, init)
}

func Test_hcsTask_DeleteExec_InitExecID_RunningState_Error(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)
	// remove the 2nd exec so we just check without it.
	lt.execs.Delete(second.id)

	// Start the init exec
	_ = init.Start(context.TODO())

	// Simulate waitInitExit() closing the host
	close(lt.closed)
	// try to delete the init exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), "")

	verifyExpectedError(t, nil, err, errdefs.ErrFailedPrecondition)
	verifyDeleteFailureValues(t, pid, status, at)
}

func Test_hcsTask_DeleteExec_InitExecID_ExitedState_Success(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)
	// remove the 2nd exec so we just check without it.
	lt.execs.Delete(second.id)

	_ = init.Kill(context.TODO(), 0xf)

	// Simulate waitInitExit() closing the host
	close(lt.closed)
	// try to delete the init exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), "")

	if err != nil {
		t.Fatalf("expected nil err got: %v", err)
	}
	verifyDeleteSuccessValues(t, pid, status, at, init)
}

func Test_hcsTask_DeleteExec_InitExecID_2ndExec_CreatedState_Error(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	// start the init exec (required to have 2nd exec)
	_ = init.Start(context.TODO())

	// Simulate waitInitExit() closing the host
	close(lt.closed)
	// try to delete the init exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), "")

	verifyExpectedError(t, nil, err, errdefs.ErrFailedPrecondition)
	verifyDeleteFailureValues(t, pid, status, at)
	if second.state != shimExecStateExited {
		t.Fatalf("2nd exec should be in exited state, got: %v", second.state)
	}
}

func Test_hcsTask_DeleteExec_InitExecID_2ndExec_RunningState_Error(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	// start the init exec (required to have 2nd exec)
	_ = init.Start(context.TODO())

	// put the 2nd exec into the running state
	_ = second.Start(context.TODO())

	// Simulate waitInitExit() closing the host
	close(lt.closed)
	// try to delete the init exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), "")

	verifyExpectedError(t, nil, err, errdefs.ErrFailedPrecondition)
	verifyDeleteFailureValues(t, pid, status, at)
	if second.state != shimExecStateExited {
		t.Fatalf("2nd exec should be in exited state, got: %v", second.state)
	}
}

func Test_hcsTask_DeleteExec_InitExecID_2ndExec_ExitedState_Success(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	// put the init exec into the exited state
	_ = init.Kill(context.TODO(), 0xf)
	// put the 2nd exec into the exited state
	_ = second.Kill(context.TODO(), 0xf)

	// Simulate waitInitExit() closing the host
	close(lt.closed)
	// try to delete the init exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), "")

	if err != nil {
		t.Fatalf("expected nil err got: %v", err)
	}
	verifyDeleteSuccessValues(t, pid, status, at, init)
}

func Test_hcsTask_DeleteExec_2ndExecID_CreatedState_Success(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	// start the init exec (required to have 2nd exec)
	_ = init.Start(context.TODO())

	// try to delete the 2nd exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), second.id)

	if err != nil {
		t.Fatalf("expected nil err got: %v", err)
	}
	verifyDeleteSuccessValues(t, pid, status, at, second)
}

func Test_hcsTask_DeleteExec_2ndExecID_RunningState_Error(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	// start the init exec (required to have 2nd exec)
	_ = init.Start(context.TODO())

	// put the 2nd exec into the running state
	_ = second.Start(context.TODO())

	// try to delete the 2nd exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), second.id)

	verifyExpectedError(t, nil, err, errdefs.ErrFailedPrecondition)
	verifyDeleteFailureValues(t, pid, status, at)
}

func Test_hcsTask_DeleteExec_2ndExecID_ExitedState_Success(t *testing.T) {
	lt, init, second := setupTestHcsTask(t)

	// start the init exec (required to have 2nd exec)
	_ = init.Kill(context.TODO(), 0xf)

	// put the 2nd exec into the exited state
	_ = second.Kill(context.TODO(), 0xf)

	// try to delete the 2nd exec
	pid, status, at, err := lt.DeleteExec(context.TODO(), second.id)

	if err != nil {
		t.Fatalf("expected nil err got: %v", err)
	}
	verifyDeleteSuccessValues(t, pid, status, at, second)
}
