package backend_test

import (
	"bytes"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"sort"
	"testing"

	"github.com/restic/restic/backend"
)

var testCleanup = flag.Bool("test.cleanup", true, "clean up after running tests (remove local backend directory with all content)")

var TestStrings = []struct {
	id   string
	data string
}{
	{"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", "foobar"},
	{"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
	{"cc5d46bdb4991c6eae3eb739c9c8a7a46fe9654fab79c47b4fe48383b5b25e1c", "foo/bar"},
	{"4e54d2c721cbdb730f01b10b62dec622962b36966ec685880effa63d71c808f2", "foo/../../baz"},
}

func setupLocalBackend(t *testing.T) *backend.Local {
	tempdir, err := ioutil.TempDir("", "restic-test-")
	ok(t, err)

	b, err := backend.CreateLocal(tempdir)
	ok(t, err)

	t.Logf("created local backend at %s", tempdir)

	return b
}

func teardownLocalBackend(t *testing.T, b *backend.Local) {
	if !*testCleanup {
		t.Logf("leaving local backend at %s\n", b.Location())
		return
	}

	ok(t, b.Delete())
}

func testBackend(b backend.Backend, t *testing.T) {
	for _, tpe := range []backend.Type{backend.Data, backend.Key, backend.Lock, backend.Snapshot, backend.Tree} {
		// detect non-existing files
		for _, test := range TestStrings {
			id, err := backend.ParseID(test.id)
			ok(t, err)

			// test if blob is already in repository
			ret, err := b.Test(tpe, id)
			ok(t, err)
			assert(t, !ret, "blob was found to exist before creating")

			// try to open not existing blob
			d, err := b.Get(tpe, id)
			assert(t, err != nil && d == nil, "blob data could be extracted befor creation")

			// try to get string out, should fail
			ret, err = b.Test(tpe, id)
			ok(t, err)
			assert(t, !ret, fmt.Sprintf("id %q was found (but should not have)", test.id))
		}

		// add files
		for _, test := range TestStrings {
			// store string in backend
			blob, err := b.Create(tpe)
			ok(t, err)

			_, err = blob.Write([]byte(test.data))
			ok(t, err)
			ok(t, blob.Close())

			id, err := blob.ID()
			ok(t, err)

			equals(t, test.id, id.String())

			// try to get it out again
			buf, err := b.Get(tpe, id)
			ok(t, err)
			assert(t, buf != nil, "Get() returned nil")

			// compare content
			equals(t, test.data, string(buf))

			// compare content again via stream function
			rd, err := b.GetReader(tpe, id)
			ok(t, err)
			buf, err = ioutil.ReadAll(rd)
			ok(t, err)
			equals(t, test.data, string(buf))
		}

		// test adding the first file again
		test := TestStrings[0]
		id, err := backend.ParseID(test.id)
		ok(t, err)

		// create blob
		blob, err := b.Create(tpe)
		ok(t, err)

		_, err = io.Copy(blob, bytes.NewReader([]byte(test.data)))
		ok(t, err)
		err = blob.Close()
		assert(t, err == backend.ErrAlreadyPresent,
			"wrong error returned: expected %v, got %v",
			backend.ErrAlreadyPresent, err)

		id2, err := blob.ID()
		ok(t, err)

		assert(t, id.Equal(id2), "IDs do not match: expected %v, got %v", id, id2)

		// remove and recreate
		err = b.Remove(tpe, id)
		ok(t, err)

		// create blob
		blob, err = b.Create(tpe)
		ok(t, err)

		_, err = io.Copy(blob, bytes.NewReader([]byte(test.data)))
		ok(t, err)
		err = blob.Close()
		ok(t, err)

		id2, err = blob.ID()
		ok(t, err)
		assert(t, id.Equal(id2), "IDs do not match: expected %v, got %v", id, id2)

		// list items
		IDs := backend.IDs{}

		for _, test := range TestStrings {
			id, err := backend.ParseID(test.id)
			ok(t, err)
			IDs = append(IDs, id)
		}

		ids, err := b.List(tpe)
		ok(t, err)

		sort.Sort(ids)
		sort.Sort(IDs)
		equals(t, IDs, ids)

		// remove content if requested
		if *testCleanup {
			for _, test := range TestStrings {
				id, err := backend.ParseID(test.id)
				ok(t, err)

				found, err := b.Test(tpe, id)
				ok(t, err)
				assert(t, found, fmt.Sprintf("id %q was not found before removal", id))

				ok(t, b.Remove(tpe, id))

				found, err = b.Test(tpe, id)
				ok(t, err)
				assert(t, !found, fmt.Sprintf("id %q not found after removal", id))
			}
		}

	}
}

func TestBackend(t *testing.T) {
	// test for non-existing backend
	b, err := backend.OpenLocal("/invalid-restic-test")
	assert(t, err != nil, "opening invalid repository at /invalid-restic-test should have failed, but err is nil")
	assert(t, b == nil, fmt.Sprintf("opening invalid repository at /invalid-restic-test should have failed, but b is not nil: %v", b))

	s := setupLocalBackend(t)
	defer teardownLocalBackend(t, s)

	testBackend(s, t)
}

func TestLocalBackendCreationFailures(t *testing.T) {
	b := setupLocalBackend(t)
	defer teardownLocalBackend(t, b)

	// test failure to create a new repository at the same location
	b2, err := backend.CreateLocal(b.Location())
	assert(t, err != nil && b2 == nil, fmt.Sprintf("creating a repository at %s for the second time should have failed", b.Location()))

	// test failure to create a new repository at the same location without a config file
	b2, err = backend.CreateLocal(b.Location())
	assert(t, err != nil && b2 == nil, fmt.Sprintf("creating a repository at %s for the second time should have failed", b.Location()))
}