//go:build windows

package main

import (
	"context"
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/stats"
	v1 "github.com/containerd/cgroups/stats/v1"
	"github.com/containerd/containerd/runtime/v2/task"
	"github.com/pkg/errors"
)

func verifyExpectedError(t *testing.T, resp interface{}, actual, expected error) {
	t.Helper()
	if actual == nil || errors.Cause(actual) != expected || !errors.Is(actual, expected) {
		t.Fatalf("expected error: %v, got: %v", expected, actual)
	}

	isnil := false
	ty := reflect.TypeOf(resp)
	if ty == nil {
		isnil = true
	} else {
		isnil = reflect.ValueOf(resp).IsNil()
	}
	if !isnil {
		t.Fatalf("expect nil response for error return, got: %v", resp)
	}
}

func verifyExpectedStats(t *testing.T, isWCOW, ownsHost bool, s *stats.Statistics) {
	t.Helper()
	if isWCOW {
		verifyExpectedWindowsContainerStatistics(t, s.GetWindows())
	} else {
		verifyExpectedCgroupMetrics(t, s.GetLinux())
	}
	if ownsHost {
		verifyExpectedVirtualMachineStatistics(t, s.VM)
	}
}

func verifyExpectedWindowsContainerStatistics(t *testing.T, w *stats.WindowsContainerStatistics) {
	t.Helper()
	if w == nil {
		t.Fatal("expected non-nil WindowsContainerStatistics")
	}
	if w.UptimeNS != 100 {
		t.Fatalf("expected WindowsContainerStatistics.UptimeNS == 100, got: %d", w.UptimeNS)
	}
	if w.Processor == nil {
		t.Fatal("expected non-nil WindowsContainerStatistics.Processor")
	}
	if w.Processor.TotalRuntimeNS != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Processor.TotalRuntimeNS == 100, got: %d", w.Processor.TotalRuntimeNS)
	}
	if w.Processor.RuntimeUserNS != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Processor.RuntimeUserNS == 100, got: %d", w.Processor.RuntimeUserNS)
	}
	if w.Processor.RuntimeKernelNS != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Processor.RuntimeKernelNS == 100, got: %d", w.Processor.RuntimeKernelNS)
	}
	if w.Memory == nil {
		t.Fatal("expected non-nil WindowsContainerStatistics.Memory")
	}
	if w.Memory.MemoryUsageCommitBytes != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Memory.MemoryUsageCommitBytes == 100, got: %d", w.Memory.MemoryUsageCommitBytes)
	}
	if w.Memory.MemoryUsageCommitPeakBytes != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Memory.MemoryUsageCommitPeakBytes == 100, got: %d", w.Memory.MemoryUsageCommitPeakBytes)
	}
	if w.Memory.MemoryUsagePrivateWorkingSetBytes != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Memory.MemoryUsagePrivateWorkingSetBytes == 100, got: %d", w.Memory.MemoryUsagePrivateWorkingSetBytes)
	}
	if w.Storage == nil {
		t.Fatal("expected non-nil WindowsContainerStatistics.Memory")
	}
	if w.Storage.ReadCountNormalized != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Storage.ReadCountNormalized == 100, got: %d", w.Storage.ReadCountNormalized)
	}
	if w.Storage.ReadSizeBytes != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Storage.ReadSizeBytes == 100, got: %d", w.Storage.ReadSizeBytes)
	}
	if w.Storage.WriteCountNormalized != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Storage.WriteCountNormalized == 100, got: %d", w.Storage.WriteCountNormalized)
	}
	if w.Storage.WriteSizeBytes != 100 {
		t.Fatalf("expected WindowsContainerStatistics.Storage.WriteSizeBytes == 100, got: %d", w.Storage.WriteSizeBytes)
	}
}

func verifyExpectedCgroupMetrics(t *testing.T, v *v1.Metrics) {
	t.Helper()
	if v == nil {
		t.Fatal("expected non-nil cgroups Metrics")
	}
	if v.CPU == nil {
		t.Fatal("expected non-nil Metrics.CPU")
	}
	if v.CPU.Usage.Total != 100 {
		t.Fatalf("Expected Metrics.CPU.Usage == 100, got: %d", v.CPU.Usage)
	}
	if v.Memory == nil {
		t.Fatal("expected non-nil Metrics.Memory")
	}
	if v.Memory.TotalInactiveFile != 100 {
		t.Fatalf("Expected Metrics.Memory.TotalInactiveFile == 100, got: %d", v.Memory.TotalInactiveFile)
	}
	if v.Memory.Usage == nil {
		t.Fatal("expected non-nil Metrics.Memory.Usage")
	}
	if v.Memory.Usage.Usage != 200 {
		t.Fatalf("Expected Metrics.Memory.Usage.Usage == 200, got: %d", v.Memory.Usage.Usage)
	}
}

func verifyExpectedVirtualMachineStatistics(t *testing.T, v *stats.VirtualMachineStatistics) {
	t.Helper()
	if v == nil {
		t.Fatal("expected non-nil VirtualMachineStatistics")
	}
	if v.Processor == nil {
		t.Fatal("expected non-nil VirtualMachineStatistics.Processor")
	}
	if v.Processor.TotalRuntimeNS != 100 {
		t.Fatalf("expected VirtualMachineStatistics.Processor.TotalRuntimeNS == 100, got: %d", v.Processor.TotalRuntimeNS)
	}
	if v.Memory == nil {
		t.Fatal("expected non-nil VirtualMachineStatistics.Memory")
	}
	if v.Memory.WorkingSetBytes != 100 {
		t.Fatalf("expected VirtualMachineStatistics.Memory.WorkingSetBytes == 100, got: %d", v.Memory.WorkingSetBytes)
	}
}

func Test_Service_shutdownInternal(t *testing.T) {
	for _, now := range []bool{true, false} {
		t.Run(fmt.Sprintf("%s_Now_%t", t.Name(), now), func(t *testing.T) {
			s, err := NewService(WithTID(t.Name()))
			if err != nil {
				t.Fatal(err)
			}

			if s.IsShutdown() {
				t.Fatal("service prematurely shutdown")
			}

			_, err = s.shutdownInternal(context.Background(), &task.ShutdownRequest{
				ID:  s.tid,
				Now: now,
			})
			if err != nil {
				t.Fatalf("could not shut down service: %v", err)
			}

			tm := time.NewTimer(5 * time.Millisecond)
			select {
			case <-tm.C:
				t.Fatalf("shutdown channel did not close")
			case <-s.Done():
				tm.Stop()
			}

			if !s.IsShutdown() {
				t.Fatal("service did not shutdown")
			}
		})
	}
}
