package file

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"runtime"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// This lists dir and checks the listing is as expected without checking the size
func checkListingNoSize(t *testing.T, dir string, want []string) {
	var got []string
	nodes, err := ioutil.ReadDir(dir)
	require.NoError(t, err)
	for _, node := range nodes {
		got = append(got, fmt.Sprintf("%s,%v", node.Name(), node.IsDir()))
	}
	assert.Equal(t, want, got)
}

// This lists dir and checks the listing is as expected
func checkListing(t *testing.T, dir string, want []string) {
	var got []string
	nodes, err := ioutil.ReadDir(dir)
	require.NoError(t, err)
	for _, node := range nodes {
		got = append(got, fmt.Sprintf("%s,%d,%v", node.Name(), node.Size(), node.IsDir()))
	}
	assert.Equal(t, want, got)
}

// Test we can rename an open file
func TestOpenFileRename(t *testing.T) {
	dir := t.TempDir()

	filepath := path.Join(dir, "file1")
	f, err := Create(filepath)
	require.NoError(t, err)

	_, err = f.Write([]byte("hello"))
	assert.NoError(t, err)

	checkListingNoSize(t, dir, []string{
		"file1,false",
	})

	// Delete the file first
	assert.NoError(t, os.Remove(filepath))

	// .. then close it
	assert.NoError(t, f.Close())

	checkListing(t, dir, nil)
}

// Test we can delete an open file
func TestOpenFileDelete(t *testing.T) {
	dir := t.TempDir()

	filepath := path.Join(dir, "file1")
	f, err := Create(filepath)
	require.NoError(t, err)

	_, err = f.Write([]byte("hello"))
	assert.NoError(t, err)

	checkListingNoSize(t, dir, []string{
		"file1,false",
	})

	// Rename the file while open
	filepath2 := path.Join(dir, "file2")
	assert.NoError(t, os.Rename(filepath, filepath2))

	checkListingNoSize(t, dir, []string{
		"file2,false",
	})

	// .. then close it
	assert.NoError(t, f.Close())

	checkListing(t, dir, []string{
		"file2,5,false",
	})
}

// Smoke test the Open, OpenFile and Create functions
func TestOpenFileOperations(t *testing.T) {
	dir := t.TempDir()

	filepath := path.Join(dir, "file1")

	// Create the file

	f, err := Create(filepath)
	require.NoError(t, err)

	_, err = f.Write([]byte("hello"))
	assert.NoError(t, err)

	assert.NoError(t, f.Close())

	checkListing(t, dir, []string{
		"file1,5,false",
	})

	// Append onto the file

	f, err = OpenFile(filepath, os.O_RDWR|os.O_APPEND, 0666)
	require.NoError(t, err)

	_, err = f.Write([]byte("HI"))
	assert.NoError(t, err)

	assert.NoError(t, f.Close())

	checkListing(t, dir, []string{
		"file1,7,false",
	})

	// Read it back in

	f, err = Open(filepath)
	require.NoError(t, err)
	var b = make([]byte, 10)
	n, err := f.Read(b)
	assert.True(t, err == io.EOF || err == nil)
	assert.Equal(t, 7, n)
	assert.Equal(t, "helloHI", string(b[:n]))

	assert.NoError(t, f.Close())

	checkListing(t, dir, []string{
		"file1,7,false",
	})

}

// Smoke test the IsReserved function
func TestIsReserved(t *testing.T) {
	if runtime.GOOS != "windows" {
		t.Skip("Skipping test on !windows")
	}
	// Regular name
	require.NoError(t, IsReserved("readme.txt"))
	require.NoError(t, IsReserved("some/path/readme.txt"))
	// Empty
	require.Error(t, IsReserved(""))
	// Separators only
	require.Error(t, IsReserved("/"))
	require.Error(t, IsReserved("////"))
	require.Error(t, IsReserved("./././././"))
	// Legacy device name
	require.Error(t, IsReserved("NUL"))
	require.Error(t, IsReserved("nul"))
	require.Error(t, IsReserved("Nul"))
	require.Error(t, IsReserved("NUL.txt"))
	require.Error(t, IsReserved("some/path/to/nul.txt"))
	require.NoError(t, IsReserved("NULL"))
	// Name end with a space or a period
	require.Error(t, IsReserved("test."))
	require.Error(t, IsReserved("test "))
}