package server

import (
	"bytes"
	"encoding/base64"
	"log"
	"testing"

	"code.fbi.h-da.de/danet/gosdn/controller/conflict"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/networkelement"
	"code.fbi.h-da.de/danet/gosdn/controller/interfaces/plugin"
	rbacInterfaces "code.fbi.h-da.de/danet/gosdn/controller/interfaces/rbac"
	"code.fbi.h-da.de/danet/gosdn/controller/mocks"

	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
	"code.fbi.h-da.de/danet/gosdn/controller/store"
	"github.com/google/uuid"
	"github.com/sethvargo/go-password/password"
	"golang.org/x/crypto/argon2"
)

const pndID = "2043519e-46d1-4963-9a8e-d99007e104b8"
const pluginID = "22ecbd5e-37de-43e3-be56-358f9716fd7d"
const pendingChangeID = "0992d600-f7d4-4906-9559-409b04d59a5f"
const committedChangeID = "804787d6-e5a8-4dba-a1e6-e73f96b0119e"
const mneID = "7e0ed8cc-ebf5-46fa-9794-741494914883"

var hostname = "manfred"

var pndUUID uuid.UUID
var pluginUUID uuid.UUID
var pendingChangeUUID uuid.UUID
var committedChangeUUID uuid.UUID
var mneUUID uuid.UUID
var mockPnd *mocks.NetworkDomain
var mockNetworkElement networkelement.NetworkElement

// Name of this file requires _test at the end, because of how the availability of varibales is handled in test files of go packages.
// Does not include actual file tests!

const adminID = "5c248a22-8eb7-48cf-b392-45680a1863a5"
const userID = "57005d13-7a4d-493d-a02b-50ca51c40197"
const adminRoleID = "126683ae-5ff2-43ee-92f7-0e2b936f8c77"
const randomRoleName = "bertram"

var adminRoleMap = map[string]string{pndID: "adminTestRole"}
var userRoleMap = map[string]string{pndID: "userTestRole"}

func clearAndCreateAuthTestSetup(userService rbacInterfaces.UserService, roleService rbacInterfaces.RoleService) error {
	//clear setup if changed
	storedUsers, err := userService.GetAll()
	if err != nil {
		return err
	}
	for _, u := range storedUsers {
		err = userService.Delete(u)
		if err != nil {
			return err
		}
	}

	storedRoles, err := roleService.GetAll()
	if err != nil {
		return err
	}
	for _, r := range storedRoles {
		err = roleService.Delete(r)
		if err != nil {
			return err
		}
	}

	// create dataset
	err = createTestUsers(userService)
	if err != nil {
		return err
	}

	err = createTestRoles(roleService)
	if err != nil {
		return err
	}

	return nil
}

func createTestUsers(userService rbacInterfaces.UserService) error {
	randomRoleMap := map[string]string{pndID: randomRoleName}

	// Generate a salt that is 16 characters long with 3 digits, 0 symbols,
	// allowing upper and lower case letters, disallowing repeat characters.
	salt, err := password.Generate(16, 3, 0, true, false)
	if err != nil {
		return err
	}

	testAdminPWD := createHashedAndSaltedPassword("admin", salt)
	testUserPWD := createHashedAndSaltedPassword("user", salt)
	testRandPWD := createHashedAndSaltedPassword("aurelius", salt)

	users := []rbac.User{
		{UserID: uuid.MustParse(adminID), UserName: "testAdmin", Roles: adminRoleMap, Password: testAdminPWD},
		{UserID: uuid.MustParse(userID), UserName: "testUser", Roles: userRoleMap, Password: testUserPWD},
		{UserID: uuid.New(), UserName: "testRandom", Roles: randomRoleMap, Password: testRandPWD, Token: "wrong token"},
	}

	for _, u := range users {
		err := userService.Add(rbac.NewUser(u.ID(), u.Name(), u.Roles, u.Password, "", salt, conflict.Metadata{ResourceVersion: 0}))
		if err != nil {
			return err
		}
	}

	return nil
}

func createTestRoles(roleService rbacInterfaces.RoleService) error {
	roles := []rbac.Role{
		{
			RoleID:      uuid.MustParse(adminRoleID),
			RoleName:    "adminTestRole",
			Description: "Admin",
			Permissions: []string{
				"/gosdn.core.CoreService/GetPnd",
				"/gosdn.core.CoreService/GetPndList",
				"/gosdn.rbac.UserService/GetUsers",
				"/gosdn.plugin_internal.PluginInternalService/GetPluginSchema",
			},
		},
		{
			RoleID:      uuid.New(),
			RoleName:    "userTestRole",
			Description: "User",
			Permissions: []string{
				"/gosdn.pnd.PndService/GetChangeList",
			},
		},
		{
			RoleID:      uuid.New(),
			RoleName:    randomRoleName,
			Description: "Not a role",
			Permissions: []string{
				"nope",
			},
		},
	}

	for _, r := range roles {
		err := roleService.Add(rbac.NewRole(r.ID(), r.Name(), r.Description, r.Permissions))
		if err != nil {
			return err
		}
	}

	return nil
}

// This is needed as a workaround for a bug where the output of the getUser test falsely was
// that it failed while actually passing. Apparently, this can happen when loggers write
// the output of test cases.
// Solution found here: https://github.com/gotestyourself/gotestsum/issues/141#issuecomment-686243110
func patchLogger(t *testing.T) {
	orig := log.Writer()
	buf := new(bytes.Buffer)
	log.SetOutput(buf)

	t.Cleanup(func() {
		// optionally check t.Failed here if you only want to print logs on failure

		t.Log(buf.String())
		log.SetOutput(orig)
	})
}

// Creates a token to be used in auth interceptor tests. If validTokenRequired is set as true, the generated token will also
// be attached to the provided user. Else the user won't have the token and can not be authorized.
func createTestUserToken(userName string, validTokenRequired bool, userService rbacInterfaces.UserService, jwt *rbac.JWTManager) (string, error) {
	token, err := jwt.GenerateToken(rbac.User{UserName: userName})
	if err != nil {
		return token, err
	}

	if validTokenRequired {
		user, err := userService.Get(store.Query{Name: userName})
		if err != nil {
			return token, err
		}
		user.SetToken(token)

		err = userService.Update(user)
		if err != nil {
			return token, err
		}
	}

	return token, nil
}

func createHashedAndSaltedPassword(plainPWD, salt string) string {
	return base64.RawStdEncoding.EncodeToString(argon2.IDKey([]byte(plainPWD), []byte(salt), 1, 64*1024, 4, 32))
}

// func getMockPnd(t *testing.T) networkdomain.NetworkDomain {
// 	mockNetworkElement = &nucleus.CommonNetworkElement{
// 		Plugin: &mocks.Plugin{},
// 		UUID:   mneUUID,
// 	}

// 	mockNetworkElement.(*nucleus.CommonNetworkElement).SetTransport(&mocks.Transport{})
// 	mockNetworkElement.(*nucleus.CommonNetworkElement).SetName(hostname)

// 	mockPnd = &mocks.NetworkDomain{}
// 	mockPnd.On("ID").Return(pndUUID)
// 	mockPnd.On("GetName").Return("test")
// 	mockPnd.On("GetDescription").Return("test")
// 	mockPnd.On("NetworkElements").Return([]uuid.UUID{mneUUID})
// 	mockPnd.On("PendingChanges").Return([]uuid.UUID{pendingChangeUUID})
// 	mockPnd.On("CommittedChanges").Return([]uuid.UUID{committedChangeUUID})
// 	mockPnd.On("AddNetworkElement", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
// 	mockPnd.On("GetNetworkElement", mock.Anything).Return(mockNetworkElement, nil)
// 	mockPnd.On("Commit", mock.Anything).Return(nil)
// 	mockPnd.On("Confirm", mock.Anything).Return(nil)
// 	mockPnd.On("ChangeMNE", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(uuid.Nil, nil)
// 	mockPnd.On("Request", mock.Anything, mock.Anything).Return(nil, nil)

// 	return mockPnd
// }

func getMockPlugin(t *testing.T) plugin.Plugin {
	mockPlugin := &mocks.Plugin{}

	mockPlugin.On("ID").Return(pluginUUID)
	mockPlugin.On("SchemaTreeGzip").Return([]byte("schema_test"), nil)
	return mockPlugin
}

func initUUIDs(t *testing.T) {
	var err error
	pndUUID, err = uuid.Parse(pndID)
	if err != nil {
		t.Fatal(err)
	}

	pluginUUID, err = uuid.Parse(pluginID)
	if err != nil {
		t.Fatal(err)
	}

	pendingChangeUUID, err = uuid.Parse(pendingChangeID)
	if err != nil {
		t.Fatal(err)
	}

	committedChangeUUID, err = uuid.Parse(committedChangeID)
	if err != nil {
		t.Fatal(err)
	}

	mneUUID, err = uuid.Parse(mneID)
	if err != nil {
		t.Fatal(err)
	}
}

// func createTestNetworkElementServer(t *testing.T, mneServer *NetworkElementServer) {
// 	initUUIDs(t)

// 	eventService := eventservice.NewMockEventService()

// 	pluginService := nucleus.NewPluginServiceMock()

// 	pndStore := nucleus.NewPndStore(pluginService)
// 	pndService := nucleus.NewPndService(pndStore)

// 	mneStore := nucleus.NewNetworkElementStore()
// 	mneService := nucleus.NewNetworkElementService(mneStore, pluginService, eventService)

// 	changeStore := store.NewChangeStore()

// 	*mneServer = *NewNetworkElementServer(mneService, pndService, pluginService, *changeStore)

// 	//t.Cleanup(removeTestStores)
// }

// func removeTestStores() {
// 	ex, err := os.Executable()
// 	if err != nil {
// 		log.Println(err)
// 	}
// 	exPath := filepath.Dir(ex)

// 	fmt.Println(exPath)

// 	err = os.RemoveAll(exPath + "/stores_testing")
// 	if err != nil {
// 		log.Println(err)
// 	}
// }

// func cleanMneAndSbiTestStore(mneServer *NetworkElementServer) {
// 	mneToDelete, _ := mneServer.mneService.GetAll()
// 	for _, mne := range mneToDelete {
// 		_ = mneServer.mneService.Delete(mne)
// 	}
// }
