package libcontainer

import (
	"errors"
	"os"
	"path/filepath"
	"reflect"
	"testing"

	"github.com/opencontainers/cgroups"
	"github.com/opencontainers/runc/libcontainer/configs"
	"github.com/opencontainers/runc/libcontainer/utils"
	"github.com/opencontainers/runtime-spec/specs-go"
)

func TestFactoryLoadNotExists(t *testing.T) {
	stateDir := t.TempDir()
	_, err := Load(stateDir, "nocontainer")
	if err == nil {
		t.Fatal("expected nil error loading non-existing container")
	}
	if !errors.Is(err, ErrNotExist) {
		t.Fatalf("expected ErrNotExist, got %v", err)
	}
}

func TestFactoryLoadContainer(t *testing.T) {
	root := t.TempDir()
	// setup default container config and state for mocking
	var (
		id            = "1"
		expectedHooks = configs.Hooks{
			configs.Prestart: configs.HookList{
				configs.CommandHook{Command: &configs.Command{Path: "prestart-hook"}},
			},
			configs.Poststart: configs.HookList{
				configs.CommandHook{Command: &configs.Command{Path: "poststart-hook"}},
			},
			configs.Poststop: configs.HookList{
				unserializableHook{},
				configs.CommandHook{Command: &configs.Command{Path: "poststop-hook"}},
			},
		}
		expectedConfig = &configs.Config{
			Rootfs: "/mycontainer/root",
			Hooks:  expectedHooks,
			Cgroups: &cgroups.Cgroup{
				Resources: &cgroups.Resources{},
			},
		}
		expectedState = &State{
			BaseState: BaseState{
				InitProcessPid: 1024,
				Config:         *expectedConfig,
			},
		}
	)
	if err := os.Mkdir(filepath.Join(root, id), 0o700); err != nil {
		t.Fatal(err)
	}
	if err := marshal(filepath.Join(root, id, stateFilename), expectedState); err != nil {
		t.Fatal(err)
	}
	container, err := Load(root, id)
	if err != nil {
		t.Fatal(err)
	}
	if container.ID() != id {
		t.Fatalf("expected container id %q but received %q", id, container.ID())
	}
	config := container.Config()
	if config.Rootfs != expectedConfig.Rootfs {
		t.Fatalf("expected rootfs %q but received %q", expectedConfig.Rootfs, config.Rootfs)
	}
	expectedHooks[configs.Poststop] = expectedHooks[configs.Poststop][1:] // expect unserializable hook to be skipped
	if !reflect.DeepEqual(config.Hooks, expectedHooks) {
		t.Fatalf("expects hooks %q but received %q", expectedHooks, config.Hooks)
	}
	if container.initProcess.pid() != expectedState.InitProcessPid {
		t.Fatalf("expected init pid %d but received %d", expectedState.InitProcessPid, container.initProcess.pid())
	}
}

func marshal(path string, v any) error {
	f, err := os.Create(path)
	if err != nil {
		return err
	}
	defer f.Close()
	return utils.WriteJSON(f, v)
}

type unserializableHook struct{}

func (unserializableHook) Run(*specs.State) error {
	return nil
}
