// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package hugofs

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/gohugoio/hugo/common/loggers"

	"github.com/gohugoio/hugo/htesting"

	"github.com/spf13/afero"

	qt "github.com/frankban/quicktest"
)

func prepareSymlinks(t *testing.T) (string, func()) {
	c := qt.New(t)

	workDir, clean, err := htesting.CreateTempDir(Os, "hugo-symlink-test")
	c.Assert(err, qt.IsNil)
	wd, _ := os.Getwd()

	blogDir := filepath.Join(workDir, "blog")
	blogSubDir := filepath.Join(blogDir, "sub")
	c.Assert(os.MkdirAll(blogSubDir, 0777), qt.IsNil)
	blogFile1 := filepath.Join(blogDir, "a.txt")
	blogFile2 := filepath.Join(blogSubDir, "b.txt")
	afero.WriteFile(Os, filepath.Join(blogFile1), []byte("content1"), 0777)
	afero.WriteFile(Os, filepath.Join(blogFile2), []byte("content2"), 0777)
	os.Chdir(workDir)
	c.Assert(os.Symlink("blog", "symlinkdedir"), qt.IsNil)
	os.Chdir(blogDir)
	c.Assert(os.Symlink("sub", "symsub"), qt.IsNil)
	c.Assert(os.Symlink("a.txt", "symlinkdedfile.txt"), qt.IsNil)

	return workDir, func() {
		clean()
		os.Chdir(wd)
	}
}

func TestNoSymlinkFs(t *testing.T) {
	if skipSymlink() {
		t.Skip("Skip; os.Symlink needs administrator rights on Windows")
	}
	c := qt.New(t)
	workDir, clean := prepareSymlinks(t)
	defer clean()

	blogDir := filepath.Join(workDir, "blog")
	blogFile1 := filepath.Join(blogDir, "a.txt")

	logger := loggers.NewWarningLogger()

	for _, bfs := range []afero.Fs{NewBaseFileDecorator(Os), Os} {
		for _, allowFiles := range []bool{false, true} {
			logger.LogCounters().WarnCounter.Reset()
			fs := NewNoSymlinkFs(bfs, logger, allowFiles)
			ls := fs.(afero.Lstater)
			symlinkedDir := filepath.Join(workDir, "symlinkdedir")
			symlinkedFilename := "symlinkdedfile.txt"
			symlinkedFile := filepath.Join(blogDir, symlinkedFilename)

			assertFileErr := func(err error) {
				if allowFiles {
					c.Assert(err, qt.IsNil)
				} else {
					c.Assert(err, qt.Equals, ErrPermissionSymlink)
				}
			}

			assertFileStat := func(name string, fi os.FileInfo, err error) {
				t.Helper()
				assertFileErr(err)
				if err == nil {
					c.Assert(fi, qt.Not(qt.IsNil))
					c.Assert(fi.Name(), qt.Equals, name)
				}
			}

			// Check Stat and Lstat
			for _, stat := range []func(name string) (os.FileInfo, error){
				func(name string) (os.FileInfo, error) {
					return fs.Stat(name)
				},
				func(name string) (os.FileInfo, error) {
					fi, _, err := ls.LstatIfPossible(name)
					return fi, err
				},
			} {
				_, err := stat(symlinkedDir)
				c.Assert(err, qt.Equals, ErrPermissionSymlink)
				fi, err := stat(symlinkedFile)
				assertFileStat(symlinkedFilename, fi, err)

				fi, err = stat(filepath.Join(workDir, "blog"))
				c.Assert(err, qt.IsNil)
				c.Assert(fi, qt.Not(qt.IsNil))

				fi, err = stat(blogFile1)
				c.Assert(err, qt.IsNil)
				c.Assert(fi, qt.Not(qt.IsNil))
			}

			// Check Open
			_, err := fs.Open(symlinkedDir)
			c.Assert(err, qt.Equals, ErrPermissionSymlink)
			_, err = fs.OpenFile(symlinkedDir, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
			c.Assert(err, qt.Equals, ErrPermissionSymlink)
			_, err = fs.OpenFile(symlinkedFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
			assertFileErr(err)
			_, err = fs.Open(symlinkedFile)
			assertFileErr(err)
			f, err := fs.Open(blogDir)
			c.Assert(err, qt.IsNil)
			f.Close()
			f, err = fs.Open(blogFile1)
			c.Assert(err, qt.IsNil)
			f.Close()

			// Check readdir
			f, err = fs.Open(workDir)
			c.Assert(err, qt.IsNil)
			// There is at least one unsupported symlink inside workDir
			_, err = f.Readdir(-1)
			c.Assert(err, qt.IsNil)
			f.Close()
			c.Assert(logger.LogCounters().WarnCounter.Count(), qt.Equals, uint64(1))

		}
	}
}
