//
//  tank
//
//  Import and Playlist Daemon for autoradio project
//
//
//  Copyright (C) 2017-2018 Christian Pointner <equinox@helsinki.at>
//
//  This file is part of tank.
//
//  tank is free software: you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation, either version 3 of the License, or
//  any later version.
//
//  tank is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with tank. If not, see <http://www.gnu.org/licenses/>.
//

package store

import (
	"fmt"
	"os"
	"os/user"
	"testing"

	"github.com/jinzhu/gorm"
)

var (
	testBasePath     = "run/aura-tank_testing"
	testDBType       = "mysql"
	testDBConnection = "tank:aura@tcp(127.0.0.1:3306)/tank?charset=utf8&parseTime=True&loc=Local"
	// testDBType       = "postgres"
	// testDBConnection = "host=127.0.0.1 port=5432 user=tank dbname=tank password=aura sslmode=disable"
	testGroup1 = "test1"
	testGroup2 = "test2"
	testUser1  = "user1"
	testUser2  = "user2"
)

func stringInSlice(slice []string, search string) bool {
	for _, element := range slice {
		if element == search {
			return true
		}
	}
	return false
}

func TestMain(m *testing.M) {
	u, err := user.Current()
	if err != nil {
		os.Exit(-1)
	}
	testBasePath = fmt.Sprintf("/run/user/%s/aura-tank_testing", u.Uid)
	//	testDBConnection = filepath.Join(testBasePath, ".db.sqlite")
	os.Exit(m.Run())
}

//
// Testing
//

func TestOpen(t *testing.T) {
	// base-path is non-existing directory
	cfg := &Config{}
	cfg.BasePath = "/nonexistend/"
	cfg.DB.Type = testDBType
	cfg.DB.Connection = testDBConnection
	if _, err := NewStore(cfg); err == nil {
		t.Fatalf("opening store in nonexisting directory should throw an error")
	}
	cfg.BasePath = testBasePath
	if err := os.MkdirAll(testBasePath, 0700); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	/// exisiting but non-database file (only possible with sqlite...)
	// if f, err := os.Create(testDBConnection); err != nil {
	// 	t.Fatalf("unexpected error: %v", err)
	// } else {
	// 	io.WriteString(f, "this is not a sqlite db.")
	// 	f.Close()
	// }
	// if _, err := NewStore(cfg); err == nil {
	// 	t.Fatalf("opening store using a invalid database should throw an error")
	// }

	/// create new db and reopen it
	// os.Remove(testDBConnection)
	store, err := NewStore(cfg)
	if err != nil {
		t.Fatalf("creating new store failed: %v", err)
	}
	store.Close()

	store, err = NewStore(cfg)
	if err != nil {
		t.Fatalf("re-opening existing store failed: %v", err)
	}
	store.Close()
}

func TestMigrations(t *testing.T) {
	cfg := &Config{}
	cfg.BasePath = testBasePath
	cfg.DB.Type = testDBType
	cfg.DB.Connection = testDBConnection

	// os.Remove(testDBConnection)
	store, err := NewStore(cfg)
	if err != nil {
		t.Fatalf("creating new store failed: %v", err)
	}
	// TODO: This is only true for now!!
	if store.GetRevision() != "201806161853" {
		t.Fatalf("for now new databases should have revision %q: got %q", "201806161853", store.GetRevision())
	}
	store.Close()
	// TODO: needs more testing!!!!
}

//// Arrrrgh!!!!!
////  both database/sql and gorm are incredibly stupid!!!!
////  using database/sql alone it is needlessly hard to write portable sql queries:
////     postgres: db.Exec("INSERT INTO groups (name) VALUES ($1)", testGroup1)
////     mysql:    db.Exec("INSERT INTO groups (name) VALUES (?)", testGroup1)
////
////  using gorm db.Exec() will deal with that but i couldn't find a good way to get
////     the last inserted id without using the model code of gorm and we specifically
////     don't want to use the model code since is not clear whether the constraints
////     are actually enforced by the DBMS or by some gorm callbacks...
////
// func TestDBConstraints(t *testing.T) {
// 	// we don't want to use the model but hand written SQL commands here since we want to check
// 	// whether the constraints are really enforced by the DBMS and not by some magic bullshit in gorm!
//  // This is even worse since gorm.AutoUpdate will not deal with foreign key constraints and we
//  // need do it ourselves at the migration scripts. It would be really nice to be able to check
//  // whether the migrations do the right thing!!!

// 	db, err := gorm.Open(testDBType, testDBConnection)
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}
// 	if err = db.DB().Ping(); err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	res := db.Exec("INSERT INTO groups (name) VALUES (?)", testGroup1)
// 	if res.Error != nil {
// 		t.Fatalf("adding group '%s' shouldn't fail: %v", testGroup1, res.Error)
// 	}
// 	if res = db.Exec("INSERT INTO groups (name) VALUES (?)", testGroup1); res.Error == nil {
// 		t.Fatalf("re-adding the same group should fail")
// 	}

// 	if res = db.Exec("INSERT INTO files (group_name, size) VALUES (?, ?)", testGroup1, 500); res.Error != nil {
// 		t.Fatalf("re-adding the same group should fail")
// 	}
// }

// // Groups
// //

// func TestGroups(t *testing.T) {
// 	os.Remove(testDBPath)
// 	store, err := NewStore(&Config{testBasePath})
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	groups, err := store.ListGroups()
// 	if err != nil {
// 		t.Fatalf("listing groups failed: %v", err)
// 	}
// 	if len(groups) != 1 && groups[0] != publicGroupBn {
// 		t.Fatalf("a newly created store should contain a single group '%s' but ListGroups returned: %q", publicGroupBn, groups)
// 	}

// 	if err = store.createGroup(testGroup1); err != nil {
// 		t.Fatalf("creating group failed: %v", err)
// 	}
// 	groups, err = store.ListGroups()
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}
// 	groupsSorted := sort.StringSlice(groups)
// 	groupsSorted.Sort()

// 	groupsExpected := sort.StringSlice([]string{publicGroupBn, testGroup1})
// 	groupsExpected.Sort()

// 	if !reflect.DeepEqual(groupsSorted, groupsExpected) {
// 		t.Fatalf("store should contain groups %q but ListGroups returned: %q", groupsExpected, groupsSorted)
// 	}

// 	if err = store.createGroup(testGroup2); err != nil {
// 		t.Fatalf("creating group failed: %v", err)
// 	}
// 	groups, err = store.ListGroups()
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}
// 	groupsSorted = sort.StringSlice(groups)
// 	groupsSorted.Sort()

// 	groupsExpected = sort.StringSlice([]string{publicGroupBn, testGroup1, testGroup2})
// 	groupsExpected.Sort()

// 	if !reflect.DeepEqual(groupsSorted, groupsExpected) {
// 		t.Fatalf("store should contain groups %q but ListGroups returned: %q", groupsExpected, groupsSorted)
// 	}
// }

// // Files
// //

// func TestFilesListAndCreate(t *testing.T) {
// 	os.Remove(testDBPath)
// 	store, err := NewStore(&Config{testBasePath})
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	files, err := store.ListFiles(publicGroupBn)
// 	if err != nil {
// 		t.Fatalf("listing files of public group failed: %v", err)
// 	}
// 	if len(files) != 0 {
// 		t.Fatalf("a newly created store should contain no files in public group but ListFiles returned: %v", files)
// 	}

// 	files, err = store.ListFiles("notexistend")
// 	if err != nil {
// 		t.Fatalf("listing files of not existing group shouldn't throw an error but returned: %v", err)
// 	}
// 	if len(files) != 0 {
// 		t.Fatalf("listing files of not existing group should return and empty list but ListFiles returned: %v", files)
// 	}

// 	_, err = store.CreateFile(publicGroupBn, File{})
// 	if err != nil {
// 		t.Fatalf("creating file in public group failed: %v", err)
// 	}

// 	files, err = store.ListFiles(publicGroupBn)
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}
// 	if len(files) != 1 {
// 		t.Fatalf("ListFiles should return a single file but returned: %v", files)
// 	}

// 	_, err = store.CreateFile(testGroup1, File{})
// 	if err != nil {
// 		t.Fatalf("creating file in not existing group shouldn't throw an error but CreateFile returned: %v", err)
// 	}

// 	groups, err := store.ListGroups()
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}
// 	if !stringInSlice(groups, testGroup1) {
// 		t.Fatalf("creating file in not existing group '%s' should create that group but ListGroups returned : %q", testGroup1, groups)
// 	}
// }

// func TestNewAndChangedFile(t *testing.T) {
// 	file := NewFile(testUser1)
// 	if file.Created.User != testUser1 {
// 		t.Fatalf("new file should be created by user '%s' but is: '%s'", testUser1, file.Created.User)
// 	}
// 	age := time.Since(file.Created.Timestamp)
// 	if age < 0 || age > 3*time.Second {
// 		t.Fatalf("new file timestamp is off: %s", file.Created.Timestamp)
// 	}
// 	if file.LastChanged.User != "" || !file.LastChanged.Timestamp.IsZero() {
// 		t.Fatalf("new file's changed value should be empty but is: %+v", file.LastChanged)
// 	}

// 	orig := *file

// 	file.Changed(testUser2)
// 	if file.Created.User != orig.Created.User {
// 		t.Fatalf("marking file as changed shouldn't change Created.User or Created.Timestamp, was '%+v' but is now '%+v'", orig.Created, file.Created)
// 	}
// 	if file.LastChanged.User != testUser2 {
// 		t.Fatalf("changed file should have changed by user '%s' but is: '%s'", testUser2, file.LastChanged.User)
// 	}
// 	age = time.Since(file.LastChanged.Timestamp)
// 	if age < 0 || age > 3*time.Second {
// 		t.Fatalf("changed file timestamp is off: %s", file.LastChanged.Timestamp)
// 	}
// }

// func TestFilesCreateAndGet(t *testing.T) {
// 	os.Remove(testDBPath)
// 	store, err := NewStore(&Config{testBasePath})
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	_, err = store.GetFile(testGroup1, InvalidID)
// 	if err != ErrNotFound {
// 		t.Fatalf("getting file in not-existing group should return ErrNotFound, but GetFile returned: %v", err)
// 	}

// 	expected := NewFile(testUser1)
// 	fileID, err := store.CreateFile(testGroup1, *expected)
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	_, err = store.GetFile(testGroup1, fileID)
// 	//file, err := store.GetFile(testGroup1, fileID)
// 	if err != nil {
// 		t.Fatalf("getting existing file from store shouldn't return an error, but GetFile returned: %v", err)
// 	}
// 	// This does not work because DeepEqual does not use time.Equal when comparing timestamps...
// 	// if !reflect.DeepEqual(expected, file) {
// 	// 	t.Fatalf("GetFile returned different file than expected. Got %v, expected %v", file, expected)
// 	// }

// 	_, err = store.GetFile(testGroup1, InvalidID)
// 	if err != ErrNotFound {
// 		t.Fatalf("getting not-existing file in existing group should return ErrNotFound, but GetFile returned: %v", err)
// 	}
// }

// func TestFilesUpdate(t *testing.T) {
// 	os.Remove(testDBPath)
// 	store, err := NewStore(&Config{testBasePath})
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	err = store.UpdateFile(testGroup1, InvalidID, File{})
// 	if err != ErrNotFound {
// 		t.Fatalf("updateing not-existing file should return ErrNotFound, but UpdateFile returned: %v", err)
// 	}

// 	orig := NewFile(testUser1)
// 	fileID, err := store.CreateFile(testGroup1, *orig)
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	updated := *orig
// 	updated.Changed(testUser2)

// 	err = store.UpdateFile(testGroup1, fileID, updated)
// 	if err != nil {
// 		t.Fatalf("updating file failed, UpdateFile returned: %v", err)
// 	}

// 	// TODO: check if file was successfully updated
// }

// func TestFilesDelete(t *testing.T) {
// 	os.Remove(testDBPath)
// 	store, err := NewStore(&Config{testBasePath})
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	err = store.DeleteFile(testGroup1, InvalidID)
// 	if err != ErrNotFound {
// 		t.Fatalf("deleting not-existing file should return ErrNotFound, but DeleteFile returned: %v", err)
// 	}

// 	file := NewFile(testUser1)
// 	fileID, err := store.CreateFile(testGroup1, *file)
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	err = store.DeleteFile(testGroup1, fileID)
// 	if err != nil {
// 		t.Fatalf("deleting file failed, UpdateFile returned: %v", err)
// 	}

// 	err = store.DeleteFile(testGroup1, fileID)
// 	if err != ErrNotFound {
// 		t.Fatalf("repeated deletion of file should return ErrNotFound, but UpdateFile returned: %v", err)
// 	}

// 	// test file usage
// 	fileID, err = store.CreateFile(testGroup1, *file)
// 	if err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	file.UsedBy = []uint64{42}
// 	file.Changed(testUser1)
// 	if err = store.UpdateFile(testGroup1, fileID, *file); err != nil {
// 		t.Fatalf("unexpected error: %v", err)
// 	}

// 	err = store.DeleteFile(testGroup1, fileID)
// 	if err != ErrFileInUse {
// 		t.Fatalf("deleting  file should return ErrFileInUse, but DeleteFile returned: %v", err)
// 	}
// }