package osvfs

import (
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
	"gotest.tools/v3/assert"
	"gotest.tools/v3/assert/cmp"
)

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

	targetFile, linkFile := setupSymlinks(t)

	gotContents, err := os.ReadFile(linkFile)
	assert.NilError(t, err)
	assert.Equal(t, string(gotContents), "hello")

	fs := FS()

	targetRealpath := fs.Realpath(tspath.NormalizePath(targetFile))
	linkRealpath := fs.Realpath(tspath.NormalizePath(linkFile))

	if !assert.Check(t, cmp.Equal(targetRealpath, linkRealpath)) {
		cmd := exec.Command("node", "-e", `console.log({ native: fs.realpathSync.native(process.argv[1]), node: fs.realpathSync(process.argv[1]) })`, linkFile)
		out, err := cmd.CombinedOutput()
		assert.NilError(t, err)
		t.Logf("node: %s", out)
	}
}

func setupSymlinks(tb testing.TB) (targetFile, linkFile string) {
	tb.Helper()

	tmp := tb.TempDir()

	target := filepath.Join(tmp, "target")
	targetFile = filepath.Join(target, "file")

	link := filepath.Join(tmp, "link")
	linkFile = filepath.Join(link, "file")

	assert.NilError(tb, os.MkdirAll(target, 0o777))
	assert.NilError(tb, os.WriteFile(targetFile, []byte("hello"), 0o666))

	mklink(tb, target, link, true)

	return targetFile, linkFile
}

func mklink(tb testing.TB, target, link string, isDir bool) {
	tb.Helper()

	if runtime.GOOS == "windows" && isDir {
		// Don't use os.Symlink on Windows, as it creates a "real" symlink, not a junction.
		assert.NilError(tb, exec.Command("cmd", "/c", "mklink", "/J", link, target).Run())
	} else {
		err := os.Symlink(target, link)
		if err != nil && !isDir && runtime.GOOS == "windows" && strings.Contains(err.Error(), "A required privilege is not held by the client") {
			tb.Log(err)
			tb.Skip("file symlink support is not enabled without elevation or developer mode")
		}
		assert.NilError(tb, err)
	}
}

func BenchmarkRealpath(b *testing.B) {
	targetFile, linkFile := setupSymlinks(b)

	fs := FS()
	normalizedTargetFile := tspath.NormalizePath(targetFile)
	normalizedLinkFile := tspath.NormalizePath(linkFile)

	b.Run("target", func(b *testing.B) {
		b.ReportAllocs()

		for b.Loop() {
			fs.Realpath(normalizedTargetFile)
		}
	})

	b.Run("link", func(b *testing.B) {
		b.ReportAllocs()

		for b.Loop() {
			fs.Realpath(normalizedLinkFile)
		}
	})
}

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

	tmp := t.TempDir()
	target := filepath.Join(tmp, "target")
	link := filepath.Join(tmp, "link")

	assert.NilError(t, os.MkdirAll(target, 0o777))
	assert.NilError(t, os.MkdirAll(link, 0o777))

	targetFile1 := filepath.Join(target, "file1")
	targetFile2 := filepath.Join(target, "file2")

	assert.NilError(t, os.WriteFile(targetFile1, []byte("hello"), 0o666))
	assert.NilError(t, os.WriteFile(targetFile2, []byte("world"), 0o666))

	targetDir1 := filepath.Join(target, "dir1")
	targetDir2 := filepath.Join(target, "dir2")

	assert.NilError(t, os.MkdirAll(targetDir1, 0o777))
	assert.NilError(t, os.MkdirAll(targetDir2, 0o777))

	mklink(t, targetFile1, filepath.Join(link, "file1"), false)
	mklink(t, targetFile2, filepath.Join(link, "file2"), false)
	mklink(t, targetDir1, filepath.Join(link, "dir1"), true)
	mklink(t, targetDir2, filepath.Join(link, "dir2"), true)

	fs := FS()

	entries := fs.GetAccessibleEntries(tspath.NormalizePath(link))

	assert.DeepEqual(t, entries.Directories, []string{"dir1", "dir2"})
	assert.DeepEqual(t, entries.Files, []string{"file1", "file2"})
}
