package leveldb

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

	"github.com/kellegous/go/internal"
)

func TestGetPut(t *testing.T) {
	tmp, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmp)

	backend, err := New(filepath.Join(tmp, "data"))
	if err != nil {
		t.Fatal(err)
	}
	defer backend.Close()

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	if _, err := backend.Get(ctx, "not_found"); err != internal.ErrRouteNotFound {
		t.Fatalf("expected ErrRouteNotFound, got \"%v\"", err)
	}

	a := &internal.Route{
		URL:  "http://www.kellegous.com/",
		Time: time.Now(),
	}

	if err := backend.Put(ctx, "key", a); err != nil {
		t.Fatal(err)
	}

	b, err := backend.Get(ctx, "key")
	if err != nil {
		t.Fatal(err)
	}

	if b.URL != a.URL {
		t.Fatalf("expected URL of %s, got %s", a.URL, b.URL)
	}

	if !b.Time.Equal(a.Time) {
		t.Fatalf("expected Time of %s, got %s", a.Time, b.Time)
	}
}

func TestNextID(t *testing.T) {
	tmp, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmp)

	backend, err := New(filepath.Join(tmp, "data"))
	if err != nil {
		t.Fatal(err)
	}
	defer backend.Close()

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	var e uint64 = 1
	for i := 0; i < 501; i++ {
		r, err := backend.NextID(ctx)
		if err != nil {
			t.Fatal(err)
		}

		if r != e {
			t.Fatalf("expected %d, got %d", e, r)
		}

		e++
	}
}

func TestEmptyList(t *testing.T) {
	tmp, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmp)

	backend, err := New(filepath.Join(tmp, "data"))
	if err != nil {
		t.Fatal(err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	it, err := backend.List(ctx, "")
	defer it.Release()

	if it.Valid() {
		t.Fatal("Expected iterator to be invalid at start")
	}

	if it.Next() {
		t.Fatal("Expected there to be no next")
	}

	if err := it.Error(); err != nil {
		t.Fatal(err)
	}
}

func putRoutes(ctx context.Context, backend *Backend, names ...string) error {
	for _, name := range names {
		if err := backend.Put(ctx, name, &internal.Route{
			URL:  fmt.Sprintf("http://%s/", name),
			Time: time.Unix(0, 420),
		}); err != nil {
			return err
		}
	}
	return nil
}

func mustBeIterOf(t *testing.T, iter internal.RouteIterator, names ...string) {
	defer iter.Release()

	if iter.Valid() {
		t.Fatal("expected Iter to be invalid at start")
	}

	if err := iter.Error(); err != nil {
		t.Fatal("expected Iter not to begin with error")
	}

	if iter.Name() != "" {
		t.Fatalf("expected empty name but got \"%s\"", iter.Name())
	}

	if iter.Route() != nil {
		t.Fatalf("expected empty route but got %v", iter.Route())
	}

	for i, name := range names {
		if !iter.Next() {
			t.Fatalf("at item %d, expected more items", i)
		}

		if !iter.Valid() {
			t.Fatalf("on item %d, expected a valid iterator", i)
		}

		if iter.Name() != name {
			t.Fatalf("expected name of %s, got %s", name, iter.Name())
		}

		if iter.Route().URL != fmt.Sprintf("http://%s/", name) {
			t.Fatalf("expected route to have URL of http://%s/ got %s", name, iter.Route().URL)
		}
	}

	if iter.Next() {
		t.Fatal("iterator has too many items")
	}

	if iter.Valid() {
		t.Fatal("iterator should not be valid at end")
	}
}

func TestList(t *testing.T) {
	tmp, err := ioutil.TempDir("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmp)

	backend, err := New(filepath.Join(tmp, "data"))
	if err != nil {
		t.Fatal(err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
	defer cancel()

	if err := putRoutes(ctx, backend, "a", "c", "d"); err != nil {
		t.Fatal(err)
	}

	iter, err := backend.List(ctx, "")
	if err != nil {
		t.Fatal(err)
	}
	mustBeIterOf(t, iter, "a", "c", "d")

	iter, err = backend.List(ctx, "b")
	if err != nil {
		t.Fatal(err)
	}
	mustBeIterOf(t, iter, "c", "d")

	iter, err = backend.List(ctx, "z")
	if err != nil {
		t.Fatal(err)
	}
	mustBeIterOf(t, iter)
}
