package notmuch

// Copyright © 2015 The go.notmuch Authors. Authors can be found in the AUTHORS file.
// Licensed under the GPLv3 or later.
// See COPYING at the root of the repository for details.

import (
	"fmt"
	"io"
	"os"
	"path"
	"path/filepath"
	"strings"
	"testing"
)

var dbPath string

func init() {
	var err error
	dbPath, err = filepath.Abs("fixtures/database-v1")
	if err != nil {
		panic(err)
	}
}

func TestOpenNotFound(t *testing.T) {
	_, err := Open("/not-found", DBReadOnly)
	if err == nil {
		t.Errorf("Open(%q): expected error got nil", "/not-found")
	}
}

func TestCreate(t *testing.T) {
	tmp := os.TempDir()
	tmpDbPath := path.Join(tmp, ".notmuch")
	defer func() {
		os.RemoveAll(tmpDbPath)
	}()

	db, err := Create(tmp)
	if err != nil {
		t.Fatal(err)
	}
	if want, got := 1, db.Version(); got < want {
		t.Errorf("db.Version(): want at least %d got %d", want, got)
	}
}

func TestOpen(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if want, got := 1, db.Version(); got < want {
		t.Errorf("db.Version(): want at least %d got %d", want, got)
	}
}

func TestLastStatus(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if want, got := "", db.LastStatus(); want != got {
		t.Errorf("db.LastStatus(): want %s got %s", want, got)
	}
	// TODO(kalbasit): use add_message later to cause an error and add a test for it
}

func TestPath(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if want, got := "fixtures/database-v1", db.Path(); !strings.HasSuffix(got, want) {
		t.Errorf("db.Path(): want %s got %s", want, got)
	}
}

func TestNeedsUpgrade(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if want, got := false, db.NeedsUpgrade(); want != got {
		t.Errorf("db.NeedsUpgrade(): want %t got %t", want, got)
	}
}

func TestUpgrade(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if want, got := ErrReadOnlyDB, db.Upgrade(); want != got {
		t.Errorf("db.Upgrade(): want error %q got %q", want, got)
	}

	db, err = Open(dbPath, DBReadWrite)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if want, got := error(nil), db.Upgrade(); want != got {
		t.Errorf("db.Upgrade(): want error %q got %q", want, got)
	}
}

func TestAddMessage(t *testing.T) {
	fp, err := filepath.Abs("fixtures/emails/notmuch0202.2,")
	if err != nil {
		t.Fatalf("error getting the absolute path: %s", err)
	}
	nfp, err := filepath.Abs("fixtures/database-v1/new/notmuch0202.2,")
	if err != nil {
		t.Fatalf("error getting the absolute path: %s", err)
	}

	f, err := os.Open(fp)
	if err != nil {
		t.Fatalf("error opening the new email: %s", err)
	}
	defer f.Close()
	nf, err := os.Create(nfp)
	if err != nil {
		t.Fatalf("error creating the new email: %s", err)
	}
	defer nf.Close()
	if _, err := io.Copy(nf, f); err != nil {
		t.Fatalf("error copying the email: %s", err)
	}
	defer os.Remove(nfp)

	db, err := Open(dbPath, DBReadWrite)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	msg, err := db.AddMessage(nfp)
	if err != nil {
		t.Fatalf("AddMessage(%q): got error: %s", nfp, err)
	}
	defer db.RemoveMessage(nfp)
	if msg == nil {
		t.Errorf("expecting msg to not be nil")
	}
}

func testFindMessage(t *testing.T, db *DB, id string) {
	msg, err := db.FindMessage(id)
	if err != nil {
		t.Fatalf("db.FindMessage(%q): unexpected error: %s", id, err)
	}
	if want, got := id, msg.ID(); want != got {
		t.Errorf("db.FindMessage(%q).ID(): want %s got %s", id, want, got)
	}
}

func TestFindMessage(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if _, err := db.FindMessage("notfound"); err != ErrNotFound {
		t.Errorf("db.FindMessage(\"notfound\"): expecting ErrNotFound got %s", err)
	}
	id := "87iqd9rn3l.fsf@vertex.dottedmag"
	testFindMessage(t, db, id)
}

func TestCompact(t *testing.T) {
	backup := fmt.Sprintf("%s.backup", dbPath)
	defer os.RemoveAll(backup)
	if err := Compact(dbPath, backup); err != nil {
		t.Fatalf("error compacting %q: %s", dbPath, err)
	}

	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	id := "87iqd9rn3l.fsf@vertex.dottedmag"
	testFindMessage(t, db, id)
}

func TestFindMessageByFilename(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if _, err := db.FindMessageByFilename("notfound"); err != ErrNotFound {
		t.Errorf("db.FindMessageByFilename(\"notfound\"): expecting ErrNotFound got %s", err)
	}
	id := "87iqd9rn3l.fsf@vertex.dottedmag"
	p := path.Join(dbPath, "new", "04:2,")
	msg, err := db.FindMessageByFilename(p)
	if err != nil {
		t.Fatalf("db.FindMessageByFilename(%q): unexpected error: %s", p, err)
	}
	if want, got := id, msg.ID(); want != got {
		t.Errorf("db.FindMessageByFilename(%q).ID(): want %s got %s", p, want, got)
	}
}

func TestTags(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if _, err := db.Tags(); err != nil {
		t.Fatalf("db.Tags(): got error %s", err)
	}
	// TODO(kalbasit): extend the test when tags are fully implemented.
}

func TestGetConfigList(t *testing.T) {
	db, err := Open(dbPath, DBReadOnly)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if _, err := db.GetConfigList(""); err != nil {
		t.Errorf("db.GetConfigList(\"\"): unexpected error %s", err)
	}
}

func TestSetConfig(t *testing.T) {
	db, err := Open(dbPath, DBReadWrite)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	cfgKey := "search.exclude_tags"
	cfgVal := "spam"
	if err := db.SetConfig(cfgKey, cfgVal); err != nil {
		t.Errorf("db.SetConfig(%q, %q): unexpected error %s", cfgKey, cfgVal, err)
	}
}

func TestGetConfig(t *testing.T) {
	db, err := Open(dbPath, DBReadWrite)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	if _, err := db.GetConfig("blah"); err != nil {
		t.Errorf("db.GetConfig(\"blah\"): unexpected error %s", err)
	}
}

func TestConfigRoundtrip(t *testing.T) {
	db, err := Open(dbPath, DBReadWrite)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	cfgKey := "search.exclude_tags"
	cfgVal := "spam"
	if err := db.SetConfig(cfgKey, cfgVal); err != nil {
		t.Errorf("db.SetConfig(%q, %q): unexpected error %s", cfgKey, cfgVal, err)
	}
	value, err := db.GetConfig(cfgKey)
	if err != nil {
		t.Errorf("db.GetConfig(%q): unexpected error %s", cfgKey, err)
	}
	if value != cfgVal {
		t.Errorf("db.GetConfig(%q): want: %q, got %q", cfgKey, cfgVal, value)
	}
}

func TestConfigListNext(t *testing.T) {
	db, err := Open(dbPath, DBReadWrite)
	if err != nil {
		t.Fatalf("Open(%q): unexpected error: %s", dbPath, err)
	}
	defer db.Close()
	cfgKey := "search.exclude_tags"
	cfgVal := "spam"
	if err := db.SetConfig(cfgKey, cfgVal); err != nil {
		t.Errorf("db.SetConfig(%q, %q): unexpected error %s", cfgKey, cfgVal, err)
	}
	cfgList, err := db.GetConfigList("")
	if err != nil {
		t.Fatalf("db.GetConfigList(%q): unexpected error: %s", "", err)
	}
	var resKey string
	var resVal string
	for cfgList.Next(&resKey, &resVal) {
		break
	}
	if resKey != cfgKey {
		t.Errorf("config key: expected %q, got %q", cfgKey, resKey)
	}
	if resVal != cfgVal {
		t.Errorf("config value: expected %q, got %q", cfgVal, resVal)
	}
	if cfgList.Next(&resKey, &resVal) {
		t.Errorf("iteration did not stop after the end of the options")
	}
}
