package content

import (
	"bytes"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/kopia/kopia/internal/faketime"
	"github.com/kopia/kopia/internal/gather"
	"github.com/kopia/kopia/internal/testlogging"
	"github.com/kopia/kopia/internal/testutil"
	"github.com/kopia/kopia/repo/blob"
	"github.com/kopia/kopia/repo/content/index"
)

func TestCommittedContentIndexCache_Disk(t *testing.T) {
	t.Parallel()

	ta := faketime.NewClockTimeWithOffset(0)

	testCache(t, &diskCommittedContentIndexCache{
		testutil.TempDirectory(t),
		ta.NowFunc(),
		func() int { return 3 },
		nil, DefaultIndexCacheSweepAge,
	}, ta)
}

func TestCommittedContentIndexCache_Memory(t *testing.T) {
	t.Parallel()

	testCache(t, &memoryCommittedContentIndexCache{
		contents:             map[blob.ID]index.Index{},
		v1PerContentOverhead: func() int { return 3 },
	}, nil)
}

//nolint:thelper
func testCache(t *testing.T, cache committedContentIndexCache, fakeTime *faketime.ClockTimeWithOffset) {
	ctx := testlogging.Context(t)

	has, err := cache.hasIndexBlobID(ctx, "ndx1")
	require.NoError(t, err)

	if has {
		t.Fatal("hasIndexBlobID invalid response, expected false")
	}

	if _, err = cache.openIndex(ctx, "ndx1"); err == nil {
		t.Fatal("openIndex unexpectedly succeeded")
	}

	require.NoError(t, cache.addContentToCache(ctx, "ndx1", mustBuildIndex(t, index.Builder{
		mustParseID(t, "c1"): Info{PackBlobID: "p1234", ContentID: mustParseID(t, "c1")},
		mustParseID(t, "c2"): Info{PackBlobID: "p1234", ContentID: mustParseID(t, "c2")},
	})))

	has, err = cache.hasIndexBlobID(ctx, "ndx1")
	require.NoError(t, err)

	if !has {
		t.Fatal("hasIndexBlobID invalid response, expected true")
	}

	require.NoError(t, cache.addContentToCache(ctx, "ndx2", mustBuildIndex(t, index.Builder{
		mustParseID(t, "c3"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c3")},
		mustParseID(t, "c4"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c4")},
	})))

	require.NoError(t, cache.addContentToCache(ctx, "ndx2", mustBuildIndex(t, index.Builder{
		mustParseID(t, "c3"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c3")},
		mustParseID(t, "c4"): Info{PackBlobID: "p2345", ContentID: mustParseID(t, "c4")},
	})))

	ndx1, err := cache.openIndex(ctx, "ndx1")
	require.NoError(t, err)

	ndx2, err := cache.openIndex(ctx, "ndx2")
	require.NoError(t, err)

	var i Info

	ok, err := ndx1.GetInfo(mustParseID(t, "c1"), &i)
	require.True(t, ok)
	require.NoError(t, err)

	if got, want := i.PackBlobID, blob.ID("p1234"); got != want {
		t.Fatalf("unexpected pack blob ID: %v, want %v", got, want)
	}

	require.NoError(t, ndx1.Close())

	ok, err = ndx2.GetInfo(mustParseID(t, "c3"), &i)
	require.True(t, ok)
	require.NoError(t, err)

	if got, want := i.PackBlobID, blob.ID("p2345"); got != want {
		t.Fatalf("unexpected pack blob ID: %v, want %v", got, want)
	}

	// expire leaving only ndx2, this will actually not do anything for disk cache
	// because it relies on passage of time for safety, we'll re-do it in a sec.
	require.NoError(t, cache.expireUnused(ctx, []blob.ID{"ndx2"}))

	if fakeTime != nil {
		fakeTime.Advance(2 * time.Hour)
	}

	// expire leaving only ndx2
	require.NoError(t, cache.expireUnused(ctx, []blob.ID{"ndx2"}))

	has, err = cache.hasIndexBlobID(ctx, "ndx1")
	require.NoError(t, err)

	if has {
		t.Fatal("hasIndexBlobID invalid response, expected false")
	}

	if _, err = cache.openIndex(ctx, "ndx1"); err == nil {
		t.Fatal("openIndex unexpectedly succeeded")
	}
}

func mustBuildIndex(t *testing.T, b index.Builder) gather.Bytes {
	t.Helper()

	var buf bytes.Buffer
	if err := b.Build(&buf, index.Version2); err != nil {
		t.Fatal(err)
	}

	return gather.FromSlice(buf.Bytes())
}

func mustParseID(t *testing.T, s string) ID {
	t.Helper()

	id, err := index.ParseID(s)
	require.NoError(t, err)

	return id
}
