// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package fs

import (
	"errors"
	"fmt"
	osexec "os/exec"
	"path/filepath"
	"runtime"
	"testing"
)

func testWalkSkipSymlink(t *testing.T, fsType FilesystemType, uri string) {
	if runtime.GOOS == "windows" {
		t.Skip("Symlinks skipping is not tested on windows")
	}

	fs := NewFilesystem(fsType, uri)

	if err := fs.MkdirAll("target/foo", 0755); err != nil {
		t.Fatal(err)
	}
	if err := fs.Mkdir("towalk", 0755); err != nil {
		t.Fatal(err)
	}
	if err := fs.CreateSymlink("target", "towalk/symlink"); err != nil {
		t.Fatal(err)
	}
	if err := fs.Walk("towalk", func(path string, info FileInfo, err error) error {
		if err != nil {
			t.Fatal(err)
		}
		if info.Name() != "symlink" && info.Name() != "towalk" {
			t.Fatal("Walk unexpected file", info.Name())
		}
		return nil
	}); err != nil {
		t.Fatal(err)
	}
}

func createDirJunct(target string, name string) error {
	output, err := osexec.Command("cmd", "/c", "mklink", "/J", name, target).CombinedOutput()
	if err != nil {
		return fmt.Errorf("Failed to run mklink %v %v: %v %q", name, target, err, output)
	}
	return nil
}

func testWalkTraverseDirJunct(t *testing.T, fsType FilesystemType, uri string) {
	if runtime.GOOS != "windows" {
		t.Skip("Directory junctions are available and tested on windows only")
	}

	fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))

	if err := fs.MkdirAll("target/foo", 0); err != nil {
		t.Fatal(err)
	}
	if err := fs.Mkdir("towalk", 0); err != nil {
		t.Fatal(err)
	}
	if err := createDirJunct(filepath.Join(uri, "target"), filepath.Join(uri, "towalk/dirjunct")); err != nil {
		t.Fatal(err)
	}
	traversed := false
	if err := fs.Walk("towalk", func(path string, info FileInfo, err error) error {
		if err != nil {
			t.Fatal(err)
		}
		if info.Name() == "foo" {
			traversed = true
		}
		return nil
	}); err != nil {
		t.Fatal(err)
	}
	if !traversed {
		t.Fatal("Directory junction was not traversed")
	}
}

func testWalkInfiniteRecursion(t *testing.T, fsType FilesystemType, uri string) {
	if runtime.GOOS != "windows" {
		t.Skip("Infinite recursion detection is tested on windows only")
	}

	fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))

	if err := fs.MkdirAll("target/foo", 0); err != nil {
		t.Fatal(err)
	}
	if err := fs.Mkdir("towalk", 0); err != nil {
		t.Fatal(err)
	}
	if err := createDirJunct(filepath.Join(uri, "target"), filepath.Join(uri, "towalk/dirjunct")); err != nil {
		t.Fatal(err)
	}
	if err := createDirJunct(filepath.Join(uri, "towalk"), filepath.Join(uri, "target/foo/recurse")); err != nil {
		t.Fatal(err)
	}
	dirjunctCnt := 0
	fooCnt := 0
	found := false
	if err := fs.Walk("towalk", func(path string, info FileInfo, err error) error {
		if err != nil {
			if errors.Is(err, ErrInfiniteRecursion) {
				if found {
					t.Fatal("second infinite recursion detected at", path)
				}
				found = true
				return nil
			}
			t.Fatal(err)
		}
		if info.Name() == "dirjunct" {
			dirjunctCnt++
		} else if info.Name() == "foo" {
			fooCnt++
		}
		return nil
	}); err != nil {
		t.Fatal(err)
	}
	if dirjunctCnt != 2 || fooCnt != 1 || !found {
		t.Fatal("Infinite recursion not detected correctly")
	}
}
