package management

import (
	"crypto/rand"
	"encoding/base64"
	"fmt"
	d "playback-device-server/data"
	"time"

	"github.com/rs/zerolog/log"
)

const DEFAULT_USERNAME = "admin"
const MIN_PASSWORD_LENGTH = 6

type UserManager struct {
	userDatabase *d.UserDatabase
}

func (um *UserManager) Initialize(userDatabase *d.UserDatabase) error {
	um.userDatabase = userDatabase

	exists, error := um.UsernameExists(DEFAULT_USERNAME)
	if error != nil {
		return error
	}

	if !exists {
		password, error := generateRandomPassword(MIN_PASSWORD_LENGTH)
		if error != nil {
			return error
		}

		log.Info().Str("username", DEFAULT_USERNAME).Str("password", password).Msg("creating default admin user")

		user := d.User{Username: DEFAULT_USERNAME, Password: password, IsAdmin: true}
		_, error = um.CreateUser(&user)
		if error != nil {
			return error
		}
	}

	return nil
}

func (um *UserManager) CreateUser(user *d.User) (string, error) {
	exists, error := um.UsernameExists(user.Username)
	if error != nil {
		return "", error
	}
	if exists {
		return "", fmt.Errorf("User '%s' already exists", user.Username)
	}

	if !isValidPassword(user.Password) {
		return "", fmt.Errorf("invalid password")
	}

	id, error := um.userDatabase.CreateUser(user.Username, user.Password, user.IsAdmin)
	if error != nil {
		return "", error
	}
	return id, nil
}

func (um *UserManager) UsernameExists(username string) (bool, error) {
	exists, error := um.userDatabase.UsernameExists(username)
	if error != nil {
		return false, error
	}
	return exists, nil
}

func (um *UserManager) UserIdExists(id string) (bool, error) {
	exists, error := um.userDatabase.UserIdExists(id)
	if error != nil {
		return false, error
	}
	return exists, nil
}

func (um *UserManager) Login(username, password string) (string, error) {
	exists, error := um.UsernameExists(username)
	if error != nil {
		return "", error
	}
	if !exists {
		return "", fmt.Errorf("user '%s' doesn't exist", username)
	}

	correct, error := um.userDatabase.CheckCredentials(username, password)
	if error != nil {
		return "", error
	}
	if !correct {
		return "", fmt.Errorf("wrong password")
	}

	user, error := um.userDatabase.GetUserByUsername(username)
	if error != nil {
		return "", error
	}

	expiryDate := time.Now().AddDate(0, 0, 30)
	token, error := um.userDatabase.CreateSession(user.ID, expiryDate)
	if error != nil {
		return "", error
	}

	return token, nil
}

func (um *UserManager) GetSession(sessionToken string) (*d.UserSession, error) {
	session, error := um.userDatabase.GetSession(sessionToken)
	if error != nil {
		return nil, error
	}
	return session, nil
}

func (um *UserManager) GetUserById(id string) (*d.User, error) {
	user, error := um.userDatabase.GetUserById(id)
	if error != nil {
		return nil, error
	}
	return user, nil
}

func (um *UserManager) UpdateUser(user *d.User) error {
	error := um.userDatabase.UpdateUser(user)
	return error
}

func (um *UserManager) UpdatePassword(currentPassword string, newPassword string, user *d.User) error {
	correct, error := um.userDatabase.CheckCredentials(user.Username, currentPassword)
	if error != nil {
		return error
	}
	if !correct {
		return fmt.Errorf("wrong password")
	}

	if !isValidPassword(user.Password) {
		return fmt.Errorf("invalid password")
	}

	error = um.userDatabase.UpdatePassword(user.ID, newPassword)
	return error
}

func (um *UserManager) DeleteSession(token string) error {
	error := um.userDatabase.DeleteSessionByToken(token)
	if error != nil {
		return error
	}
	return nil
}

func (um *UserManager) GetUsers() (*[]d.User, error) {
	users, error := um.userDatabase.GetUsers()

	return users, error
}

func (um *UserManager) DeleteUser(ID string) error {
	error := um.userDatabase.DeleteUser(ID)

	return error
}

func isValidPassword(password string) bool {
	return len(password) >= MIN_PASSWORD_LENGTH
}

func generateRandomPassword(length int) (string, error) {
	numBytes := length * 3 / 4 // Base64 encoding increases length by 4/3

	randomBytes := make([]byte, numBytes)
	_, err := rand.Read(randomBytes)
	if err != nil {
		return "", err
	}

	password := base64.URLEncoding.EncodeToString(randomBytes)

	if len(password) > length {
		password = password[:length]
	}

	return password, nil
}
