package fs

import (
	"reflect"
	"slices"
	"testing"

	"github.com/opencontainers/cgroups"
)

const (
	cpuAcctUsageContents       = "12262454190222160"
	cpuAcctUsagePerCPUContents = "1564936537989058 1583937096487821 1604195415465681 1596445226820187 1481069084155629 1478735613864327 1477610593414743 1476362015778086"
	cpuAcctStatContents        = "user 452278264\nsystem 291429664"
	cpuAcctUsageAll            = `cpu user system
	0 962250696038415 637727786389114
	1 981956408513304 638197595421064
	2 1002658817529022 638956774598358
	3 994937703492523 637985531181620
	4 874843781648690 638837766495476
	5 872544369885276 638763309884944
	6 870104915696359 640081778921247
	7 870202363887496 638716766259495
	`
)

func TestCpuacctStats(t *testing.T) {
	path := tempDir(t, "cpuacct")
	writeFileContents(t, path, map[string]string{
		"cpuacct.usage":        cpuAcctUsageContents,
		"cpuacct.usage_percpu": cpuAcctUsagePerCPUContents,
		"cpuacct.stat":         cpuAcctStatContents,
		"cpuacct.usage_all":    cpuAcctUsageAll,
	})

	cpuacct := &CpuacctGroup{}
	actualStats := *cgroups.NewStats()
	err := cpuacct.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	expectedStats := cgroups.CpuUsage{
		TotalUsage: uint64(12262454190222160),
		PercpuUsage: []uint64{
			1564936537989058, 1583937096487821, 1604195415465681, 1596445226820187,
			1481069084155629, 1478735613864327, 1477610593414743, 1476362015778086,
		},
		PercpuUsageInKernelmode: []uint64{
			637727786389114, 638197595421064, 638956774598358, 637985531181620,
			638837766495476, 638763309884944, 640081778921247, 638716766259495,
		},
		PercpuUsageInUsermode: []uint64{
			962250696038415, 981956408513304, 1002658817529022, 994937703492523,
			874843781648690, 872544369885276, 870104915696359, 870202363887496,
		},
		UsageInKernelmode: (uint64(291429664) * nsInSec) / clockTicks,
		UsageInUsermode:   (uint64(452278264) * nsInSec) / clockTicks,
	}

	if !reflect.DeepEqual(expectedStats, actualStats.CpuStats.CpuUsage) {
		t.Errorf("Expected CPU usage %#v but found %#v\n",
			expectedStats, actualStats.CpuStats.CpuUsage)
	}
}

func TestCpuacctStatsWithoutUsageAll(t *testing.T) {
	path := tempDir(t, "cpuacct")
	writeFileContents(t, path, map[string]string{
		"cpuacct.usage":        cpuAcctUsageContents,
		"cpuacct.usage_percpu": cpuAcctUsagePerCPUContents,
		"cpuacct.stat":         cpuAcctStatContents,
	})

	cpuacct := &CpuacctGroup{}
	actualStats := *cgroups.NewStats()
	err := cpuacct.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	expectedStats := cgroups.CpuUsage{
		TotalUsage: uint64(12262454190222160),
		PercpuUsage: []uint64{
			1564936537989058, 1583937096487821, 1604195415465681, 1596445226820187,
			1481069084155629, 1478735613864327, 1477610593414743, 1476362015778086,
		},
		PercpuUsageInKernelmode: []uint64{},
		PercpuUsageInUsermode:   []uint64{},
		UsageInKernelmode:       (uint64(291429664) * nsInSec) / clockTicks,
		UsageInUsermode:         (uint64(452278264) * nsInSec) / clockTicks,
	}

	if !reflect.DeepEqual(expectedStats, actualStats.CpuStats.CpuUsage) {
		t.Errorf("Expected CPU usage %#v but found %#v\n",
			expectedStats, actualStats.CpuStats.CpuUsage)
	}
}

// TestCpuacctUsageAllExtra checks that if there are extra columns
// in cpuacct.usage_all, yet the first three are as expected, we
// can still parse it successfully.
func TestCpuacctUsageAllExtra(t *testing.T) {
	path := tempDir(t, "cpuacct")
	// These extra columns come from the custom Tencent kernel,
	// see https://github.com/OpenCloudOS/TencentOS-kernel-0/commit/0b667819c3aaa9c8ac904c6b03256b86be93fc05
	err := cgroups.WriteFile(path, "cpuacct.usage_all",
		`cpu user system bt_user bt_system
0 962250696038415 637727786389114 0 0
1 981956408513304 638197595421064 0 0
`)
	if err != nil {
		t.Fatal(err)
	}

	system, user, err := getPercpuUsageInModes(path)
	if err != nil {
		t.Fatal(err)
	}

	expUser := []uint64{962250696038415, 981956408513304}
	expSystem := []uint64{637727786389114, 638197595421064}
	if !slices.Equal(user, expUser) {
		t.Fatalf("unexpected user data (want %+v, got %+v", expUser, user)
	}
	if !slices.Equal(system, expSystem) {
		t.Fatalf("unexpected system data (want %+v, got +%v)", expSystem, system)
	}
}

func TestCpuacctUsageAllBad(t *testing.T) {
	path := tempDir(t, "cpuacct")
	err := cgroups.WriteFile(path, "cpuacct.usage_all",
		`cpu bad data fields
0 1 2
`)
	if err != nil {
		t.Fatal(err)
	}

	_, _, err = getPercpuUsageInModes(path)
	t.Log(err)
	if err == nil {
		t.Fatal("want error, got nil")
	}
}

func BenchmarkGetCpuUsageBreakdown(b *testing.B) {
	path := tempDir(b, "cpuacct")
	writeFileContents(b, path, map[string]string{
		"cpuacct.stat": cpuAcctStatContents,
	})

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _, err := getCpuUsageBreakdown(path)
		if err != nil {
			b.Fatal(err)
		}
	}
}
