package rc

import (
	"context"
	"fmt"
	"testing"

	"github.com/rclone/rclone/fs"
	"github.com/rclone/rclone/fs/cache"
	"github.com/rclone/rclone/fs/filter"
	"github.com/rclone/rclone/fstest/mockfs"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func mockNewFs(t *testing.T) func() {
	ctx := context.Background()
	f, err := mockfs.NewFs(ctx, "/", "", nil)
	require.NoError(t, err)
	cache.Put("/", f)
	f, err = mockfs.NewFs(ctx, "mock", "/", nil)
	require.NoError(t, err)
	cache.Put("mock:/", f)
	cache.Put(":mock:/", f)
	f, err = mockfs.NewFs(ctx, "mock", "dir/", nil)
	require.NoError(t, err)
	cache.PutErr("mock:dir/file.txt", f, fs.ErrorIsFile)
	return func() {
		cache.Clear()
	}
}

func TestGetFsNamed(t *testing.T) {
	defer mockNewFs(t)()

	in := Params{
		"potato": "/",
	}
	f, err := GetFsNamed(context.Background(), in, "potato")
	require.NoError(t, err)
	assert.NotNil(t, f)

	in = Params{
		"sausage": "/",
	}
	f, err = GetFsNamed(context.Background(), in, "potato")
	require.Error(t, err)
	assert.Nil(t, f)
}

func TestGetFsNamedStruct(t *testing.T) {
	defer mockNewFs(t)()

	in := Params{
		"potato": Params{
			"type":  "mock",
			"_root": "/",
		},
	}
	f, err := GetFsNamed(context.Background(), in, "potato")
	require.NoError(t, err)
	assert.NotNil(t, f)

	in = Params{
		"potato": Params{
			"_name": "mock",
			"_root": "/",
		},
	}
	f, err = GetFsNamed(context.Background(), in, "potato")
	require.NoError(t, err)
	assert.NotNil(t, f)
}

func TestGetFsNamedFileOK(t *testing.T) {
	defer mockNewFs(t)()
	ctx := context.Background()

	in := Params{
		"potato": "/",
	}
	newCtx, f, err := GetFsNamedFileOK(ctx, in, "potato")
	require.NoError(t, err)
	assert.NotNil(t, f)
	assert.Equal(t, ctx, newCtx)

	in = Params{
		"sausage": "/",
	}
	newCtx, f, err = GetFsNamedFileOK(ctx, in, "potato")
	require.Error(t, err)
	assert.Nil(t, f)
	assert.Equal(t, ctx, newCtx)

	in = Params{
		"potato": "mock:dir/file.txt",
	}
	newCtx, f, err = GetFsNamedFileOK(ctx, in, "potato")
	assert.Nil(t, err)
	assert.NotNil(t, f)
	assert.NotEqual(t, ctx, newCtx)

	fi := filter.GetConfig(newCtx)
	assert.False(t, fi.InActive())
	assert.True(t, fi.IncludeRemote("file.txt"))
	assert.False(t, fi.IncludeRemote("other.txt"))
}

func TestGetConfigMap(t *testing.T) {
	for _, test := range []struct {
		in           Params
		fsName       string
		wantFsString string
		wantErr      string
	}{
		{
			in: Params{
				"Fs": Params{},
			},
			fsName:  "Fs",
			wantErr: `couldn't find "type" or "_name" in JSON config definition`,
		},
		{
			in: Params{
				"Fs": Params{
					"notastring": true,
				},
			},
			fsName:  "Fs",
			wantErr: `cannot unmarshal bool`,
		},
		{
			in: Params{
				"Fs": Params{
					"_name": "potato",
				},
			},
			fsName:       "Fs",
			wantFsString: "potato:",
		},
		{
			in: Params{
				"Fs": Params{
					"type": "potato",
				},
			},
			fsName:       "Fs",
			wantFsString: ":potato:",
		},
		{
			in: Params{
				"Fs": Params{
					"type":       "sftp",
					"_name":      "potato",
					"parameter":  "42",
					"parameter2": "true",
					"_root":      "/path/to/somewhere",
				},
			},
			fsName:       "Fs",
			wantFsString: "potato,parameter='42',parameter2='true':/path/to/somewhere",
		},
	} {
		gotFsString, gotErr := getConfigMap(test.in, test.fsName)
		what := fmt.Sprintf("%+v", test.in)
		assert.Equal(t, test.wantFsString, gotFsString, what)
		if test.wantErr == "" {
			assert.NoError(t, gotErr)
		} else {
			require.Error(t, gotErr)
			assert.Contains(t, gotErr.Error(), test.wantErr)

		}
	}
}

func TestGetFs(t *testing.T) {
	defer mockNewFs(t)()

	in := Params{
		"fs": "/",
	}
	f, err := GetFs(context.Background(), in)
	require.NoError(t, err)
	assert.NotNil(t, f)
}

func TestGetFsAndRemoteNamed(t *testing.T) {
	defer mockNewFs(t)()

	in := Params{
		"fs":     "/",
		"remote": "hello",
	}
	f, remote, err := GetFsAndRemoteNamed(context.Background(), in, "fs", "remote")
	require.NoError(t, err)
	assert.NotNil(t, f)
	assert.Equal(t, "hello", remote)

	f, _, err = GetFsAndRemoteNamed(context.Background(), in, "fsX", "remote")
	require.Error(t, err)
	assert.Nil(t, f)

	f, _, err = GetFsAndRemoteNamed(context.Background(), in, "fs", "remoteX")
	require.Error(t, err)
	assert.Nil(t, f)

}

func TestGetFsAndRemote(t *testing.T) {
	defer mockNewFs(t)()

	in := Params{
		"fs":     "/",
		"remote": "hello",
	}
	f, remote, err := GetFsAndRemote(context.Background(), in)
	require.NoError(t, err)
	assert.NotNil(t, f)
	assert.Equal(t, "hello", remote)

	t.Run("RcFscache", func(t *testing.T) {
		getEntries := func() int {
			call := Calls.Get("fscache/entries")
			require.NotNil(t, call)

			in := Params{}
			out, err := call.Fn(context.Background(), in)
			require.NoError(t, err)
			require.NotNil(t, out)
			return out["entries"].(int)
		}

		t.Run("Entries", func(t *testing.T) {
			assert.NotEqual(t, 0, getEntries())
		})

		t.Run("Clear", func(t *testing.T) {
			call := Calls.Get("fscache/clear")
			require.NotNil(t, call)

			in := Params{}
			out, err := call.Fn(context.Background(), in)
			require.NoError(t, err)
			require.Nil(t, out)
		})

		t.Run("Entries2", func(t *testing.T) {
			assert.Equal(t, 0, getEntries())
		})
	})
}
