// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fstest

import (
	"errors"
	"internal/testenv"
	"io/fs"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"testing"
)

func TestSymlink(t *testing.T) {
	testenv.MustHaveSymlink(t)

	tmp := t.TempDir()
	tmpfs := os.DirFS(tmp)

	if err := os.WriteFile(filepath.Join(tmp, "hello"), []byte("hello, world\n"), 0644); err != nil {
		t.Fatal(err)
	}

	if err := os.Symlink(filepath.Join(tmp, "hello"), filepath.Join(tmp, "hello.link")); err != nil {
		t.Fatal(err)
	}
	if err := os.Symlink("hello", filepath.Join(tmp, "hello_rel.link")); err != nil {
		t.Fatal(err)
	}

	if err := TestFS(tmpfs, "hello", "hello.link"); err != nil {
		t.Fatal(err)
	}
}

func TestDash(t *testing.T) {
	m := MapFS{
		"a-b/a": {Data: []byte("a-b/a")},
	}
	if err := TestFS(m, "a-b/a"); err != nil {
		t.Error(err)
	}
}

type shuffledFS MapFS

func (fsys shuffledFS) Open(name string) (fs.File, error) {
	f, err := MapFS(fsys).Open(name)
	if err != nil {
		return nil, err
	}
	return &shuffledFile{File: f}, nil
}

type shuffledFile struct{ fs.File }

func (f *shuffledFile) ReadDir(n int) ([]fs.DirEntry, error) {
	dirents, err := f.File.(fs.ReadDirFile).ReadDir(n)
	// Shuffle in a deterministic way, all we care about is making sure that the
	// list of directory entries is not is the lexicographic order.
	//
	// We do this to make sure that the TestFS test suite is not affected by the
	// order of directory entries.
	slices.SortFunc(dirents, func(a, b fs.DirEntry) int {
		return strings.Compare(b.Name(), a.Name())
	})
	return dirents, err
}

func TestShuffledFS(t *testing.T) {
	fsys := shuffledFS{
		"tmp/one":   {Data: []byte("1")},
		"tmp/two":   {Data: []byte("2")},
		"tmp/three": {Data: []byte("3")},
	}
	if err := TestFS(fsys, "tmp/one", "tmp/two", "tmp/three"); err != nil {
		t.Error(err)
	}
}

// failPermFS is a filesystem that always fails with fs.ErrPermission.
type failPermFS struct{}

func (f failPermFS) Open(name string) (fs.File, error) {
	if !fs.ValidPath(name) {
		return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrInvalid}
	}
	return nil, &fs.PathError{Op: "open", Path: name, Err: fs.ErrPermission}
}

func TestTestFSWrappedErrors(t *testing.T) {
	err := TestFS(failPermFS{})
	if err == nil {
		t.Fatal("error expected")
	}
	t.Logf("Error (expecting wrapped fs.ErrPermission):\n%v", err)

	if !errors.Is(err, fs.ErrPermission) {
		t.Errorf("error should be a wrapped ErrPermission: %#v", err)
	}

	// TestFS is expected to return a list of errors.
	// Enforce that the list can be extracted for browsing.
	type wrapper interface {
		error
		Unwrap() []error
	}
	errs, ok := errors.AsType[wrapper](err)
	if !ok {
		t.Errorf("caller should be able to extract the errors as a list: %#v", err)
	} else {
		for _, err := range errs.Unwrap() {
			// ErrPermission is expected
			// but any other error must be reported.
			if !errors.Is(err, fs.ErrPermission) {
				t.Errorf("unexpected error: %v", err)
			}
		}
	}
}
