package hst_test

import (
	"encoding/json"
	"errors"
	"net"
	"os"
	"reflect"
	"syscall"
	"testing"

	"hakurei.app/hst"
	"hakurei.app/internal/stub"
	"hakurei.app/message"
)

func TestAppError(t *testing.T) {
	t.Parallel()

	testCases := []struct {
		name    string
		err     error
		s       string
		message string
		is, isF error
	}{
		{"message", &hst.AppError{Step: "obtain uid from hsu", Err: stub.UniqueError(0),
			Msg: "the setuid helper is missing: /run/wrappers/bin/hsu"},
			"unique error 0 injected by the test suite",
			"the setuid helper is missing: /run/wrappers/bin/hsu",
			stub.UniqueError(0), os.ErrNotExist},

		{"os.PathError", &hst.AppError{Step: "passthrough os.PathError",
			Err: &os.PathError{Op: "stat", Path: "/proc/nonexistent", Err: os.ErrNotExist}},
			"stat /proc/nonexistent: file does not exist",
			"cannot stat /proc/nonexistent: file does not exist",
			os.ErrNotExist, stub.UniqueError(0xdeadbeef)},

		{"os.LinkError", &hst.AppError{Step: "passthrough os.LinkError",
			Err: &os.LinkError{Op: "link", Old: "/proc/self", New: "/proc/nonexistent", Err: os.ErrNotExist}},
			"link /proc/self /proc/nonexistent: file does not exist",
			"cannot link /proc/self /proc/nonexistent: file does not exist",
			os.ErrNotExist, stub.UniqueError(0xdeadbeef)},

		{"os.SyscallError", &hst.AppError{Step: "passthrough os.SyscallError",
			Err: &os.SyscallError{Syscall: "meow", Err: syscall.ENOSYS}},
			"meow: function not implemented",
			"cannot meow: function not implemented",
			syscall.ENOSYS, syscall.ENOTRECOVERABLE},

		{"net.OpError", &hst.AppError{Step: "passthrough net.OpError",
			Err: &net.OpError{Op: "dial", Net: "cat", Err: net.UnknownNetworkError("cat")}},
			"dial cat: unknown network cat",
			"cannot dial cat: unknown network cat",
			net.UnknownNetworkError("cat"), syscall.ENOTRECOVERABLE},

		{"default", &hst.AppError{Step: "initialise container configuration", Err: stub.UniqueError(1)},
			"unique error 1 injected by the test suite",
			"cannot initialise container configuration: unique error 1 injected by the test suite",
			stub.UniqueError(1), os.ErrInvalid},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			t.Run("error", func(t *testing.T) {
				t.Parallel()
				if got := tc.err.Error(); got != tc.s {
					t.Errorf("Error: %s, want %s", got, tc.s)
				}
			})

			t.Run("message", func(t *testing.T) {
				t.Parallel()
				gotMessage, gotMessageOk := message.GetMessage(tc.err)
				if want := tc.message != "\x00"; gotMessageOk != want {
					t.Errorf("GetMessage: ok = %v, want %v", gotMessage, want)
				}

				if gotMessageOk {
					if gotMessage != tc.message {
						t.Errorf("GetMessage: %s, want %s", gotMessage, tc.message)
					}
				}
			})

			t.Run("is", func(t *testing.T) {
				t.Parallel()
				if !errors.Is(tc.err, tc.is) {
					t.Errorf("Is: unexpected false for %v", tc.is)
				}
				if errors.Is(tc.err, tc.isF) {
					t.Errorf("Is: unexpected true for %v", tc.isF)
				}
			})
		})
	}
}

func TestTemplate(t *testing.T) {
	t.Parallel()

	const want = `{
	"id": "org.chromium.Chromium",
	"enablements": {
		"wayland": true,
		"dbus": true,
		"pipewire": true
	},
	"session_bus": {
		"see": null,
		"talk": [
			"org.freedesktop.Notifications",
			"org.freedesktop.FileManager1",
			"org.freedesktop.ScreenSaver",
			"org.freedesktop.secrets",
			"org.kde.kwalletd5",
			"org.kde.kwalletd6",
			"org.gnome.SessionManager"
		],
		"own": [
			"org.chromium.Chromium.*",
			"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
			"org.mpris.MediaPlayer2.chromium.*"
		],
		"call": {
			"org.freedesktop.portal.*": "*"
		},
		"broadcast": {
			"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
		},
		"filter": true
	},
	"system_bus": {
		"see": null,
		"talk": [
			"org.bluez",
			"org.freedesktop.Avahi",
			"org.freedesktop.UPower"
		],
		"own": null,
		"call": null,
		"broadcast": null,
		"filter": true
	},
	"extra_perms": [
		{
			"ensure": true,
			"path": "/var/lib/hakurei/u0",
			"x": true
		},
		{
			"path": "/var/lib/hakurei/u0/org.chromium.Chromium",
			"r": true,
			"w": true,
			"x": true
		}
	],
	"identity": 9,
	"groups": [
		"video",
		"dialout",
		"plugdev"
	],
	"container": {
		"hostname": "localhost",
		"wait_delay": -1,
		"env": {
			"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
			"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
			"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
		},
		"filesystem": [
			{
				"type": "bind",
				"dst": "/",
				"src": "/var/lib/hakurei/base/org.debian",
				"write": true,
				"special": true
			},
			{
				"type": "bind",
				"dst": "/etc/",
				"src": "/etc/",
				"special": true
			},
			{
				"type": "ephemeral",
				"dst": "/tmp/",
				"write": true,
				"perm": 493
			},
			{
				"type": "overlay",
				"dst": "/nix/store",
				"lower": [
					"/var/lib/hakurei/base/org.nixos/ro-store"
				],
				"upper": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/upper",
				"work": "/var/lib/hakurei/nix/u0/org.chromium.Chromium/rw-store/work"
			},
			{
				"type": "link",
				"dst": "/run/current-system",
				"linkname": "/run/current-system",
				"dereference": true
			},
			{
				"type": "link",
				"dst": "/run/opengl-driver",
				"linkname": "/run/opengl-driver",
				"dereference": true
			},
			{
				"type": "bind",
				"dst": "/data/data/org.chromium.Chromium",
				"src": "/var/lib/hakurei/u0/org.chromium.Chromium",
				"write": true,
				"ensure": true
			},
			{
				"type": "bind",
				"src": "/dev/dri",
				"dev": true,
				"optional": true
			}
		],
		"username": "chronos",
		"shell": "/run/current-system/sw/bin/zsh",
		"home": "/data/data/org.chromium.Chromium",
		"path": "/run/current-system/sw/bin/chromium",
		"args": [
			"chromium",
			"--ignore-gpu-blocklist",
			"--disable-smooth-scrolling",
			"--enable-features=UseOzonePlatform",
			"--ozone-platform=wayland"
		],
		"seccomp_compat": true,
		"devel": true,
		"userns": true,
		"host_net": true,
		"host_abstract": true,
		"tty": true,
		"multiarch": true,
		"map_real_uid": true,
		"noplace": true,
		"device": true,
		"cover_run": true,
		"share_runtime": true,
		"share_tmpdir": true
	}
}`

	t.Run("marshal", func(t *testing.T) {
		t.Parallel()
		if p, err := json.MarshalIndent(hst.Template(), "", "\t"); err != nil {
			t.Fatalf("cannot marshal: %v", err)
		} else if s := string(p); s != want {
			t.Fatalf("Template:\n%s\nwant:\n%s",
				s, want)
		}
	})

	t.Run("unmarshal", func(t *testing.T) {
		t.Parallel()

		var got *hst.Config
		if err := json.Unmarshal([]byte(want), &got); err != nil {
			t.Fatalf("Unmarshal: error = %v", err)
		}

		wantVal := hst.Template()
		wantVal.Container.Flags = hst.FAll
		if !reflect.DeepEqual(got, wantVal) {
			t.Fatalf("Unmarshal: %#v, want %#v", got, wantVal)
		}
	})
}
