package fusefrontend

import (
	"strings"
	"syscall"
	"testing"

	"golang.org/x/sys/unix"

	"github.com/hanwen/go-fuse/v2/fuse"

	"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
	"github.com/rfjakob/gocryptfs/v2/tests/test_helpers"
)

func TestPrepareAtSyscall(t *testing.T) {
	cipherdir := test_helpers.InitFS(t)
	t.Logf("cipherdir = %q", cipherdir)
	args := Args{
		Cipherdir: cipherdir,
	}
	rn := newTestFS(args)
	out := &fuse.EntryOut{}

	child, errno := rn.Mkdir(nil, "dir1", 0700, out)
	if errno != 0 {
		t.Fatal(errno)
	}
	rn.AddChild("dir1", child, false)
	dir1 := toNode(child.Operations())
	_, errno = dir1.Mkdir(nil, "dir2", 0700, out)
	if errno != 0 {
		t.Fatal(errno)
	}

	dirfd, cName, errno := rn.prepareAtSyscallMyself()
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName != "." {
		t.Fatal("cName should be .")
	}
	syscall.Close(dirfd)

	// Again, but populate the cache for "" by looking up a non-existing file
	rn.Lookup(nil, "xyz1234", &fuse.EntryOut{})
	dirfd, cName, errno = rn.prepareAtSyscallMyself()
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName != "." {
		t.Fatal("cName should be .")
	}

	err := syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
	if err != nil {
		t.Error(err)
	}
	err = syscallcompat.Faccessat(dirfd, ".", unix.R_OK)
	if err != nil {
		t.Error(err)
	}
	syscall.Close(dirfd)

	dirfd, cName, errno = rn.prepareAtSyscall("dir1")
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName == "" {
		t.Fatal("cName should not be empty")
	}
	err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
	if err != nil {
		t.Error(err)
	}
	syscall.Close(dirfd)

	dirfd, cName, errno = dir1.prepareAtSyscall("dir2")
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName == "" {
		t.Fatal("cName should not be empty")
	}
	err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
	if err != nil {
		t.Errorf("Faccessat(%d, %q): %v", dirfd, cName, err)
	}
	syscall.Close(dirfd)

	n255 := strings.Repeat("n", 255)
	dir1.Mkdir(nil, n255, 0700, out)
	dirfd, cName, errno = dir1.prepareAtSyscall(n255)
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName == "" {
		t.Fatal("cName should not be empty")
	}
	if len(cName) >= 255 {
		t.Fatalf("cName is too long: %q", cName)
	}
	err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
	if err != nil {
		t.Errorf("Faccessat(%d, %q): %v", dirfd, cName, err)
	}
	syscall.Close(dirfd)
}

func TestPrepareAtSyscallPlaintextnames(t *testing.T) {
	cipherdir := test_helpers.InitFS(t, "-plaintextnames")
	args := Args{
		Cipherdir:      cipherdir,
		PlaintextNames: true,
	}
	rn := newTestFS(args)
	out := &fuse.EntryOut{}

	child, errno := rn.Mkdir(nil, "dir1", 0700, out)
	if errno != 0 {
		t.Fatal(errno)
	}
	rn.AddChild("dir1", child, false)
	dir1 := toNode(child.Operations())
	_, errno = dir1.Mkdir(nil, "dir2", 0700, out)
	if errno != 0 {
		t.Fatal(errno)
	}

	dirfd, cName, errno := rn.prepareAtSyscallMyself()
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName != "." {
		t.Fatal("cName should be .")
	}
	err := syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
	if err != nil {
		t.Error(err)
	}
	err = syscallcompat.Faccessat(dirfd, ".", unix.R_OK)
	if err != nil {
		t.Error(err)
	}
	syscall.Close(dirfd)

	dirfd, cName, errno = rn.prepareAtSyscall("dir1")
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName != "dir1" {
		t.Fatalf("wrong cName: %q", cName)
	}
	err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
	if err != nil {
		t.Error(err)
	}
	syscall.Close(dirfd)

	dirfd, cName, errno = dir1.prepareAtSyscall("dir2")
	if errno != 0 {
		t.Fatal(errno)
	}
	if cName != "dir2" {
		t.Fatalf("wrong cName: %q", cName)
	}
	err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)
	if err != nil {
		t.Error(err)
	}
	syscall.Close(dirfd)
}