//go:build windows

package fs

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
	"time"

	"github.com/restic/restic/internal/data"
	"github.com/restic/restic/internal/errors"
	"github.com/restic/restic/internal/test"
	"golang.org/x/sys/windows"
)

func TestRestoreSecurityDescriptors(t *testing.T) {
	t.Parallel()
	tempDir := t.TempDir()
	for i, sd := range testFileSDs {
		testRestoreSecurityDescriptor(t, sd, tempDir, data.NodeTypeFile, fmt.Sprintf("testfile%d", i))
	}
	for i, sd := range testDirSDs {
		testRestoreSecurityDescriptor(t, sd, tempDir, data.NodeTypeDir, fmt.Sprintf("testdir%d", i))
	}
}

func testRestoreSecurityDescriptor(t *testing.T, sd string, tempDir string, fileType data.NodeType, fileName string) {
	// Decode the encoded string SD to get the security descriptor input in bytes.
	sdInputBytes, err := base64.StdEncoding.DecodeString(sd)
	test.OK(t, errors.Wrapf(err, "Error decoding SD for: %s", fileName))
	// Wrap the security descriptor bytes in windows attributes and convert to generic attributes.
	genericAttributes, err := data.WindowsAttrsToGenericAttributes(data.WindowsAttributes{CreationTime: nil, FileAttributes: nil, SecurityDescriptor: &sdInputBytes})
	test.OK(t, errors.Wrapf(err, "Error constructing windows attributes for: %s", fileName))
	// Construct a Node with the generic attributes.
	expectedNode := getNode(fileName, fileType, genericAttributes)

	// Restore the file/dir and restore the meta data including the security descriptors.
	testPath, node := restoreAndGetNode(t, tempDir, &expectedNode, false)
	// Get the security descriptor from the node constructed from the file info of the restored path.
	sdByteFromRestoredNode := getWindowsAttr(t, testPath, node).SecurityDescriptor

	// Get the security descriptor for the test path after the restore.
	sdBytesFromRestoredPath, err := getSecurityDescriptor(testPath)
	test.OK(t, errors.Wrapf(err, "Error while getting the security descriptor for: %s", testPath))

	// Compare the input SD and the SD got from the restored file.
	compareSecurityDescriptors(t, testPath, sdInputBytes, *sdBytesFromRestoredPath)
	// Compare the SD got from node constructed from the restored file info and the SD got directly from the restored file.
	compareSecurityDescriptors(t, testPath, *sdByteFromRestoredNode, *sdBytesFromRestoredPath)
}

// TestRestoreSecurityDescriptorInheritance checks that the DACL protection (inheritance)
// flags are correctly restored. This is the mechanism that preserves the `IsInherited`
// property on individual ACEs.
func TestRestoreSecurityDescriptorInheritance(t *testing.T) {
	// This test requires admin privileges to manipulate ACLs effectively.
	isAdmin, err := isAdmin()
	test.OK(t, err)
	if !isAdmin {
		t.Skip("Skipping inheritance test, requires admin privileges")
	}

	tempDir := t.TempDir()

	// 1. Create a parent/child directory structure.
	parentDir := filepath.Join(tempDir, "parent")
	err = os.Mkdir(parentDir, 0755)
	test.OK(t, err)

	childDir := filepath.Join(parentDir, "child")
	err = os.Mkdir(childDir, 0755)
	test.OK(t, err)

	// 2. Set inheritable permissions on the parent.
	// We will give the "Users" group inheritable read access.
	users, err := windows.StringToSid("S-1-5-32-545") // BUILTIN\Users
	test.OK(t, err)

	// Create an EXPLICIT_ACCESS structure for the new ACE.
	explicitAccess := windows.EXPLICIT_ACCESS{
		AccessPermissions: windows.GENERIC_READ,
		AccessMode:        windows.GRANT_ACCESS,
		Inheritance:       windows.OBJECT_INHERIT_ACE | windows.CONTAINER_INHERIT_ACE,
		Trustee: windows.TRUSTEE{
			TrusteeForm:  windows.TRUSTEE_IS_SID,
			TrusteeType:  windows.TRUSTEE_IS_GROUP,
			TrusteeValue: windows.TrusteeValueFromSID(users),
		},
	}

	// Create a new DACL from the entry.
	dacl, err := windows.ACLFromEntries([]windows.EXPLICIT_ACCESS{explicitAccess}, nil)
	test.OK(t, err)

	// Apply this new DACL to the parent, marking it as unprotected so it can be inherited.
	err = windows.SetNamedSecurityInfo(
		parentDir,
		windows.SE_FILE_OBJECT,
		windows.DACL_SECURITY_INFORMATION|windows.UNPROTECTED_DACL_SECURITY_INFORMATION,
		nil, nil, dacl, nil,
	)
	test.OK(t, errors.Wrapf(err, "failed to set inheritable ACL on parent dir"))

	// 3. Get the Security Descriptor of the child, which should now have inherited the ACE.
	sdBytesOriginal, err := getSecurityDescriptor(childDir)
	test.OK(t, err)

	// Sanity check: verify the original child SD is NOT protected from inheritance.
	sdOriginal, err := securityDescriptorBytesToStruct(*sdBytesOriginal)
	test.OK(t, err)
	control, _, err := sdOriginal.Control()
	test.OK(t, err)
	test.Assert(t, control&windows.SE_DACL_PROTECTED == 0, "Pre-condition failed: child directory should have inheritance enabled")

	// 4. Create a restic node for the child directory.
	genericAttrs, err := data.WindowsAttrsToGenericAttributes(data.WindowsAttributes{SecurityDescriptor: sdBytesOriginal})
	test.OK(t, err)
	childNode := getNode("child-restored", "dir", genericAttrs)

	// 5. Restore the node to a new location.
	restoreDir := filepath.Join(tempDir, "restore")
	err = os.Mkdir(restoreDir, 0755)
	test.OK(t, err)

	restoredPath, _ := restoreAndGetNode(t, restoreDir, &childNode, false)

	// 6. Get the Security Descriptor of the restored child directory.
	sdBytesRestored, err := getSecurityDescriptor(restoredPath)
	test.OK(t, err)

	// 7. Compare the control flags of the original and restored SDs.
	sdRestored, err := securityDescriptorBytesToStruct(*sdBytesRestored)
	test.OK(t, err)
	controlRestored, _, err := sdRestored.Control()
	test.OK(t, err)

	// The core of the test: Ensure the restored DACL protection flag matches the original.
	originalIsProtected := (control & windows.SE_DACL_PROTECTED) != 0
	restoredIsProtected := (controlRestored & windows.SE_DACL_PROTECTED) != 0
	test.Equals(t, originalIsProtected, restoredIsProtected, "DACL protection flag was not restored correctly. Inheritance state is wrong.")
}

// TestRestoreSecurityDescriptorInheritanceLowPrivilege tests that the low-privilege restore
// path (setNamedSecurityInfoLow) correctly handles inheritance flags. This test doesn't require
// admin privileges and focuses on DACL restoration only.
func TestRestoreSecurityDescriptorInheritanceLowPrivilege(t *testing.T) {
	tempDir := t.TempDir()

	// 1. Create a test directory
	testDir := filepath.Join(tempDir, "testdir")
	err := os.Mkdir(testDir, 0755)
	test.OK(t, err)

	// 2. Get its security descriptor (which will have some default ACL)
	sdBytesOriginal, err := getSecurityDescriptor(testDir)
	test.OK(t, err)

	// Verify we can get the control flags
	sdOriginal, err := securityDescriptorBytesToStruct(*sdBytesOriginal)
	test.OK(t, err)
	controlOriginal, _, err := sdOriginal.Control()
	test.OK(t, err)

	// 3. Test both protected and unprotected scenarios by modifying the control flags
	testCases := []struct {
		name              string
		shouldBeProtected bool
	}{
		{"unprotected_inheritance_enabled", false},
		{"protected_inheritance_disabled", true},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create a copy of the SD bytes to modify
			sdBytesTest := make([]byte, len(*sdBytesOriginal))
			copy(sdBytesTest, *sdBytesOriginal)

			// Modify the control flags to simulate protected/unprotected DACL
			sdTest, err := securityDescriptorBytesToStruct(sdBytesTest)
			test.OK(t, err)

			// Get the DACL from the test SD
			dacl, _, err := sdTest.DACL()
			test.OK(t, err)

			// Determine which control flag to use based on test case
			var controlToUse windows.SECURITY_DESCRIPTOR_CONTROL
			if tc.shouldBeProtected {
				controlToUse = controlOriginal | windows.SE_DACL_PROTECTED
			} else {
				controlToUse = controlOriginal &^ windows.SE_DACL_PROTECTED
			}

			// 4. Call setNamedSecurityInfoLow directly to test the low-privilege path
			restoreTarget := filepath.Join(tempDir, "restore_"+tc.name)
			err = os.Mkdir(restoreTarget, 0755)
			test.OK(t, err)

			// This directly tests the low-privilege restore function
			err = setNamedSecurityInfoLow(restoreTarget, dacl, controlToUse)
			test.OK(t, err)

			// 5. Get the security descriptor of the restored directory
			sdBytesRestored, err := getSecurityDescriptor(restoreTarget)
			test.OK(t, err)

			// 6. Verify that the control flags were correctly restored
			sdRestored, err := securityDescriptorBytesToStruct(*sdBytesRestored)
			test.OK(t, err)
			controlRestored, _, err := sdRestored.Control()
			test.OK(t, err) // Check if the protection flag matches what we requested
			restoredIsProtected := (controlRestored & windows.SE_DACL_PROTECTED) != 0
			if tc.shouldBeProtected != restoredIsProtected {
				t.Errorf("DACL protection flag was not restored correctly in low-privilege path. Expected protected=%v, got protected=%v",
					tc.shouldBeProtected, restoredIsProtected)
			}
		})
	}
}

func getNode(name string, fileType data.NodeType, genericAttributes map[data.GenericAttributeType]json.RawMessage) data.Node {
	return data.Node{
		Name:              name,
		Type:              fileType,
		Mode:              0644,
		ModTime:           parseTime("2024-02-21 6:30:01.111"),
		AccessTime:        parseTime("2024-02-22 7:31:02.222"),
		ChangeTime:        parseTime("2024-02-23 8:32:03.333"),
		GenericAttributes: genericAttributes,
	}
}

func getWindowsAttr(t *testing.T, testPath string, node *data.Node) data.WindowsAttributes {
	windowsAttributes, unknownAttribs, err := genericAttributesToWindowsAttrs(node.GenericAttributes)
	test.OK(t, errors.Wrapf(err, "Error getting windows attr from generic attr: %s", testPath))
	test.Assert(t, len(unknownAttribs) == 0, "Unknown attribs found: %s for: %s", unknownAttribs, testPath)
	return windowsAttributes
}

func TestRestoreCreationTime(t *testing.T) {
	t.Parallel()
	path := t.TempDir()
	fi, err := os.Lstat(path)
	test.OK(t, errors.Wrapf(err, "Could not Lstat for path: %s", path))
	attr := fi.Sys().(*syscall.Win32FileAttributeData)
	creationTimeAttribute := attr.CreationTime
	//Using the temp dir creation time as the test creation time for the test file and folder
	runGenericAttributesTest(t, path, data.TypeCreationTime, data.WindowsAttributes{CreationTime: &creationTimeAttribute}, false)
}

func TestRestoreFileAttributes(t *testing.T) {
	t.Parallel()
	genericAttributeName := data.TypeFileAttributes
	tempDir := t.TempDir()
	normal := uint32(syscall.FILE_ATTRIBUTE_NORMAL)
	hidden := uint32(syscall.FILE_ATTRIBUTE_HIDDEN)
	system := uint32(syscall.FILE_ATTRIBUTE_SYSTEM)
	archive := uint32(syscall.FILE_ATTRIBUTE_ARCHIVE)
	encrypted := uint32(windows.FILE_ATTRIBUTE_ENCRYPTED)
	fileAttributes := []data.WindowsAttributes{
		//normal
		{FileAttributes: &normal},
		//hidden
		{FileAttributes: &hidden},
		//system
		{FileAttributes: &system},
		//archive
		{FileAttributes: &archive},
		//encrypted
		{FileAttributes: &encrypted},
	}
	for i, fileAttr := range fileAttributes {
		genericAttrs, err := data.WindowsAttrsToGenericAttributes(fileAttr)
		test.OK(t, err)
		expectedNodes := []data.Node{
			{
				Name:              fmt.Sprintf("testfile%d", i),
				Type:              data.NodeTypeFile,
				Mode:              0655,
				ModTime:           parseTime("2005-05-14 21:07:03.111"),
				AccessTime:        parseTime("2005-05-14 21:07:04.222"),
				ChangeTime:        parseTime("2005-05-14 21:07:05.333"),
				GenericAttributes: genericAttrs,
			},
		}
		runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, fileAttr, false)
	}
	normal = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY)
	hidden = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | syscall.FILE_ATTRIBUTE_HIDDEN)
	system = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_SYSTEM)
	archive = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ARCHIVE)
	encrypted = uint32(syscall.FILE_ATTRIBUTE_DIRECTORY | windows.FILE_ATTRIBUTE_ENCRYPTED)
	folderAttributes := []data.WindowsAttributes{
		//normal
		{FileAttributes: &normal},
		//hidden
		{FileAttributes: &hidden},
		//system
		{FileAttributes: &system},
		//archive
		{FileAttributes: &archive},
		//encrypted
		{FileAttributes: &encrypted},
	}
	for i, folderAttr := range folderAttributes {
		genericAttrs, err := data.WindowsAttrsToGenericAttributes(folderAttr)
		test.OK(t, err)
		expectedNodes := []data.Node{
			{
				Name:              fmt.Sprintf("testdirectory%d", i),
				Type:              data.NodeTypeDir,
				Mode:              0755,
				ModTime:           parseTime("2005-05-14 21:07:03.111"),
				AccessTime:        parseTime("2005-05-14 21:07:04.222"),
				ChangeTime:        parseTime("2005-05-14 21:07:05.333"),
				GenericAttributes: genericAttrs,
			},
		}
		runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, folderAttr, false)
	}
}

func runGenericAttributesTest(t *testing.T, tempDir string, genericAttributeName data.GenericAttributeType, genericAttributeExpected data.WindowsAttributes, warningExpected bool) {
	genericAttributes, err := data.WindowsAttrsToGenericAttributes(genericAttributeExpected)
	test.OK(t, err)
	expectedNodes := []data.Node{
		{
			Name:              "testfile",
			Type:              data.NodeTypeFile,
			Mode:              0644,
			ModTime:           parseTime("2005-05-14 21:07:03.111"),
			AccessTime:        parseTime("2005-05-14 21:07:04.222"),
			ChangeTime:        parseTime("2005-05-14 21:07:05.333"),
			GenericAttributes: genericAttributes,
		},
		{
			Name:              "testdirectory",
			Type:              data.NodeTypeDir,
			Mode:              0755,
			ModTime:           parseTime("2005-05-14 21:07:03.111"),
			AccessTime:        parseTime("2005-05-14 21:07:04.222"),
			ChangeTime:        parseTime("2005-05-14 21:07:05.333"),
			GenericAttributes: genericAttributes,
		},
	}
	runGenericAttributesTestForNodes(t, expectedNodes, tempDir, genericAttributeName, genericAttributeExpected, warningExpected)
}
func runGenericAttributesTestForNodes(t *testing.T, expectedNodes []data.Node, tempDir string, genericAttr data.GenericAttributeType, genericAttributeExpected data.WindowsAttributes, warningExpected bool) {

	for _, testNode := range expectedNodes {
		testPath, node := restoreAndGetNode(t, tempDir, &testNode, warningExpected)
		rawMessage := node.GenericAttributes[genericAttr]
		genericAttrsExpected, err := data.WindowsAttrsToGenericAttributes(genericAttributeExpected)
		test.OK(t, err)
		rawMessageExpected := genericAttrsExpected[genericAttr]
		test.Equals(t, rawMessageExpected, rawMessage, "Generic attribute: %s got from NodeFromFileInfo not equal for path: %s", string(genericAttr), testPath)
	}
}

func restoreAndGetNode(t *testing.T, tempDir string, testNode *data.Node, warningExpected bool) (string, *data.Node) {
	testPath := filepath.Join(tempDir, "001", testNode.Name)
	err := os.MkdirAll(filepath.Dir(testPath), testNode.Mode)
	test.OK(t, errors.Wrapf(err, "Failed to create parent directories for: %s", testPath))

	if testNode.Type == data.NodeTypeFile {

		testFile, err := os.Create(testPath)
		test.OK(t, errors.Wrapf(err, "Failed to create test file: %s", testPath))
		testFile.Close()
	} else if testNode.Type == data.NodeTypeDir {

		err := os.Mkdir(testPath, testNode.Mode)
		test.OK(t, errors.Wrapf(err, "Failed to create test directory: %s", testPath))
	}

	err = NodeRestoreMetadata(testNode, testPath, func(msg string) {
		if warningExpected {
			test.Assert(t, warningExpected, "Warning triggered as expected: %s", msg)
		} else {
			// If warning is not expected, this code should not get triggered.
			test.OK(t, fmt.Errorf("Warning triggered for path: %s: %s", testPath, msg))
		}
	}, func(_ string) bool { return true }, false)
	test.OK(t, errors.Wrapf(err, "Failed to restore metadata for: %s", testPath))

	fs := &Local{}
	meta, err := fs.OpenFile(testPath, O_NOFOLLOW, true)
	test.OK(t, err)
	nodeFromFileInfo, err := meta.ToNode(false, t.Logf)
	test.OK(t, errors.Wrapf(err, "Could not get NodeFromFileInfo for path: %s", testPath))
	test.OK(t, meta.Close())

	return testPath, nodeFromFileInfo
}

const TypeSomeNewAttribute data.GenericAttributeType = "MockAttributes.SomeNewAttribute"

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

	newGenericAttribute := map[data.GenericAttributeType]json.RawMessage{}
	newGenericAttribute[TypeSomeNewAttribute] = []byte("any value")

	tempDir := t.TempDir()
	expectedNodes := []data.Node{
		{
			Name:              "testfile",
			Type:              data.NodeTypeFile,
			Mode:              0644,
			ModTime:           parseTime("2005-05-14 21:07:03.111"),
			AccessTime:        parseTime("2005-05-14 21:07:04.222"),
			ChangeTime:        parseTime("2005-05-14 21:07:05.333"),
			GenericAttributes: newGenericAttribute,
		},
		{
			Name:              "testdirectory",
			Type:              data.NodeTypeDir,
			Mode:              0755,
			ModTime:           parseTime("2005-05-14 21:07:03.111"),
			AccessTime:        parseTime("2005-05-14 21:07:04.222"),
			ChangeTime:        parseTime("2005-05-14 21:07:05.333"),
			GenericAttributes: newGenericAttribute,
		},
	}
	for _, testNode := range expectedNodes {
		testPath, node := restoreAndGetNode(t, tempDir, &testNode, true)
		_, ua, err := genericAttributesToWindowsAttrs(node.GenericAttributes)
		test.OK(t, err)
		// Since this GenericAttribute is unknown to this version of the software, it will not get set on the file.
		test.Assert(t, len(ua) == 0, "Unknown attributes: %s found for path: %s", ua, testPath)
	}
}

func TestRestoreExtendedAttributes(t *testing.T) {
	t.Parallel()
	tempDir := t.TempDir()
	expectedNodes := []data.Node{
		{
			Name:       "testfile",
			Type:       data.NodeTypeFile,
			Mode:       0644,
			ModTime:    parseTime("2005-05-14 21:07:03.111"),
			AccessTime: parseTime("2005-05-14 21:07:04.222"),
			ChangeTime: parseTime("2005-05-14 21:07:05.333"),
			ExtendedAttributes: []data.ExtendedAttribute{
				{"user.foo", []byte("bar")},
			},
		},
		{
			Name:       "testdirectory",
			Type:       data.NodeTypeDir,
			Mode:       0755,
			ModTime:    parseTime("2005-05-14 21:07:03.111"),
			AccessTime: parseTime("2005-05-14 21:07:04.222"),
			ChangeTime: parseTime("2005-05-14 21:07:05.333"),
			ExtendedAttributes: []data.ExtendedAttribute{
				{"user.foo", []byte("bar")},
			},
		},
	}
	for _, testNode := range expectedNodes {
		testPath, node := restoreAndGetNode(t, tempDir, &testNode, false)

		var handle windows.Handle
		var err error
		utf16Path := windows.StringToUTF16Ptr(testPath)
		if node.Type == data.NodeTypeFile || node.Type == data.NodeTypeDir {
			handle, err = windows.CreateFile(utf16Path, windows.FILE_READ_EA, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL|windows.FILE_FLAG_BACKUP_SEMANTICS, 0)
		}
		test.OK(t, errors.Wrapf(err, "Error opening file/directory for: %s", testPath))
		defer func() {
			err := windows.Close(handle)
			test.OK(t, errors.Wrapf(err, "Error closing file for: %s", testPath))
		}()

		extAttr, err := fgetEA(handle)
		test.OK(t, errors.Wrapf(err, "Error getting extended attributes for: %s", testPath))
		test.Equals(t, len(node.ExtendedAttributes), len(extAttr))

		for _, expectedExtAttr := range node.ExtendedAttributes {
			var foundExtAttr *extendedAttribute
			for _, ea := range extAttr {
				if strings.EqualFold(ea.Name, expectedExtAttr.Name) {
					foundExtAttr = &ea
					break

				}
			}
			test.Assert(t, foundExtAttr != nil, "Expected extended attribute not found")
			test.Equals(t, expectedExtAttr.Value, foundExtAttr.Value)
		}
	}
}

func TestPrepareVolumeName(t *testing.T) {
	currentVolume := filepath.VolumeName(func() string {
		// Get the current working directory
		pwd, err := os.Getwd()
		if err != nil {
			t.Fatalf("Failed to get current working directory: %v", err)
		}
		return pwd
	}())
	// Create a temporary directory for the test
	tempDir, err := os.MkdirTemp("", "restic_test_"+time.Now().Format("20060102150405"))
	if err != nil {
		t.Fatalf("Failed to create temp directory: %v", err)
	}
	defer os.RemoveAll(tempDir)

	// Create a long file name
	longFileName := `\Very\Long\Path\That\Exceeds\260\Characters\` + strings.Repeat(`\VeryLongFolderName`, 20) + `\\LongFile.txt`
	longFilePath := filepath.Join(tempDir, longFileName)

	tempDirVolume := filepath.VolumeName(tempDir)
	// Create the file
	content := []byte("This is a test file with a very long name.")
	err = os.MkdirAll(filepath.Dir(longFilePath), 0755)
	test.OK(t, err)
	if err != nil {
		t.Fatalf("Failed to create long folder: %v", err)
	}
	err = os.WriteFile(longFilePath, content, 0644)
	test.OK(t, err)
	if err != nil {
		t.Fatalf("Failed to create long file: %v", err)
	}
	osVolumeGUIDPath := getOSVolumeGUIDPath(t)
	osVolumeGUIDVolume := filepath.VolumeName(osVolumeGUIDPath)

	testCases := []struct {
		name                string
		path                string
		expectedVolume      string
		expectError         bool
		expectedEASupported bool
		isRealPath          bool
	}{
		{
			name:                "Network drive path",
			path:                `Z:\Shared\Documents`,
			expectedVolume:      `Z:`,
			expectError:         false,
			expectedEASupported: false,
		},
		{
			name:                "Subst drive path",
			path:                `X:\Virtual\Folder`,
			expectedVolume:      `X:`,
			expectError:         false,
			expectedEASupported: false,
		},
		{
			name:                "Windows reserved path",
			path:                `\\.\` + os.Getenv("SystemDrive") + `\System32\drivers\etc\hosts`,
			expectedVolume:      `\\.\` + os.Getenv("SystemDrive"),
			expectError:         false,
			expectedEASupported: true,
			isRealPath:          true,
		},
		{
			name:                "Long UNC path",
			path:                `\\?\UNC\LongServerName\VeryLongShareName\DeepPath\File.txt`,
			expectedVolume:      `\\LongServerName\VeryLongShareName`,
			expectError:         false,
			expectedEASupported: false,
		},
		{
			name:                "Volume GUID path",
			path:                osVolumeGUIDPath,
			expectedVolume:      osVolumeGUIDVolume,
			expectError:         false,
			expectedEASupported: true,
			isRealPath:          true,
		},
		{
			name:                "Volume GUID path with subfolder",
			path:                osVolumeGUIDPath + `\Windows`,
			expectedVolume:      osVolumeGUIDVolume,
			expectError:         false,
			expectedEASupported: true,
			isRealPath:          true,
		},
		{
			name:                "Standard path",
			path:                os.Getenv("SystemDrive") + `\Users\`,
			expectedVolume:      os.Getenv("SystemDrive"),
			expectError:         false,
			expectedEASupported: true,
			isRealPath:          true,
		},
		{
			name:                "Extended length path",
			path:                longFilePath,
			expectedVolume:      tempDirVolume,
			expectError:         false,
			expectedEASupported: true,
			isRealPath:          true,
		},
		{
			name:                "UNC path",
			path:                `\\server\share\folder`,
			expectedVolume:      `\\server\share`,
			expectError:         false,
			expectedEASupported: false,
		},
		{
			name:                "Extended UNC path",
			path:                `\\?\UNC\server\share\folder`,
			expectedVolume:      `\\server\share`,
			expectError:         false,
			expectedEASupported: false,
		},
		{
			name:                "Volume Shadow Copy root",
			path:                `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555`,
			expectedVolume:      `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555`,
			expectError:         false,
			expectedEASupported: false,
		},
		{
			name:                "Volume Shadow Copy path",
			path:                `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555\Users\test`,
			expectedVolume:      `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy5555`,
			expectError:         false,
			expectedEASupported: false,
		},
		{
			name: "Relative path",
			path: `folder\subfolder`,

			expectedVolume:      currentVolume, // Get current volume
			expectError:         false,
			expectedEASupported: true,
		},
		{
			name:                "Empty path",
			path:                ``,
			expectedVolume:      currentVolume,
			expectError:         false,
			expectedEASupported: true,
			isRealPath:          false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			isEASupported, err := checkAndStoreEASupport(tc.path)
			test.OK(t, err)
			test.Equals(t, tc.expectedEASupported, isEASupported)

			volume, err := prepareVolumeName(tc.path)

			if tc.expectError {
				test.Assert(t, err != nil, "Expected an error, but got none")
			} else {
				test.OK(t, err)
			}
			test.Equals(t, tc.expectedVolume, volume)

			if tc.isRealPath {
				isEASupportedVolume, err := pathSupportsExtendedAttributes(volume + `\`)
				// If the prepared volume name is not valid, we will next fetch the actual volume name.
				test.OK(t, err)

				test.Equals(t, tc.expectedEASupported, isEASupportedVolume)

				actualVolume, err := getVolumePathName(tc.path)
				test.OK(t, err)
				test.Equals(t, tc.expectedVolume, actualVolume)
			}
		})
	}
}

func getOSVolumeGUIDPath(t *testing.T) string {
	// Get the path of the OS drive (usually C:\)
	osDrive := os.Getenv("SystemDrive") + "\\"

	// Convert to a volume GUID path
	volumeName, err := windows.UTF16PtrFromString(osDrive)
	test.OK(t, err)
	if err != nil {
		return ""
	}

	var volumeGUID [windows.MAX_PATH]uint16
	err = windows.GetVolumeNameForVolumeMountPoint(volumeName, &volumeGUID[0], windows.MAX_PATH)
	test.OK(t, err)
	if err != nil {
		return ""
	}

	return windows.UTF16ToString(volumeGUID[:])
}

func TestGetVolumePathName(t *testing.T) {
	tempDirVolume := filepath.VolumeName(os.TempDir())
	testCases := []struct {
		name           string
		path           string
		expectedPrefix string
	}{
		{
			name:           "Root directory",
			path:           os.Getenv("SystemDrive") + `\`,
			expectedPrefix: os.Getenv("SystemDrive"),
		},
		{
			name:           "Nested directory",
			path:           os.Getenv("SystemDrive") + `\Windows\System32`,
			expectedPrefix: os.Getenv("SystemDrive"),
		},
		{
			name:           "Temp directory",
			path:           os.TempDir() + `\`,
			expectedPrefix: tempDirVolume,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			volumeName, err := getVolumePathName(tc.path)
			if err != nil {
				t.Fatalf("Unexpected error: %v", err)
			}
			if !strings.HasPrefix(volumeName, tc.expectedPrefix) {
				t.Errorf("Expected volume name to start with %s, but got %s", tc.expectedPrefix, volumeName)
			}
		})
	}

	// Test with an invalid path
	_, err := getVolumePathName("Z:\\NonExistentPath")
	if err == nil {
		t.Error("Expected an error for non-existent path, but got nil")
	}
}
