package api

import (
	"encoding/base64"
	"time"

	"code.fbi.h-da.de/danet/gosdn/controller/conflict"
	"code.fbi.h-da.de/danet/gosdn/controller/rbac"
	rbacImpl "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"
)

// 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: "admin"}
var userRoleMap = map[string]string{pndID: "user"}

func clearAndCreateAuthTestSetup() 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()
	if err != nil {
		return err
	}

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

	return nil
}

func createTestUsers() 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 := []rbacImpl.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(rbacImpl.NewUser(u.ID(), u.Name(), u.Roles, u.Password, "", salt, conflict.Metadata{ResourceVersion: 0}))
		if err != nil {
			return err
		}
	}

	return nil
}

func createTestRoles() error {
	roles := []rbacImpl.Role{
		{
			RoleID:      uuid.MustParse(adminRoleID),
			RoleName:    "adminTestRole",
			Description: "Admin",
			Permissions: []string{
				"/gosdn.core.CoreService/GetPnd",
				"/gosdn.core.CoreService/GetPndList",
			},
		},
		{
			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(rbacImpl.NewRole(r.ID(), r.Name(), r.Description, r.Permissions))
		if err != nil {
			return err
		}
	}

	return nil
}

// 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) (string, error) {
	jwt := rbacImpl.NewJWTManager("", 1*time.Minute)

	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))
}
