//go:build darwin || freebsd || linux

package fuse

import (
	"strings"
	"testing"
	"time"

	"github.com/restic/restic/internal/data"
	"github.com/restic/restic/internal/restic"
	"github.com/restic/restic/internal/test"
)

func TestPathsFromSn(t *testing.T) {
	id1, _ := restic.ParseID("1234567812345678123456781234567812345678123456781234567812345678")
	time1, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T00:00:01")
	sn1 := &data.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time1}
	data.TestSetSnapshotID(t, sn1, id1)

	var p []string
	var s string

	p, s = pathsFromSn("ids/%i", "2006-01-02T15:04:05", sn1)
	test.Equals(t, []string{"ids/12345678"}, p)
	test.Equals(t, "", s)

	p, s = pathsFromSn("snapshots/%T", "2006-01-02T15:04:05", sn1)
	test.Equals(t, []string{"snapshots/"}, p)
	test.Equals(t, "2021-01-01T00:00:01", s)

	p, s = pathsFromSn("hosts/%h/%T", "2006-01-02T15:04:05", sn1)
	test.Equals(t, []string{"hosts/host/"}, p)
	test.Equals(t, "2021-01-01T00:00:01", s)

	p, s = pathsFromSn("tags/%t/%T", "2006-01-02T15:04:05", sn1)
	test.Equals(t, []string{"tags/tag1/", "tags/tag2/"}, p)
	test.Equals(t, "2021-01-01T00:00:01", s)

	p, s = pathsFromSn("users/%u/%T", "2006-01-02T15:04:05", sn1)
	test.Equals(t, []string{"users/user/"}, p)
	test.Equals(t, "2021-01-01T00:00:01", s)

	p, s = pathsFromSn("longids/%I", "2006-01-02T15:04:05", sn1)
	test.Equals(t, []string{"longids/1234567812345678123456781234567812345678123456781234567812345678"}, p)
	test.Equals(t, "", s)

	p, s = pathsFromSn("%T/%h", "2006/01/02", sn1)
	test.Equals(t, []string{"2021/01/01/host"}, p)
	test.Equals(t, "", s)

	p, s = pathsFromSn("%T/%i", "2006/01", sn1)
	test.Equals(t, []string{"2021/01/12345678"}, p)
	test.Equals(t, "", s)
}

func TestMakeDirs(t *testing.T) {
	pathTemplates := []string{"ids/%i", "snapshots/%T", "hosts/%h/%T",
		"tags/%t/%T", "users/%u/%T", "longids/%I", "%T/%h", "%T/%i",
	}
	timeTemplate := "2006/01/02"

	sds := &SnapshotsDirStructure{
		pathTemplates: pathTemplates,
		timeTemplate:  timeTemplate,
	}

	id0, _ := restic.ParseID("0000000012345678123456781234567812345678123456781234567812345678")
	time0, _ := time.Parse("2006-01-02T15:04:05", "2020-12-31T00:00:01")
	sn0 := &data.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time0}
	data.TestSetSnapshotID(t, sn0, id0)

	id1, _ := restic.ParseID("1234567812345678123456781234567812345678123456781234567812345678")
	time1, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T00:00:01")
	sn1 := &data.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time1}
	data.TestSetSnapshotID(t, sn1, id1)

	id2, _ := restic.ParseID("8765432112345678123456781234567812345678123456781234567812345678")
	time2, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T01:02:03")
	sn2 := &data.Snapshot{Hostname: "host2", Username: "user2", Tags: []string{"tag2", "tag3", "tag4"}, Time: time2}
	data.TestSetSnapshotID(t, sn2, id2)

	id3, _ := restic.ParseID("aaaaaaaa12345678123456781234567812345678123456781234567812345678")
	time3, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T01:02:03")
	sn3 := &data.Snapshot{Hostname: "host", Username: "user2", Tags: []string{}, Time: time3}
	data.TestSetSnapshotID(t, sn3, id3)

	sds.makeDirs(data.Snapshots{sn0, sn1, sn2, sn3})

	expNames := make(map[string]*data.Snapshot)
	expLatest := make(map[string]string)

	// entries for sn0
	expNames["/ids/00000000"] = sn0
	expNames["/snapshots/2020/12/31"] = sn0
	expNames["/hosts/host/2020/12/31"] = sn0
	expNames["/tags/tag1/2020/12/31"] = sn0
	expNames["/tags/tag2/2020/12/31"] = sn0
	expNames["/users/user/2020/12/31"] = sn0
	expNames["/longids/0000000012345678123456781234567812345678123456781234567812345678"] = sn0
	expNames["/2020/12/31/host"] = sn0
	expNames["/2020/12/31/00000000"] = sn0

	// entries for sn1
	expNames["/ids/12345678"] = sn1
	expNames["/snapshots/2021/01/01"] = sn1
	expNames["/hosts/host/2021/01/01"] = sn1
	expNames["/tags/tag1/2021/01/01"] = sn1
	expNames["/tags/tag2/2021/01/01"] = sn1
	expNames["/users/user/2021/01/01"] = sn1
	expNames["/longids/1234567812345678123456781234567812345678123456781234567812345678"] = sn1
	expNames["/2021/01/01/host"] = sn1
	expNames["/2021/01/01/12345678"] = sn1

	// entries for sn2
	expNames["/ids/87654321"] = sn2
	expNames["/snapshots/2021/01/01-1"] = sn2 // sn1 and sn2 have same time string
	expNames["/hosts/host2/2021/01/01"] = sn2
	expNames["/tags/tag2/2021/01/01-1"] = sn2 // sn1 and sn2 have same time string
	expNames["/tags/tag3/2021/01/01"] = sn2
	expNames["/tags/tag4/2021/01/01"] = sn2
	expNames["/users/user2/2021/01/01"] = sn2
	expNames["/longids/8765432112345678123456781234567812345678123456781234567812345678"] = sn2
	expNames["/2021/01/01/host2"] = sn2
	expNames["/2021/01/01/87654321"] = sn2

	// entries for sn3
	expNames["/ids/aaaaaaaa"] = sn3
	expNames["/snapshots/2021/01/01-2"] = sn3   // sn1 - sn3 have same time string
	expNames["/hosts/host/2021/01/01-1"] = sn3  // sn1 and sn3 have same time string
	expNames["/users/user2/2021/01/01-1"] = sn3 // sn2 and sn3 have same time string
	expNames["/longids/aaaaaaaa12345678123456781234567812345678123456781234567812345678"] = sn3
	expNames["/2021/01/01/host-1"] = sn3 // sn1 and sn3 have same time string and identical host
	expNames["/2021/01/01/aaaaaaaa"] = sn3

	// intermediate directories
	// sn0
	expNames["/ids"] = nil
	expNames[""] = nil
	expNames["/snapshots/2020/12"] = nil
	expNames["/snapshots/2020"] = nil
	expNames["/snapshots"] = nil
	expNames["/hosts/host/2020/12"] = nil
	expNames["/hosts/host/2020"] = nil
	expNames["/hosts/host"] = nil
	expNames["/hosts"] = nil
	expNames["/tags/tag1/2020/12"] = nil
	expNames["/tags/tag1/2020"] = nil
	expNames["/tags/tag1"] = nil
	expNames["/tags"] = nil
	expNames["/tags/tag2/2020/12"] = nil
	expNames["/tags/tag2/2020"] = nil
	expNames["/tags/tag2"] = nil
	expNames["/users/user/2020/12"] = nil
	expNames["/users/user/2020"] = nil
	expNames["/users/user"] = nil
	expNames["/users"] = nil
	expNames["/longids"] = nil
	expNames["/2020/12/31"] = nil
	expNames["/2020/12"] = nil
	expNames["/2020"] = nil

	// sn1
	expNames["/snapshots/2021/01"] = nil
	expNames["/snapshots/2021"] = nil
	expNames["/hosts/host/2021/01"] = nil
	expNames["/hosts/host/2021"] = nil
	expNames["/tags/tag1/2021/01"] = nil
	expNames["/tags/tag1/2021"] = nil
	expNames["/tags/tag2/2021/01"] = nil
	expNames["/tags/tag2/2021"] = nil
	expNames["/users/user/2021/01"] = nil
	expNames["/users/user/2021"] = nil
	expNames["/2021/01/01"] = nil
	expNames["/2021/01"] = nil
	expNames["/2021"] = nil

	// sn2
	expNames["/hosts/host2/2021/01"] = nil
	expNames["/hosts/host2/2021"] = nil
	expNames["/hosts/host2"] = nil
	expNames["/tags/tag3/2021/01"] = nil
	expNames["/tags/tag3/2021"] = nil
	expNames["/tags/tag3"] = nil
	expNames["/tags/tag4/2021/01"] = nil
	expNames["/tags/tag4/2021"] = nil
	expNames["/tags/tag4"] = nil
	expNames["/users/user2/2021/01"] = nil
	expNames["/users/user2/2021"] = nil
	expNames["/users/user2"] = nil

	// target snapshots for links
	expNames["/snapshots/latest"] = sn3 // sn1 - sn3 have same time string
	expNames["/hosts/host/latest"] = sn3
	expNames["/hosts/host2/latest"] = sn2
	expNames["/tags/tag1/latest"] = sn1
	expNames["/tags/tag2/latest"] = sn2 // sn1 and sn2 have same time string
	expNames["/tags/tag3/latest"] = sn2
	expNames["/tags/tag4/latest"] = sn2
	expNames["/users/user/latest"] = sn1
	expNames["/users/user2/latest"] = sn3 // sn2 and sn3 have same time string

	// latest links
	expLatest["/snapshots/latest"] = "2021/01/01-2" // sn1 - sn3 have same time string
	expLatest["/hosts/host/latest"] = "2021/01/01-1"
	expLatest["/hosts/host2/latest"] = "2021/01/01"
	expLatest["/tags/tag1/latest"] = "2021/01/01"
	expLatest["/tags/tag2/latest"] = "2021/01/01-1" // sn1 and sn2 have same time string
	expLatest["/tags/tag3/latest"] = "2021/01/01"
	expLatest["/tags/tag4/latest"] = "2021/01/01"
	expLatest["/users/user/latest"] = "2021/01/01"
	expLatest["/users/user2/latest"] = "2021/01/01-1" // sn2 and sn3 have same time string

	verifyEntries(t, expNames, expLatest, sds.entries)
}

func verifyEntries(t *testing.T, expNames map[string]*data.Snapshot, expLatest map[string]string, entries map[string]*MetaDirData) {
	actNames := make(map[string]*data.Snapshot)
	actLatest := make(map[string]string)
	for path, entry := range entries {
		actNames[path] = entry.snapshot
		if entry.linkTarget != "" {
			actLatest[path] = entry.linkTarget
		}
	}

	test.Equals(t, expNames, actNames)
	test.Equals(t, expLatest, actLatest)

	// verify tree integrity
	for path, entry := range entries {
		// check that all children are actually contained in entry.names
		for otherPath := range entries {
			if strings.HasPrefix(otherPath, path+"/") {
				sub := otherPath[len(path)+1:]
				// remaining path does not contain a directory
				test.Assert(t, strings.Contains(sub, "/") || (entry.names != nil && entry.names[sub] != nil), "missing entry %v in %v", sub, path)
			}
		}
		if entry.names == nil {
			continue
		}
		// child entries reference the correct MetaDirData
		for elem, subentry := range entry.names {
			test.Equals(t, entries[path+"/"+elem], subentry)
		}
	}
}

func TestMakeEmptyDirs(t *testing.T) {
	pathTemplates := []string{"ids/%i", "snapshots/%T", "hosts/%h/%T",
		"tags/%t/%T", "users/%u/%T", "longids/id-%I", "%T/%h", "%T/%i", "id-%i",
	}
	timeTemplate := "2006/01/02"

	sds := &SnapshotsDirStructure{
		pathTemplates: pathTemplates,
		timeTemplate:  timeTemplate,
	}
	sds.makeDirs(data.Snapshots{})

	expNames := make(map[string]*data.Snapshot)
	expLatest := make(map[string]string)

	// empty entries for dir structure
	expNames["/ids"] = nil
	expNames["/snapshots"] = nil
	expNames["/hosts"] = nil
	expNames["/tags"] = nil
	expNames["/users"] = nil
	expNames["/longids"] = nil
	expNames[""] = nil

	verifyEntries(t, expNames, expLatest, sds.entries)
}

func TestFilenameFromTag(t *testing.T) {
	for _, c := range []struct {
		tag, filename string
	}{
		{"", "_"},
		{".", "_"},
		{"..", "__"},
		{"%.", "%."},
		{"foo", "foo"},
		{"foo ", "foo "},
		{"foo/bar_baz", "foo_bar_baz"},
	} {
		test.Equals(t, c.filename, filenameFromTag(c.tag))
	}
}
