//go:build windows

package file

import (
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// Basic test from golang's os/path_test.go
func TestMkdirAll(t *testing.T) {
	tmpDir := t.TempDir()

	path := tmpDir + "/dir/./dir2"
	err := MkdirAll(path, 0777)
	if err != nil {
		t.Fatalf("MkdirAll %q: %s", path, err)
	}

	// Already exists, should succeed.
	err = MkdirAll(path, 0777)
	if err != nil {
		t.Fatalf("MkdirAll %q (second time): %s", path, err)
	}

	// Make file.
	fpath := path + "/file"
	f, err := Create(fpath)
	if err != nil {
		t.Fatalf("create %q: %s", fpath, err)
	}
	defer func() {
		if err := f.Close(); err != nil {
			t.Fatalf("Close %q: %s", fpath, err)
		}
	}()

	// Can't make directory named after file.
	err = MkdirAll(fpath, 0777)
	if err == nil {
		t.Fatalf("MkdirAll %q: no error", fpath)
	}
	perr, ok := err.(*os.PathError)
	if !ok {
		t.Fatalf("MkdirAll %q returned %T, not *PathError", fpath, err)
	}
	if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
		t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", fpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
	}

	// Can't make subdirectory of file.
	ffpath := fpath + "/subdir"
	err = MkdirAll(ffpath, 0777)
	if err == nil {
		t.Fatalf("MkdirAll %q: no error", ffpath)
	}
	perr, ok = err.(*os.PathError)
	if !ok {
		t.Fatalf("MkdirAll %q returned %T, not *PathError", ffpath, err)
	}
	if filepath.Clean(perr.Path) != filepath.Clean(fpath) {
		t.Fatalf("MkdirAll %q returned wrong error path: %q not %q", ffpath, filepath.Clean(perr.Path), filepath.Clean(fpath))
	}

	path = tmpDir + `\dir\.\dir2\`
	err = MkdirAll(path, 0777)
	if err != nil {
		t.Fatalf("MkdirAll %q: %s", path, err)
	}
}

func unusedDrive(t *testing.T) string {
	letter := FindUnusedDriveLetter()
	require.NotEqual(t, letter, 0)
	return string(letter) + ":"
}

func checkMkdirAll(t *testing.T, path string, valid bool, errormsgs ...string) {
	if valid {
		assert.NoError(t, MkdirAll(path, 0777))
	} else {
		err := MkdirAll(path, 0777)
		assert.Error(t, err)
		ok := false
		for _, msg := range errormsgs {
			if err.Error() == msg {
				ok = true
			}
		}
		assert.True(t, ok, fmt.Sprintf("Error message '%v' didn't match any of %v", err, errormsgs))
	}
}

func checkMkdirAllSubdirs(t *testing.T, path string, valid bool, errormsgs ...string) {
	checkMkdirAll(t, path, valid, errormsgs...)
	checkMkdirAll(t, path+`\`, valid, errormsgs...)
	checkMkdirAll(t, path+`\parent`, valid, errormsgs...)
	checkMkdirAll(t, path+`\parent\`, valid, errormsgs...)
	checkMkdirAll(t, path+`\parent\child`, valid, errormsgs...)
	checkMkdirAll(t, path+`\parent\child\`, valid, errormsgs...)
}

// Testing paths on existing drive
func TestMkdirAllOnDrive(t *testing.T) {
	path := t.TempDir()

	dir, err := os.Stat(path)
	require.NoError(t, err)
	require.True(t, dir.IsDir())

	drive := filepath.VolumeName(path)

	checkMkdirAll(t, drive, true, "")
	checkMkdirAll(t, drive+`\`, true, "")
	// checkMkdirAll(t, `\\?\`+drive, true, "") - this isn't actually a Valid Windows path - this test used to work under go1.21.3 but fails under go1.21.4
	checkMkdirAll(t, `\\?\`+drive+`\`, true, "")
	checkMkdirAllSubdirs(t, path, true, "")
	checkMkdirAllSubdirs(t, `\\?\`+path, true, "")
}

// Testing paths on unused drive
// This is where there is a difference from golang's os.MkdirAll. It would
// recurse extended-length paths down to the "\\?" prefix and return the
// noninformative error:
// "mkdir \\?: The filename, directory name, or volume label syntax is incorrect."
// Our version stops the recursion at drive's root directory, and reports:
// "mkdir \\?\A:\: The system cannot find the path specified."
func TestMkdirAllOnUnusedDrive(t *testing.T) {
	path := unusedDrive(t)
	errormsg := fmt.Sprintf(`mkdir %s\: The system cannot find the path specified.`, path)
	checkMkdirAllSubdirs(t, path, false, errormsg)
	errormsg1 := fmt.Sprintf(`mkdir \\?\%s\: The system cannot find the path specified.`, path) // pre go1.21.4
	errormsg2 := fmt.Sprintf(`mkdir \\?\%s: The system cannot find the file specified.`, path)  // go1.21.4 and after
	checkMkdirAllSubdirs(t, `\\?\`+path, false, errormsg1, errormsg2)
}

// Testing paths on unknown network host
// This is an additional difference from golang's os.MkdirAll. With our
// first fix, stopping it from recursing extended-length paths down to
// the "\\?" prefix, it would now stop at `\\?\UNC`, because that is what
// filepath.VolumeName returns (which is wrong, that is not a volume name!),
// and still return a nonifnromative error:
// "mkdir \\?\UNC\\: The filename, directory name, or volume label syntax is incorrect."
// Our version stops the recursion at level before this, and reports:
// "mkdir \\?\UNC\0.0.0.0: The specified path is invalid."
func TestMkdirAllOnUnusedNetworkHost(t *testing.T) {
	path := `\\0.0.0.0\share`
	errormsg := fmt.Sprintf("mkdir %s\\: The format of the specified network name is invalid.", path)
	checkMkdirAllSubdirs(t, path, false, errormsg)
	path = `\\?\UNC\0.0.0.0\share`
	checkMkdirAllSubdirs(t, path, false,
		`mkdir \\?\UNC\0.0.0.0: The specified path is invalid.`, // pre go1.20
		`mkdir \\?\UNC\0.0.0.0\share\: The format of the specified network name is invalid.`,
	)

}