package mount

import (
	"strings"
	"syscall"
	"testing"
	"unicode/utf8"

	"github.com/seaweedfs/go-fuse/v2/fuse"
)

// TestSanitizeFuseName_InvalidBytesReplaced reproduces the filename from
// seaweedfs#9139: GNOME Trash "partial" files carry raw binary bytes
// (\x10\x98=\\\x8a\x7f) that are not valid UTF-8. The sanitizer must return a
// UTF-8-valid string so the subsequent proto marshal cannot fail.
func TestSanitizeFuseName_InvalidBytesReplaced(t *testing.T) {
	raw := "\x10\x98=\\\x8a\x7f.trashinfo.9a51454f.partial"
	out := sanitizeFuseName(raw)
	if !utf8.ValidString(out) {
		t.Fatalf("sanitizeFuseName returned non-UTF-8: %q", out)
	}
	// The replacement must be URL-safe: these sanitized names flow into HTTP
	// URLs (volume-server uploads, filer HTTP API, S3/WebDAV gateways), so a
	// '?' would be interpreted as the query-string delimiter and split the
	// path. Explicitly assert the replacement char is neither '?' nor U+FFFD.
	if strings.ContainsRune(out, '?') {
		t.Fatalf("sanitizer produced non-URL-safe '?' in %q", out)
	}
	if strings.ContainsRune(out, 0xFFFD) {
		t.Fatalf("expected single-byte replacement, got U+FFFD in %q", out)
	}
	// Valid bytes (\x10, \x3D '=', \x5C '\\', \x7F) must be preserved; only
	// \x98 and \x8A — the standalone continuation bytes — get replaced.
	if !strings.HasSuffix(out, ".trashinfo.9a51454f.partial") {
		t.Fatalf("trailing valid bytes were dropped: %q", out)
	}
}

func TestSanitizeFuseName_PassThroughValidUTF8(t *testing.T) {
	// An already-valid UTF-8 string must be returned unchanged — no heap
	// allocation, no alteration of byte content. Preserving identity matters
	// because the overwhelming hot path is valid input.
	for _, s := range []string{
		"plain.txt",
		"日本語.txt",
		"🦑 squid",
		"",
		strings.Repeat("a", 255),
	} {
		if got := sanitizeFuseName(s); got != s {
			t.Fatalf("sanitizeFuseName(%q) = %q, want unchanged", s, got)
		}
	}
}

// TestCheckName_SanitizesBeforeLengthCheck verifies the caller contract: the
// returned name is always safe to put into a proto string field, and the
// length guard still fires on over-long inputs.
func TestCheckName_SanitizesBeforeLengthCheck(t *testing.T) {
	bad := "foo\x80bar"
	got, s := checkName(bad)
	if s != fuse.OK {
		t.Fatalf("checkName(%q) status = %v, want OK", bad, s)
	}
	if !utf8.ValidString(got) {
		t.Fatalf("checkName did not sanitize: %q", got)
	}

	tooLong := strings.Repeat("x", 300)
	if _, s := checkName(tooLong); s != fuse.Status(syscall.ENAMETOOLONG) {
		t.Fatalf("checkName length guard lost: status=%v", s)
	}
}
