//go:build windows

package server

import (
	"os/user"
	"testing"
	"unsafe"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/sys/windows"
)

// filterNormalAccount limits NetUserEnum to normal user accounts.
const filterNormalAccount = 0x2

// TOKEN_ELEVATION_TYPE values.
const (
	tokenElevationTypeDefault = 1
	tokenElevationTypeFull    = 2
	tokenElevationTypeLimited = 3
)

// tokenElevationType reads TokenElevationType from a token.
func tokenElevationType(token windows.Token) (uint32, error) {
	var elevationType, returnedLen uint32
	err := windows.GetTokenInformation(token, windows.TokenElevationType,
		(*byte)(unsafe.Pointer(&elevationType)), uint32(unsafe.Sizeof(elevationType)), &returnedLen)
	if err != nil {
		return 0, err
	}
	return elevationType, nil
}

// userInfo0 mirrors USER_INFO_0.
type userInfo0 struct {
	name *uint16
}

func mustParseSID(t *testing.T, s string) *windows.SID {
	t.Helper()
	sid, err := windows.StringToSid(s)
	require.NoError(t, err, "parse SID %s", s)
	return sid
}

// localAccountNames returns the names of the local user accounts.
func localAccountNames(t *testing.T) []string {
	t.Helper()

	var buf *byte
	var entriesRead, totalEntries, resume uint32
	err := windows.NetUserEnum(nil, 0, filterNormalAccount, &buf, maxPreferredLength,
		&entriesRead, &totalEntries, &resume)
	require.NoError(t, err, "enumerate local users")
	t.Cleanup(func() {
		require.NoError(t, windows.NetApiBufferFree(buf), "free NetApi buffer")
	})

	entries := unsafe.Slice((*userInfo0)(unsafe.Pointer(buf)), entriesRead)
	names := make([]string, 0, entriesRead)
	for _, entry := range entries {
		names = append(names, windows.UTF16PtrToString(entry.name))
	}
	return names
}

// localAccountNameByRID returns the name of the local account carrying the
// given RID. Accounts such as Administrator and Guest can be renamed and are
// localized, so tests must not name them literally.
func localAccountNameByRID(t *testing.T, rid uint32) string {
	t.Helper()

	for _, name := range localAccountNames(t) {
		sid, _, _, err := windows.LookupSID("", name)
		if err != nil {
			continue
		}
		if sid.IdentifierAuthority() != windows.SECURITY_NT_AUTHORITY {
			continue
		}
		count := sid.SubAuthorityCount()
		if count < 2 || sid.SubAuthority(0) != 21 {
			continue
		}
		if sid.SubAuthority(uint32(count-1)) == rid {
			return name
		}
	}

	t.Fatalf("no local account with RID %d", rid)
	return ""
}

// wellKnownAccountName resolves a well-known SID to the qualified account name
// the local system uses for it, which is localized.
func wellKnownAccountName(t *testing.T, sidType windows.WELL_KNOWN_SID_TYPE) string {
	t.Helper()

	sid, err := windows.CreateWellKnownSid(sidType)
	require.NoError(t, err, "create well-known SID")
	name, domain, _, err := sid.LookupAccount("")
	require.NoError(t, err, "resolve %s to an account name", sid)
	if domain == "" {
		return name
	}
	return domain + `\` + name
}

func TestIsBuiltinAdministratorSID(t *testing.T) {
	tests := []struct {
		name string
		sid  string
		want bool
	}{
		{"machine_administrator", "S-1-5-21-1111111111-2222222222-3333333333-500", true},
		{"domain_administrator", "S-1-5-21-3390233681-4087452608-412898826-500", true},
		{"regular_user", "S-1-5-21-1111111111-2222222222-3333333333-1001", false},
		{"guest_account", "S-1-5-21-1111111111-2222222222-3333333333-501", false},
		{"domain_admins_group", "S-1-5-21-1111111111-2222222222-3333333333-512", false},
		{"system", "S-1-5-18", false},
		{"administrators_group", "S-1-5-32-544", false},
		{"non_nt_authority", "S-1-1-0", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isBuiltinAdministratorSID(mustParseSID(t, tt.sid))
			assert.Equal(t, tt.want, result, "RID 500 detection for %s", tt.sid)
		})
	}
}

func TestIsPrivilegedUserSID(t *testing.T) {
	tests := []struct {
		name string
		sid  string
		want bool
	}{
		{"local_system", "S-1-5-18", true},
		{"local_service", "S-1-5-19", true},
		{"network_service", "S-1-5-20", true},
		{"administrators_group", "S-1-5-32-544", true},
		{"builtin_administrator", "S-1-5-21-1111111111-2222222222-3333333333-500", true},
		{"regular_user", "S-1-5-21-1111111111-2222222222-3333333333-1001", false},
		{"users_group", "S-1-5-32-545", false},
		{"everyone", "S-1-1-0", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isPrivilegedUserSID(mustParseSID(t, tt.sid))
			assert.Equal(t, tt.want, result, "SID privilege classification for %s", tt.sid)
		})
	}
}

func TestIsWindowsAccountPrivileged(t *testing.T) {
	tests := []struct {
		name     string
		username string
		want     bool
	}{
		{"system", wellKnownAccountName(t, windows.WinLocalSystemSid), true},
		{"local_service", wellKnownAccountName(t, windows.WinLocalServiceSid), true},
		{"network_service", wellKnownAccountName(t, windows.WinNetworkServiceSid), true},
		{"administrators_group", wellKnownAccountName(t, windows.WinBuiltinAdministratorsSid), true},
		// The built-in Administrator (RID 500) and Guest (RID 501) accounts
		// exist on every Windows installation, though they may be disabled.
		{"builtin_administrator", localAccountNameByRID(t, 500), true},
		{"guest", localAccountNameByRID(t, 501), false},
		// Unresolvable accounts fail closed.
		{"nonexistent_user", "netbird-no-such-user", true},
		{"empty_username", "", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isWindowsAccountPrivileged(tt.username)
			assert.Equal(t, tt.want, result, "account privilege classification for %q", tt.username)
		})
	}
}

func TestIsProcessElevated(t *testing.T) {
	elevated := isProcessElevated()

	// TokenElevationType is a second, independent view of the same token:
	// Full means elevated and Limited means a filtered administrator, while
	// Default covers both a standard user and an administrator with no linked
	// token (UAC off, the built-in Administrator, SYSTEM), so it implies nothing.
	elevationType, err := tokenElevationType(windows.GetCurrentProcessToken())
	require.NoError(t, err, "read token elevation type")

	adminSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
	require.NoError(t, err, "create Administrators SID")

	// Token(0) makes CheckTokenMembership evaluate the caller's own token. It
	// counts only enabled SIDs, so a filtered administrator reports false here.
	member, err := windows.Token(0).IsMember(adminSid)
	require.NoError(t, err, "check own Administrators membership")

	t.Logf("elevated=%v elevationType=%d memberOfAdministrators=%v", elevated, elevationType, member)

	switch elevationType {
	case tokenElevationTypeFull:
		assert.True(t, elevated, "a token of elevation type Full must report elevated")
	case tokenElevationTypeLimited:
		assert.False(t, elevated, "a filtered administrator token must not report elevated")
	}

	// Administrators enabled in the token means the token wields administrative
	// rights, which is what elevation reports.
	if member {
		assert.True(t, elevated, "token with enabled Administrators membership must report elevated")
	}
}

// TestS4UMembershipAgreesWithLocalGroups exercises the S4U token path used
// for domain accounts. S4U logons need the TCB privilege, so the test runs
// only as SYSTEM (which is how CI executes the suite). For local accounts the
// token's Administrators membership must agree with the SAM enumeration.
func TestS4UMembershipAgreesWithLocalGroups(t *testing.T) {
	system, err := windows.CreateWellKnownSid(windows.WinLocalSystemSid)
	require.NoError(t, err, "create SYSTEM SID")
	current, err := user.Current()
	require.NoError(t, err, "get current user")
	if current.Uid != system.String() {
		t.Skipf("S4U logon requires SYSTEM (running as %s)", current.Username)
	}

	adminSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
	require.NoError(t, err, "create Administrators SID")

	checked := 0
	for _, name := range localAccountNames(t) {
		viaToken, err := s4uTokenIsMember(name, ".", adminSid)
		if err != nil {
			// Disabled or logon-restricted accounts cannot get an S4U logon.
			t.Logf("skipping %s: %v", name, err)
			continue
		}
		viaSAM, err := localGroupsContainSID(name, adminSid)
		require.NoError(t, err, "enumerate local groups for %s", name)

		assert.Equal(t, viaSAM, viaToken, "S4U token and SAM enumeration must agree on Administrators membership for %s", name)
		checked++
	}
	// Ineligible accounts are skipped, so without this the test could report
	// success while comparing nothing at all.
	require.Positive(t, checked, "no local account completed an S4U logon, so nothing was compared")
	t.Logf("checked %d local accounts via S4U", checked)
}

// TestLocalGroupsContainSID_Administrator checks the positive case against the
// built-in Administrator, a member of Administrators on every installation.
func TestLocalGroupsContainSID_Administrator(t *testing.T) {
	adminSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
	require.NoError(t, err, "create Administrators SID")

	administrator := localAccountNameByRID(t, 500)
	member, err := localGroupsContainSID(administrator, adminSid)
	require.NoError(t, err, "enumerate local groups for %s", administrator)
	assert.True(t, member, "%s is a member of the Administrators group", administrator)
}

// TestLocalGroupsContainSID_UnresolvableGroupFailsClosed covers a wanted SID
// that resolves to no group: the error must surface rather than being reported
// as "not a member", so the privilege check treats the account as privileged.
func TestLocalGroupsContainSID_UnresolvableGroupFailsClosed(t *testing.T) {
	unknown := mustParseSID(t, "S-1-5-21-1111111111-2222222222-3333333333-4444")

	_, err := localGroupsContainSID(localAccountNameByRID(t, 500), unknown)
	require.Error(t, err, "must report an error when the wanted group cannot be identified")
}

func TestLocalGroupsContainSID_Guest(t *testing.T) {
	guestsSid, err := windows.CreateWellKnownSid(windows.WinBuiltinGuestsSid)
	require.NoError(t, err, "create Guests SID")
	adminsSid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
	require.NoError(t, err, "create Administrators SID")

	guest := localAccountNameByRID(t, 501)

	inGuests, err := localGroupsContainSID(guest, guestsSid)
	require.NoError(t, err, "enumerate local groups for %s", guest)
	assert.True(t, inGuests, "%s is a member of the Guests group", guest)

	inAdmins, err := localGroupsContainSID(guest, adminsSid)
	require.NoError(t, err, "enumerate local groups for %s", guest)
	assert.False(t, inAdmins, "%s is not a member of the Administrators group", guest)
}
