package cache_test

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/kopia/kopia/internal/blobtesting"
	"github.com/kopia/kopia/internal/cache"
	"github.com/kopia/kopia/internal/gather"
	"github.com/kopia/kopia/internal/testlogging"
	"github.com/kopia/kopia/internal/testutil"
	"github.com/kopia/kopia/repo/blob"
)

func TestContentCacheForData(t *testing.T) {
	ctx := testlogging.Context(t)

	underlyingData := blobtesting.DataMap{}
	underlying := blobtesting.NewMapStorage(underlyingData, nil, nil)

	cacheData := blobtesting.DataMap{}
	cacheStorage := testutil.EnsureType[cache.Storage](t, blobtesting.NewMapStorage(cacheData, nil, nil))

	dataCache, err := cache.NewContentCache(ctx, underlying, cache.Options{
		Storage:    cacheStorage,
		HMACSecret: []byte{1, 2, 3, 4},
		Sweep: cache.SweepSettings{
			MaxSizeBytes: 150,
		},
	}, nil)
	require.NoError(t, err)

	var tmp gather.WriteBuffer
	defer tmp.Close()

	// get something we don't have in the underlying storage
	require.ErrorIs(t, dataCache.GetContent(ctx, "key1", "blob1", 0, 3, &tmp), blob.ErrBlobNotFound)

	require.NoError(t, underlying.PutBlob(ctx, "blob1", gather.FromSlice([]byte{1, 2, 3, 4, 5, 6}), blob.PutOptions{}))

	require.NoError(t, dataCache.GetContent(ctx, "xkey1", "blob1", 0, 3, &tmp))
	require.Equal(t, []byte{1, 2, 3}, tmp.ToByteSlice())

	require.NoError(t, dataCache.GetContent(ctx, "xkey2", "blob1", 3, 3, &tmp))
	require.Equal(t, []byte{4, 5, 6}, tmp.ToByteSlice())

	// cache has 2 entries, one for each section of the blob.
	require.Len(t, cacheData, 2)

	// keys are mangled so that last character (which is always 0..9 a..f) is the first.
	require.Contains(t, cacheData, blob.ID("key1x"))
	require.Contains(t, cacheData, blob.ID("key2x"))

	dataCache.Close(ctx)

	// get slice with cache miss
	require.NoError(t, underlying.PutBlob(ctx, "blob2", gather.FromSlice([]byte{1, 2, 3, 4, 5, 6}), blob.PutOptions{}))
	require.NoError(t, dataCache.GetContent(ctx, "aaa1", "blob2", 3, 3, &tmp))
	require.Equal(t, []byte{4, 5, 6}, tmp.ToByteSlice())

	// even-length content IDs are not mangled
	require.Len(t, cacheData, 3)
	require.Contains(t, cacheData, blob.ID("aaa1"))

	require.NoError(t, underlying.PutBlob(ctx, "pblob3", gather.FromSlice([]byte{1, 2, 3, 4, 5, 6}), blob.PutOptions{}))
	dataCache.PrefetchBlob(ctx, "pblob3")
	require.NoError(t, underlying.DeleteBlob(ctx, "pblob3"))

	// make sure blob ID is properly mangled
	require.Contains(t, cacheData, blob.ID("blob3p"))
	require.NoError(t, dataCache.GetContent(ctx, "aaa3", "pblob3", 2, 4, &tmp))
	require.Equal(t, []byte{3, 4, 5, 6}, tmp.ToByteSlice())
}

func TestContentCacheForData_Passthrough(t *testing.T) {
	underlyingData := blobtesting.DataMap{}
	underlying := blobtesting.NewMapStorage(underlyingData, nil, nil)

	ctx := testlogging.Context(t)

	dataCache, err := cache.NewContentCache(ctx, underlying, cache.Options{}, nil)

	require.NoError(t, err)
	require.NoError(t, underlying.PutBlob(ctx, "blob1", gather.FromSlice([]byte{1, 2, 3, 4, 5, 6}), blob.PutOptions{}))

	var tmp gather.WriteBuffer
	defer tmp.Close()

	require.NoError(t, dataCache.GetContent(ctx, "key1", "blob1", 0, 5, &tmp))
	require.Equal(t, []byte{1, 2, 3, 4, 5}, tmp.ToByteSlice())
}
