package barfdb

import (
	"context"
	"database/sql"
	"testing"

	"github.com/google/go-cmp/cmp"

	_ "github.com/mattn/go-sqlite3"
)

func makeDB(t *testing.T) *DB {
	t.Helper()
	sdb, err := sql.Open("sqlite3", ":memory:")
	if err != nil {
		t.Fatalf("creating inmemory sqlite3 database: %v", err)
	}
	t.Cleanup(func() { sdb.Close() })

	db := &DB{sdb}
	if err := db.Initialize(context.Background()); err != nil {
		t.Fatalf("initializing DB: %v", err)
	}

	return &DB{sdb}
}

func TestGetOrCreateKey(t *testing.T) {
	db := makeDB(t)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	ds := Dataset{
		Name: "Slartibartfast",
	}

	key, err := db.GetOrCreateKey(ctx, "testkey", &ds)
	if err != nil {
		t.Fatalf("GetOrCreateKey (first time): %v", err)
	}

	key2, err := db.GetOrCreateKey(ctx, "testkey", &ds)
	if err != nil {
		t.Fatalf("GetOrCreateKey (second time): %v", err)
	}

	if key != key2 {
		t.Errorf("GetOrCreateKey returned different results for 'testkey': %v != %v", key, key2)
	}

	gotds, err := db.GetResponseForKey(ctx, key)
	if err != nil {
		t.Fatalf("GetResponseForKey(%q): %v", key, err)
	}
	if diff := cmp.Diff(*gotds, ds); diff != "" {
		t.Errorf("GetResponseForKey diff (-got +want):\n%s", diff)
	}
}

func TestSaveResponseForKey(t *testing.T) {
	db := makeDB(t)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	key, err := db.GetOrCreateKey(ctx, "testkey", &Dataset{})
	if err != nil {
		t.Fatalf("GetOrCreateKey: %v", err)
	}

	ds := Dataset{
		Name: "Slartibartfast",
	}
	if err := db.SaveResponseForKey(ctx, key, &ds); err != nil {
		t.Fatalf("SaveResponseForKey: %v", err)
	}

	gotds, err := db.GetResponseForKey(ctx, key)
	if err != nil {
		t.Fatalf("GetResponseForKey: %v", err)
	}
	if diff := cmp.Diff(*gotds, ds); diff != "" {
		t.Errorf("GetResponseForKey diff (-got +want):\n%s", diff)
	}
}

func TestSaveResponseForKey_DoesNotExist(t *testing.T) {
	db := makeDB(t)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	ds := Dataset{
		Name: "Slartibartfast",
	}
	if err := db.SaveResponseForKey(ctx, "testkey/foo", &ds); err != ErrNoSuchKey {
		t.Fatalf("SaveResponseForKey: %v (want ErrNoSuchKey)", err)
	}
}

func TestGetResponseForKey_DoesNotExist(t *testing.T) {
	db := makeDB(t)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	if _, err := db.GetResponseForKey(ctx, "testkey/foo"); err != ErrNoSuchKey {
		t.Fatalf("GetResponseForKey: %v (want ErrNoSuchKey)", err)
	}
}

func TestBaseKey(t *testing.T) {
	for _, tc := range []struct {
		FullKey string
		Want    string
	}{{
		FullKey: "foo",
		Want:    "foo",
	}, {
		FullKey: "foo/bar",
		Want:    "foo",
	}, {
		FullKey: "foo/bar/baz",
		Want:    "foo",
	}} {
		e := Entry{FullKey: tc.FullKey}
		got := e.BaseKey()
		if got != tc.Want {
			t.Errorf("Entry{FullKey: %q}.BaseKey() = %q; want %q", tc.FullKey, got, tc.Want)
		}
	}
}

func TestList(t *testing.T) {
	db := makeDB(t)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	keys := map[string]string{}
	datasets := map[string]*Dataset{}
	mustGetOrCreateKey := func(key string, ds *Dataset) {
		fullKey, err := db.GetOrCreateKey(ctx, key, ds)
		if err != nil {
			t.Fatalf("GetOrCreateKey(ctx, %q, ds): %v", key, err)
		}
		keys[key] = fullKey
		datasets[key] = ds
	}
	mustGetOrCreateKey("2-foo", &Dataset{Name: "A"})
	mustGetOrCreateKey("1-fully-complete", &Dataset{
		Name: "B Fully Complete",
	})
	want := []Entry{{
		FullKey: keys["1-fully-complete"],
		Dataset: *datasets["1-fully-complete"],
	}, {
		FullKey: keys["2-foo"],
		Dataset: *datasets["2-foo"],
	}}

	es, err := db.List(ctx)
	if err != nil {
		t.Fatalf("List(ctx): %v", err)
	}
	if diff := cmp.Diff(es, want); diff != "" {
		t.Errorf("List(ctx): has diff (-want +got):\n%s", diff)
	}
}