package disk

import (
	"fmt"
	"os"
	"testing"

	"github.com/google/go-containerregistry/pkg/name"
	"github.com/stretchr/testify/assert"

	"edge-infra.dev/pkg/f8n/warehouse/lift"
	"edge-infra.dev/pkg/f8n/warehouse/oci"
	"edge-infra.dev/pkg/f8n/warehouse/oci/layout"
	"edge-infra.dev/pkg/lib/build/bazel"
	"edge-infra.dev/pkg/lib/uuid"
	"edge-infra.dev/test/fixtures"
)

var (
	fixturePath *fixtures.Path
	shoot       oci.Artifact
	redpanda    oci.Artifact
)

func TestMain(m *testing.M) {
	var err error

	fixturePath, err = fixtures.Layout()
	if err != nil {
		panic(err)
	}
	shoot, err = fixturePath.Get(name.MustParseReference("shoot:latest"))
	if err != nil {
		panic(err)
	}
	redpanda, err = fixturePath.Get(name.MustParseReference("redpanda-system:latest"))
	if err != nil {
		panic(err)
	}

	os.Exit(m.Run())
}

func TestNew(t *testing.T) {
	assert.NotEmpty(t, New(testLayout(t, "new-cache")))
}

func TestCache(t *testing.T) {
	t.Parallel()
	var (
		diskCache      = New(testLayout(t, "add"))
		shootTag       = testTag(t, "shoot", uuid.New().Hash())
		shootDigest    = testDigest(t, "shoot", shoot)
		redpandaDigest = testDigest(t, "redpanda", redpanda)
	)

	assert.NotEmpty(t, diskCache)

	t.Run("Add", func(t *testing.T) {
		assert.NoError(t, diskCache.Add(shootTag, shoot))
		assert.NoError(t, diskCache.Add(redpandaDigest, redpanda))
	})

	t.Run("Get", func(t *testing.T) {
		a, err := diskCache.Get(shootTag)
		assert.NoError(t, err)
		sameDigest(t, shoot, a)

		a, err = diskCache.Get(shootDigest)
		assert.NoError(t, err)
		sameDigest(t, shoot, a)

		a, err = diskCache.Get(redpandaDigest)
		assert.NoError(t, err)
		sameDigest(t, redpanda, a)
	})

	t.Run("Exists", func(t *testing.T) {
		assert.True(t, diskCache.Exists(shootTag))
		assert.True(t, diskCache.Exists(redpandaDigest))
		assert.False(t, diskCache.Exists(name.MustParseReference("ubuntu:latest")))
	})
}

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

	diskCache := New(testLayout(t, "purge"))
	assert.NotEmpty(t, diskCache)
	assert.NoError(t, diskCache.Add(testTag(t, "shoot", uuid.New().Hash()), shoot))
	assert.NoError(t, diskCache.Add(testTag(t, "redpanda", uuid.New().Hash()), redpanda))

	assert.NoError(t, diskCache.Purge())

	assert.NoError(t, diskCache.Add(testTag(t, "shoot", uuid.New().Hash()), shoot))
	assert.NoError(t, diskCache.Add(testTag(t, "redpanda", uuid.New().Hash()), redpanda))
}

func testLayout(t *testing.T, id string) *layout.Path {
	t.Helper()
	dir, err := bazel.NewTestTmpDir(fmt.Sprintf("edge-infra-disk-cache-%s-", id))
	assert.NoError(t, err)
	path, err := layout.New(dir)
	assert.NoError(t, err)
	return path
}

func testTag(t *testing.T, n, tag string) name.Reference {
	t.Helper()
	ref, err := name.NewTag(fmt.Sprintf("%s/%s:%s", lift.LocalRegistryAlias, n, tag))
	assert.NoError(t, err)
	return ref
}

func testDigest(t *testing.T, n string, a oci.Artifact) name.Reference {
	t.Helper()
	d, err := a.Digest()
	assert.NoError(t, err)
	ref, err := name.NewDigest(fmt.Sprintf("%s/%s@%s", lift.LocalRegistryAlias, n, d))
	assert.NoError(t, err)
	return ref
}

func sameDigest(t *testing.T, exp, actual oci.Artifact) {
	ad, err := actual.Digest()
	assert.NoError(t, err)

	ed, err := exp.Digest()
	assert.NoError(t, err)

	assert.Equal(t, ed, ad)
}