package layout

import (
	"fmt"
	"path"
	"path/filepath"
	"reflect"
	"sort"
	"strings"
	"testing"

	"github.com/restic/restic/internal/backend"
	rtest "github.com/restic/restic/internal/test"
)

func TestDefaultLayout(t *testing.T) {
	tempdir := rtest.TempDir(t)

	var tests = []struct {
		path string
		join func(...string) string
		backend.Handle
		filename string
	}{
		{
			tempdir,
			filepath.Join,
			backend.Handle{Type: backend.PackFile, Name: "0123456"},
			filepath.Join(tempdir, "data", "01", "0123456"),
		},
		{
			tempdir,
			filepath.Join,
			backend.Handle{Type: backend.ConfigFile, Name: "CFG"},
			filepath.Join(tempdir, "config"),
		},
		{
			tempdir,
			filepath.Join,
			backend.Handle{Type: backend.SnapshotFile, Name: "123456"},
			filepath.Join(tempdir, "snapshots", "123456"),
		},
		{
			tempdir,
			filepath.Join,
			backend.Handle{Type: backend.IndexFile, Name: "123456"},
			filepath.Join(tempdir, "index", "123456"),
		},
		{
			tempdir,
			filepath.Join,
			backend.Handle{Type: backend.LockFile, Name: "123456"},
			filepath.Join(tempdir, "locks", "123456"),
		},
		{
			tempdir,
			filepath.Join,
			backend.Handle{Type: backend.KeyFile, Name: "123456"},
			filepath.Join(tempdir, "keys", "123456"),
		},
		{
			"",
			path.Join,
			backend.Handle{Type: backend.PackFile, Name: "0123456"},
			"data/01/0123456",
		},
		{
			"",
			path.Join,
			backend.Handle{Type: backend.ConfigFile, Name: "CFG"},
			"config",
		},
		{
			"",
			path.Join,
			backend.Handle{Type: backend.SnapshotFile, Name: "123456"},
			"snapshots/123456",
		},
		{
			"",
			path.Join,
			backend.Handle{Type: backend.IndexFile, Name: "123456"},
			"index/123456",
		},
		{
			"",
			path.Join,
			backend.Handle{Type: backend.LockFile, Name: "123456"},
			"locks/123456",
		},
		{
			"",
			path.Join,
			backend.Handle{Type: backend.KeyFile, Name: "123456"},
			"keys/123456",
		},
	}

	t.Run("Paths", func(t *testing.T) {
		l := &DefaultLayout{
			path: tempdir,
			join: filepath.Join,
		}

		dirs := l.Paths()

		want := []string{
			filepath.Join(tempdir, "data"),
			filepath.Join(tempdir, "snapshots"),
			filepath.Join(tempdir, "index"),
			filepath.Join(tempdir, "locks"),
			filepath.Join(tempdir, "keys"),
		}

		for i := 0; i < 256; i++ {
			want = append(want, filepath.Join(tempdir, "data", fmt.Sprintf("%02x", i)))
		}

		sort.Strings(want)
		sort.Strings(dirs)

		if !reflect.DeepEqual(dirs, want) {
			t.Fatalf("wrong paths returned, want:\n  %v\ngot:\n  %v", want, dirs)
		}
	})

	for _, test := range tests {
		t.Run(fmt.Sprintf("%v/%v", test.Type, test.Handle.Name), func(t *testing.T) {
			l := &DefaultLayout{
				path: test.path,
				join: test.join,
			}

			filename := l.Filename(test.Handle)
			if filename != test.filename {
				t.Fatalf("wrong filename, want %v, got %v", test.filename, filename)
			}
		})
	}
}

func TestRESTLayout(t *testing.T) {
	url := `https://hostname.foo`

	var tests = []struct {
		backend.Handle
		filename string
	}{
		{
			backend.Handle{Type: backend.PackFile, Name: "0123456"},
			strings.Join([]string{url, "data", "0123456"}, "/"),
		},
		{
			backend.Handle{Type: backend.ConfigFile, Name: "CFG"},
			strings.Join([]string{url, "config"}, "/"),
		},
		{
			backend.Handle{Type: backend.SnapshotFile, Name: "123456"},
			strings.Join([]string{url, "snapshots", "123456"}, "/"),
		},
		{
			backend.Handle{Type: backend.IndexFile, Name: "123456"},
			strings.Join([]string{url, "index", "123456"}, "/"),
		},
		{
			backend.Handle{Type: backend.LockFile, Name: "123456"},
			strings.Join([]string{url, "locks", "123456"}, "/"),
		},
		{
			backend.Handle{Type: backend.KeyFile, Name: "123456"},
			strings.Join([]string{url, "keys", "123456"}, "/"),
		},
	}

	l := &RESTLayout{
		url: url,
	}

	t.Run("Paths", func(t *testing.T) {
		dirs := l.Paths()

		want := []string{
			strings.Join([]string{url, "data"}, "/"),
			strings.Join([]string{url, "snapshots"}, "/"),
			strings.Join([]string{url, "index"}, "/"),
			strings.Join([]string{url, "locks"}, "/"),
			strings.Join([]string{url, "keys"}, "/"),
		}

		sort.Strings(want)
		sort.Strings(dirs)

		if !reflect.DeepEqual(dirs, want) {
			t.Fatalf("wrong paths returned, want:\n  %v\ngot:\n  %v", want, dirs)
		}
	})

	for _, test := range tests {
		t.Run(fmt.Sprintf("%v/%v", test.Type, test.Handle.Name), func(t *testing.T) {
			filename := l.Filename(test.Handle)
			if filename != test.filename {
				t.Fatalf("wrong filename, want %v, got %v", test.filename, filename)
			}
		})
	}
}

func TestRESTLayoutURLs(t *testing.T) {
	var tests = []struct {
		l   Layout
		h   backend.Handle
		fn  string
		dir string
	}{
		{
			&RESTLayout{url: "https://hostname.foo"},
			backend.Handle{Type: backend.PackFile, Name: "foobar"},
			"https://hostname.foo/data/foobar",
			"https://hostname.foo/data/",
		},
		{
			&RESTLayout{url: "https://hostname.foo:1234/prefix/repo"},
			backend.Handle{Type: backend.LockFile, Name: "foobar"},
			"https://hostname.foo:1234/prefix/repo/locks/foobar",
			"https://hostname.foo:1234/prefix/repo/locks/",
		},
		{
			&RESTLayout{url: "https://hostname.foo:1234/prefix/repo"},
			backend.Handle{Type: backend.ConfigFile, Name: "foobar"},
			"https://hostname.foo:1234/prefix/repo/config",
			"https://hostname.foo:1234/prefix/repo/",
		},
	}

	for _, test := range tests {
		t.Run(fmt.Sprintf("%T", test.l), func(t *testing.T) {
			fn := test.l.Filename(test.h)
			if fn != test.fn {
				t.Fatalf("wrong filename, want %v, got %v", test.fn, fn)
			}

			dir := test.l.Dirname(test.h)
			if dir != test.dir {
				t.Fatalf("wrong dirname, want %v, got %v", test.dir, dir)
			}
		})
	}
}