package hst_test

import (
	"reflect"
	"testing"

	"hakurei.app/fhs"
	"hakurei.app/hst"
)

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

	testCases := []struct {
		name    string
		config  *hst.Config
		flags   int
		wantErr error
	}{
		{"nil", nil, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
			Msg: "invalid configuration"}},

		{"identity lower", &hst.Config{Identity: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
			Msg: "identity -1 out of range"}},
		{"identity upper", &hst.Config{Identity: 10000}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrIdentityBounds,
			Msg: "identity 10000 out of range"}},

		{"sched lower", &hst.Config{SchedPolicy: -1}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
			Msg: "scheduling policy -1 out of range"}},
		{"sched upper", &hst.Config{SchedPolicy: 0xcafe}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrSchedPolicyBounds,
			Msg: "scheduling policy 51966 out of range"}},

		{"dbus session", &hst.Config{SessionBus: &hst.BusConfig{See: []string{""}}}, 0,
			&hst.BadInterfaceError{Interface: "", Segment: "session"}},
		{"dbus system", &hst.Config{SystemBus: &hst.BusConfig{See: []string{""}}}, 0,
			&hst.BadInterfaceError{Interface: "", Segment: "system"}},

		{"container", &hst.Config{}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
			Msg: "configuration missing container state"}},
		{"home", &hst.Config{Container: &hst.ContainerConfig{}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
			Msg: "container configuration missing path to home directory"}},
		{"shell", &hst.Config{Container: &hst.ContainerConfig{
			Home: fhs.AbsTmp,
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
			Msg: "container configuration missing path to shell"}},
		{"path", &hst.Config{Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrConfigNull,
			Msg: "container configuration missing path to initial program"}},

		{"env equals", &hst.Config{Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
			Env:   map[string]string{"TERM=": ""},
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
			Msg: `invalid environment variable "TERM="`}},
		{"env NUL", &hst.Config{Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
			Env:   map[string]string{"TERM\x00": ""},
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrEnviron,
			Msg: `invalid environment variable "TERM\x00"`}},

		{"insecure pulse", &hst.Config{Enablements: new(hst.EPulse), Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
			Msg: "enablement PulseAudio is insecure and no longer supported"}},

		{"direct wayland", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
			Msg: "direct_wayland is insecure and no longer supported"}},
		{"direct wayland allow", &hst.Config{Enablements: new(hst.EWayland), DirectWayland: true, Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, hst.VAllowInsecure, nil},

		{"direct pipewire", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
			Msg: "direct_pipewire is insecure and no longer supported"}},
		{"direct pipewire allow", &hst.Config{Enablements: new(hst.EPipeWire), DirectPipeWire: true, Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, hst.VAllowInsecure, nil},

		{"direct pulse", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, 0, &hst.AppError{Step: "validate configuration", Err: hst.ErrInsecure,
			Msg: "direct_pulse is insecure and no longer supported"}},
		{"direct pulse allow", &hst.Config{Enablements: new(hst.EPulse), DirectPulse: true, Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, hst.VAllowInsecure, nil},

		{"valid", &hst.Config{Container: &hst.ContainerConfig{
			Home:  fhs.AbsTmp,
			Shell: fhs.AbsTmp,
			Path:  fhs.AbsTmp,
		}}, 0, nil},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if err := tc.config.Validate(tc.flags); !reflect.DeepEqual(err, tc.wantErr) {
				t.Errorf("Validate: error = %#v, want %#v", err, tc.wantErr)
			}
		})
	}
}

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

	testCases := []struct {
		name   string
		config *hst.ExtraPermConfig
		want   string
	}{
		{"nil", nil, "<invalid>"},
		{"nil path", &hst.ExtraPermConfig{Path: nil}, "<invalid>"},
		{"r", &hst.ExtraPermConfig{Path: fhs.AbsRoot, Read: true}, "r--:/"},
		{"r+", &hst.ExtraPermConfig{Ensure: true, Path: fhs.AbsRoot, Read: true}, "r--+:/"},
		{"w", &hst.ExtraPermConfig{Path: hst.AbsPrivateTmp, Write: true}, "-w-:/.hakurei"},
		{"w+", &hst.ExtraPermConfig{Ensure: true, Path: hst.AbsPrivateTmp, Write: true}, "-w-+:/.hakurei"},
		{"x", &hst.ExtraPermConfig{Path: fhs.AbsRunUser, Execute: true}, "--x:/run/user/"},
		{"x+", &hst.ExtraPermConfig{Ensure: true, Path: fhs.AbsRunUser, Execute: true}, "--x+:/run/user/"},
		{"rwx", &hst.ExtraPermConfig{Path: fhs.AbsTmp, Read: true, Write: true, Execute: true}, "rwx:/tmp/"},
		{"rwx+", &hst.ExtraPermConfig{Ensure: true, Path: fhs.AbsTmp, Read: true, Write: true, Execute: true}, "rwx+:/tmp/"},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			if got := tc.config.String(); got != tc.want {
				t.Errorf("String: %q, want %q", got, tc.want)
			}
		})
	}
}
