package kmod

import (
	"bytes"
	"log"
	"os"
	"path/filepath"
	"testing"

	"golang.org/x/sys/unix"
)

func createFiles() (string, []module) {
	writeFile := func(path, text string) {
		if err := os.WriteFile(path, []byte(text), 0o600); err != nil {
			log.Fatal(err)
		}
	}
	var u unix.Utsname
	if err := unix.Uname(&u); err != nil {
		log.Fatal(err)
	}
	kernelVersion := string(u.Release[:bytes.IndexByte(u.Release[:], 0)])

	rootdir, err := os.MkdirTemp("", "kmod")
	if err != nil {
		log.Fatal(err)
	}

	moddir := filepath.Join(rootdir, kernelVersion)
	if err := os.Mkdir(moddir, 0o700); err != nil {
		log.Fatal(err)
	}

	text := "kernel/a/foo.ko:\n"
	text += "kernel/a/bar.ko: kernel/a/foo.ko\n"
	text += "kernel/a/baz.ko: kernel/a/bar.ko kernel/a/foo.ko\n"
	writeFile(filepath.Join(moddir, "modules.dep"), text)

	text = "kernel/a/foo_bi.ko\n"
	text += "kernel/a/bar-bi.ko.gz\n"
	writeFile(filepath.Join(moddir, "modules.builtin"), text)

	text = "alias ignore ignore\n"
	text += "alias foo_a foo\n"
	text += "alias bar-a bar\n"
	writeFile(filepath.Join(moddir, "modules.alias"), text)

	text = "options foo foo1 foo2\n"
	text += "options bar bar1\n"
	writeFile(filepath.Join(rootdir, "modprobe.conf"), text)

	modules := []module{
		{name: "foo", path: "kernel/a/foo.ko", params: "foo1 foo2"},
		{name: "bar", path: "kernel/a/bar.ko", params: "bar1"},
		{name: "baz", path: "kernel/a/baz.ko"},
	}
	return rootdir, modules
}

func TestIsBuiltin(t *testing.T) {
	rootdir, _ := createFiles()
	defer os.RemoveAll(rootdir)

	k, err := New(SetRootDir(rootdir))
	if err != nil {
		t.Fatal(err)
	}

	tests := []struct {
		name string
		want bool
	}{
		{"", false},
		{"none", false},
		{"foo_bi", true},
		{"bar_bi", true},
	}

	for _, tt := range tests {
		ok, err := k.isBuiltin(tt.name)
		if err != nil {
			t.Fatal(err)
		}
		if ok != tt.want {
			t.Fatalf("%s want:%t got:%t", tt.name, tt.want, ok)
		}
	}
}

func TestApplyConfig(t *testing.T) {
	rootdir, modules := createFiles()
	defer os.RemoveAll(rootdir)

	path := filepath.Join(rootdir, "modprobe.conf")
	k, err := New(SetRootDir(rootdir), SetConfigFile(path))
	if err != nil {
		t.Fatal(err)
	}

	var tests []module
	for _, m := range modules {
		m.params = ""
		tests = append(tests, m)
	}

	if err := k.applyConfig(tests); err != nil {
		t.Fatal(err)
	}

	for i := range tests {
		if modules[i] != tests[i] {
			t.Fatalf("want:%v got:%v", modules[i], tests[i])
		}
	}
}

func TestOptionIgnoreStatus(t *testing.T) {
	rootdir, _ := createFiles()
	defer os.RemoveAll(rootdir)

	{
		k, err := New(SetRootDir(rootdir))
		if err != nil {
			t.Fatal(err)
		}

		want := unloaded
		status, err := k.modStatus("foo")
		if err != nil {
			t.Fatal(err)
		}
		if status != want {
			t.Fatalf("want:%v got:%v", want, status)
		}
	}

	{
		k, err := New(SetRootDir(rootdir), SetIgnoreStatus())
		if err != nil {
			t.Fatal(err)
		}

		want := unknown
		status, err := k.modStatus("foo")
		if err != nil {
			t.Fatal(err)
		}
		if status != want {
			t.Fatalf("want:%v got:%v", want, status)
		}
	}
}

func TestDependencies(t *testing.T) {
	rootdir, modules := createFiles()
	defer os.RemoveAll(rootdir)

	k, err := New(SetRootDir(rootdir))
	if err != nil {
		t.Fatal(err)
	}

	list, err := k.Dependencies("baz")
	if err != nil {
		t.Fatal(err)
	}

	if len(list) != len(modules) {
		t.Fatalf("len(dependency list) want: %d got:%d", len(modules), len(list))
	}

	for i := len(modules) - 2; i <= 0; i-- {
		if modules[i].path != list[i] {
			t.Fatalf("want:%v got:%v", modules[i].path, list[i])
		}
	}
}