package metadata

import (
	"bytes"
	"context"
	"path/filepath"
	"testing"

	"github.com/boltdb/bolt"
	"github.com/containerd/containerd/content"
	"github.com/containerd/containerd/content/local"
	"github.com/containerd/containerd/content/testsuite"
	"github.com/containerd/containerd/errdefs"
	"github.com/containerd/containerd/leases"
	"github.com/containerd/containerd/namespaces"
	digest "github.com/opencontainers/go-digest"
	"github.com/pkg/errors"
)

func createContentStore(ctx context.Context, root string) (content.Store, func() error, error) {
	// TODO: Use mocked or in-memory store
	cs, err := local.NewStore(root)
	if err != nil {
		return nil, nil, err
	}

	db, err := bolt.Open(filepath.Join(root, "metadata.db"), 0660, nil)
	if err != nil {
		return nil, nil, err
	}

	return NewDB(db, cs, nil).ContentStore(), func() error {
		return db.Close()
	}, nil
}

func TestContent(t *testing.T) {
	testsuite.ContentSuite(t, "metadata", createContentStore)
}

func TestContentLeased(t *testing.T) {
	ctx, db, cancel := testDB(t)
	defer cancel()

	cs := db.ContentStore()

	blob := []byte("any content")
	expected := digest.FromBytes(blob)

	lctx, _, err := createLease(ctx, db, "lease-1")
	if err != nil {
		t.Fatal(err)
	}
	if err := content.WriteBlob(lctx, cs, "test-1", bytes.NewReader(blob), int64(len(blob)), expected); err != nil {
		t.Fatal(err)
	}
	if err := checkContentLeased(lctx, db, expected); err != nil {
		t.Fatal("lease checked failed:", err)
	}

	lctx, _, err = createLease(ctx, db, "lease-2")
	if err != nil {
		t.Fatal(err)
	}

	if _, err := cs.Writer(lctx, "test-2", int64(len(blob)), expected); err == nil {
		t.Fatal("expected already exist error")
	} else if !errdefs.IsAlreadyExists(err) {
		t.Fatal(err)
	}
	if err := checkContentLeased(lctx, db, expected); err != nil {
		t.Fatal("lease checked failed:", err)
	}
}

func createLease(ctx context.Context, db *DB, name string) (context.Context, func() error, error) {
	if err := db.Update(func(tx *bolt.Tx) error {
		_, err := NewLeaseManager(tx).Create(ctx, name, nil)
		return err
	}); err != nil {
		return nil, nil, err
	}
	return leases.WithLease(ctx, name), func() error {
		return db.Update(func(tx *bolt.Tx) error {
			return NewLeaseManager(tx).Delete(ctx, name)
		})
	}, nil
}

func checkContentLeased(ctx context.Context, db *DB, dgst digest.Digest) error {
	ns, ok := namespaces.Namespace(ctx)
	if !ok {
		return errors.New("no namespace in context")
	}
	lease, ok := leases.Lease(ctx)
	if !ok {
		return errors.New("no lease in context")
	}

	return db.View(func(tx *bolt.Tx) error {
		bkt := getBucket(tx, bucketKeyVersion, []byte(ns), bucketKeyObjectLeases, []byte(lease), bucketKeyObjectContent)
		if bkt == nil {
			return errors.Wrapf(errdefs.ErrNotFound, "bucket not found", lease)
		}
		v := bkt.Get([]byte(dgst.String()))
		if v == nil {
			return errors.Wrap(errdefs.ErrNotFound, "object not leased")
		}

		return nil
	})
}
