package fscache // import "github.com/docker/docker/builder/fscache"

import (
	"context"
	"io/ioutil"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/moby/buildkit/session/filesync"
	"gotest.tools/assert"
	is "gotest.tools/assert/cmp"
)

func TestFSCache(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "fscache")
	assert.Check(t, err)
	defer os.RemoveAll(tmpDir)

	backend := NewNaiveCacheBackend(filepath.Join(tmpDir, "backend"))

	opt := Opt{
		Root:     tmpDir,
		Backend:  backend,
		GCPolicy: GCPolicy{MaxSize: 15, MaxKeepDuration: time.Hour},
	}

	fscache, err := NewFSCache(opt)
	assert.Check(t, err)

	defer fscache.Close()

	err = fscache.RegisterTransport("test", &testTransport{})
	assert.Check(t, err)

	src1, err := fscache.SyncFrom(context.TODO(), &testIdentifier{"foo", "data", "bar"})
	assert.Check(t, err)

	dt, err := ioutil.ReadFile(filepath.Join(src1.Root().Path(), "foo"))
	assert.Check(t, err)
	assert.Check(t, is.Equal(string(dt), "data"))

	// same id doesn't recalculate anything
	src2, err := fscache.SyncFrom(context.TODO(), &testIdentifier{"foo", "data2", "bar"})
	assert.Check(t, err)
	assert.Check(t, is.Equal(src1.Root().Path(), src2.Root().Path()))

	dt, err = ioutil.ReadFile(filepath.Join(src1.Root().Path(), "foo"))
	assert.Check(t, err)
	assert.Check(t, is.Equal(string(dt), "data"))
	assert.Check(t, src2.Close())

	src3, err := fscache.SyncFrom(context.TODO(), &testIdentifier{"foo2", "data2", "bar"})
	assert.Check(t, err)
	assert.Check(t, src1.Root().Path() != src3.Root().Path())

	dt, err = ioutil.ReadFile(filepath.Join(src3.Root().Path(), "foo2"))
	assert.Check(t, err)
	assert.Check(t, is.Equal(string(dt), "data2"))

	s, err := fscache.DiskUsage(context.TODO())
	assert.Check(t, err)
	assert.Check(t, is.Equal(s, int64(0)))

	assert.Check(t, src3.Close())

	s, err = fscache.DiskUsage(context.TODO())
	assert.Check(t, err)
	assert.Check(t, is.Equal(s, int64(5)))

	// new upload with the same shared key shoutl overwrite
	src4, err := fscache.SyncFrom(context.TODO(), &testIdentifier{"foo3", "data3", "bar"})
	assert.Check(t, err)
	assert.Check(t, src1.Root().Path() != src3.Root().Path())

	dt, err = ioutil.ReadFile(filepath.Join(src3.Root().Path(), "foo3"))
	assert.Check(t, err)
	assert.Check(t, is.Equal(string(dt), "data3"))
	assert.Check(t, is.Equal(src4.Root().Path(), src3.Root().Path()))
	assert.Check(t, src4.Close())

	s, err = fscache.DiskUsage(context.TODO())
	assert.Check(t, err)
	assert.Check(t, is.Equal(s, int64(10)))

	// this one goes over the GC limit
	src5, err := fscache.SyncFrom(context.TODO(), &testIdentifier{"foo4", "datadata", "baz"})
	assert.Check(t, err)
	assert.Check(t, src5.Close())

	// GC happens async
	time.Sleep(100 * time.Millisecond)

	// only last insertion after GC
	s, err = fscache.DiskUsage(context.TODO())
	assert.Check(t, err)
	assert.Check(t, is.Equal(s, int64(8)))

	// prune deletes everything
	released, err := fscache.Prune(context.TODO())
	assert.Check(t, err)
	assert.Check(t, is.Equal(released, uint64(8)))

	s, err = fscache.DiskUsage(context.TODO())
	assert.Check(t, err)
	assert.Check(t, is.Equal(s, int64(0)))
}

type testTransport struct {
}

func (t *testTransport) Copy(ctx context.Context, id RemoteIdentifier, dest string, cs filesync.CacheUpdater) error {
	testid := id.(*testIdentifier)
	return ioutil.WriteFile(filepath.Join(dest, testid.filename), []byte(testid.data), 0600)
}

type testIdentifier struct {
	filename  string
	data      string
	sharedKey string
}

func (t *testIdentifier) Key() string {
	return t.filename
}
func (t *testIdentifier) SharedKey() string {
	return t.sharedKey
}
func (t *testIdentifier) Transport() string {
	return "test"
}