package socketclients

import (
	"encoding/base64"
	"jannex/admin-dashboard-backend/modules/cache"
	"jannex/admin-dashboard-backend/modules/database"
	"jannex/admin-dashboard-backend/modules/logger"
	"jannex/admin-dashboard-backend/modules/structs"
	"jannex/admin-dashboard-backend/modules/systempermissions"
	"jannex/admin-dashboard-backend/modules/utils"
	"os"
	"strconv"
	"time"

	"github.com/gofiber/websocket/v2"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

func BroadcastMessage(sendSocketMessage structs.SendSocketMessage) {
	for _, client := range cache.GetSocketClients() {
		client.SendMessage(sendSocketMessage)
	}
}

func BroadcastMessageExceptUserSessionId(ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) {
	for _, client := range cache.GetSocketClients() {
		if client.SessionId != ignoreUserSessionId {
			client.SendMessage(sendSocketMessage)
		}
	}
}

func BroadcastMessageExceptUserId(ignoreUserId string, sendSocketMessage structs.SendSocketMessage) {
	for _, client := range cache.GetSocketClients() {
		if client.UserId != ignoreUserId {
			client.SendMessage(sendSocketMessage)
		}
	}
}

func BroadcastMessageToUsersWithPermission(neededPermission string, sendSocketMessage structs.SendSocketMessage) {
	var rolePermissions []structs.RolePermission

	database.DB.Model(&structs.RolePermission{}).Where("permission_id = ?", neededPermission).Find(&rolePermissions)

	var foundUsers []structs.User

	for _, rolePermission := range rolePermissions {
		var users []structs.User

		database.DB.Where("role_id = ?", rolePermission.RoleId).Select("id").Find(&users)

		foundUsers = append(foundUsers, users...)
	}

	for _, foundUser := range foundUsers {
		SendMessageToUser(foundUser.Id, "", sendSocketMessage)
	}
}

func SendMessageToUser(userId string, ignoreUserSessionId string, sendSocketMessage structs.SendSocketMessage) {
	for _, client := range cache.GetSocketClients() {
		if client.UserId == userId && client.SessionId != ignoreUserSessionId {
			client.SendMessage(sendSocketMessage)
		}
	}
}

func SendMessageOnlyToSessionId(sessionId string, sendSocketMessage structs.SendSocketMessage) {
	for _, client := range cache.GetSocketClients() {
		if client.SessionId == sessionId {
			client.SendMessage(sendSocketMessage)
		}
	}
}

func SendMessageToUsersByRoleId(roleId string, sendSocketMessage structs.SendSocketMessage) {
	for _, client := range cache.GetSocketClients() {
		var user structs.User

		database.DB.Where("id = ?", client.UserId).First(&user)

		if user.RoleId == roleId {
			client.SendMessage(sendSocketMessage)
		}
	}
}

// This close all connections that are connected with one session id.
// For example when a user has two browser tabs opened
func CloseAllUserSessionConnections(sessionId string) {
	for _, client := range cache.GetSocketClients() {
		if client.SessionId == sessionId {
			client.SendSessionClosedMessage()
		}
	}
}

// Used to close all user connections
// For example when a user changed his password
func CloseAndDeleteAllUserConnections(userId string) {
	for _, client := range cache.GetSocketClients() {
		if client.UserId == userId {
			client.SendSessionClosedMessage()
		}
	}

	database.DB.Where("user_id = ?", userId).Delete(&structs.UserSession{})
}

func UpdateConnectedUsers(userId string) {
	var user structs.User

	database.DB.First(&user, "id = ?", userId)

	BroadcastMessage(structs.SendSocketMessage{
		Cmd: utils.SentCmdUpdateConnectedUsers,
		Body: struct {
			WebSocketUsersCount int
			UserId              string
			ConnectionStatus    uint8
			LastOnline          time.Time
		}{
			WebSocketUsersCount: len(cache.GetSocketClients()),
			UserId:              userId,
			ConnectionStatus:    isUserGenerallyConnected(userId),
			LastOnline:          user.LastOnline,
		},
	})
}

func GetUserSessions(userId string) []structs.UserSessionSocket {
	var userSessions []structs.UserSession

	database.DB.Where("user_id = ?", userId).Find(&userSessions)

	var userSessionsSocket []structs.UserSessionSocket

	socketClients := cache.GetSocketClients()

	for _, userSession := range userSessions {
		userSessionsSocket = append(userSessionsSocket, structs.UserSessionSocket{
			IdForDeletion:    userSession.IdForDeletion,
			UserAgent:        userSession.UserAgent,
			ConnectionStatus: isUserSessionConnected(userSession.Id, socketClients),
			LastUsed:         userSession.LastUsed,
			ExpiresAt:        userSession.ExpiresAt,
		})
	}

	return userSessionsSocket
}

func UpdateUserSessionsForUser(userId string, ignoreUserSessionId string) {
	GetUserSessions(userId)

	SendMessageToUser(userId, ignoreUserSessionId, structs.SendSocketMessage{
		Cmd:  utils.SentCmdUpdateUserSessions,
		Body: GetUserSessions(userId),
	})
}

func isUserSessionConnected(userSessionId string, socketClients []*structs.SocketClient) uint8 {
	for _, socketClient := range socketClients {
		if socketClient.SessionId == userSessionId {
			return 1
		}
	}

	return 0
}

// Used to determine if a user is connected regardless of the session used
func isUserGenerallyConnected(userId string) uint8 {
	for _, socketClient := range cache.GetSocketClients() {
		if socketClient.UserId == userId {
			return utils.ConnectionStateOnline
		}
	}

	return utils.ConnectionStateOffline
}

// Get all users from database.
// This is used in the UI to display all users.
func GetAllUsers() []structs.AllUsers {
	var users []structs.User
	var allUsers []structs.AllUsers

	database.DB.Find(&users)

	for _, user := range users {
		allUsers = append(allUsers, structs.AllUsers{
			Id:               user.Id,
			RoleId:           user.RoleId,
			Avatar:           user.Avatar,
			Username:         user.Username,
			ConnectionStatus: isUserGenerallyConnected(user.Id),
			Deactivated:      user.Deactivated,
			LastOnline:       user.LastOnline,
		})
	}

	return allUsers
}

func GetAllScanners() []structs.Scanner {
	var scanners []structs.Scanner
	var allScanners []structs.Scanner

	database.DB.Find(&scanners)

	for _, scanner := range scanners {
		// clear session to prevent leaking and sending to ui
		scanner.Session = ""

		allScanners = append(allScanners, scanner)
	}

	return allScanners
}

func isUsernameAvailable(username string) bool {
	var user structs.User
	database.DB.Select("username").Where("username = ?", username).Find(&user)

	return user.Username == ""
}

func isEmailAvailable(email string) bool {
	var user structs.User
	database.DB.Select("email").Where("email = ?", email).Find(&user)

	return user.Email == ""
}

func UpdateUserProfile(conn *websocket.Conn, changes map[string]interface{}) {
	sessionId := conn.Locals("sessionId").(string)
	userId := conn.Locals("userId").(string)

	var user structs.User
	var updates = make(map[string]interface{})

	if changes["username"] != nil {
		username := changes["username"].(string)

		if isValueLenValid(username, utils.MinUsername, utils.MaxUsername) { // only affected if username was manipulated as min and max is provided in web ui
			if isUsernameAvailable(username) {
				user.Username = username
				updates["Username"] = username
			} else {
				SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
					Cmd: utils.SentCmdUserProfileUpdated,
					Body: struct {
						UserId string
						Result uint8
					}{
						UserId: userId,
						Result: 0,
					},
				})
				return
			}
		}
	}

	if changes["email"] != nil {
		email := changes["email"].(string)

		if isEmailAvailable(email) {
			user.Email = email
			updates["Email"] = email
		} else {
			SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
				Cmd: utils.SentCmdUserProfileUpdated,
				Body: struct {
					UserId string
					Result uint8
				}{
					UserId: userId,
					Result: 1,
				},
			})
			return
		}
	}

	if changes["oldPassword"] != nil && changes["newPassword"] != nil {
		oldPassword := changes["oldPassword"].(string)
		newPassword := changes["newPassword"].(string)

		decodedOldPassword, err := base64.StdEncoding.DecodeString(oldPassword)
		decodedNewPassword, err1 := base64.StdEncoding.DecodeString(newPassword)

		if err != nil || err1 != nil {
			log.Error().Msgf("Error decoding old or new password %s %s", err.Error(), err1.Error())

			SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
				Cmd: utils.SentCmdUserProfileUpdated,
				Body: struct {
					UserId string
					Result uint8
				}{
					UserId: userId,
					Result: 2,
				},
			})
			return
		}

		if utils.IsPasswordLengthValid(string(decodedOldPassword)) { // only affected if username was manipulated as min and max is provided in web ui
			database.DB.Select("password").First(&user, "id = ?", userId)

			if err := bcrypt.CompareHashAndPassword([]byte(user.Password), decodedOldPassword); err == nil {
				hashedPassword, err := bcrypt.GenerateFromPassword([]byte(decodedNewPassword), bcrypt.DefaultCost)

				if err == nil {
					user.Password = string(hashedPassword)
				} else {
					log.Error().Msgf("Failed to generate hash password %s", err.Error())
				}
			} else {
				log.Error().Msg("Incorrect password")

				SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
					Cmd: utils.SentCmdUserProfileUpdated,
					Body: struct {
						UserId string
						Result uint8
					}{
						UserId: userId,
						Result: 2,
					},
				})
				return
			}
		}
	}

	if len(changes) > 0 {
		user.UpdatedAt = time.Now()

		database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(user)

		if changes["username"] != nil || changes["email"] != nil || changes["oldPassword"] != nil && changes["newPassword"] != nil {
			if changes["oldPassword"] != nil && changes["newPassword"] != nil {
				// user has changed password - logout all his sessions
				CloseAndDeleteAllUserConnections(userId)

				logger.AddSystemLog(structs.LogMessage{
					Id:   12,
					Type: utils.LogTypeInfo,
					Messages: []structs.LogData{
						{Type: "userId", Value: userId},
					},
				})
				return
			}

			SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
				Cmd: utils.SentCmdUserProfileUpdated,
				Body: struct {
					UserId  string
					Changes map[string]interface{}
				}{
					UserId:  userId,
					Changes: updates,
				},
			})

			BroadcastMessageExceptUserSessionId(sessionId, structs.SendSocketMessage{
				Cmd: utils.SentCmdUserProfileUpdated,
				Body: struct {
					UserId  string
					Changes map[string]interface{}
				}{
					UserId:  userId,
					Changes: updates,
				},
			})

			logger.AddSystemLog(structs.LogMessage{
				Id:   13,
				Type: utils.LogTypeInfo,
				Messages: []structs.LogData{
					{Type: "userId", Value: userId},
					{Type: "changes", Value: utils.MarshalJson(updates)},
				},
			})
		}
	}
}

func isValueLenValid(value string, min int, max int) bool {
	l := len(value)
	return l >= min && l <= max
}

func GetAllRoles() []structs.Role {
	var roles []structs.Role

	database.DB.Order("sorting_order").Find(&roles)

	return roles
}

func GetPermissionsByRoleId(roleId string) []string {
	var rolePermissions []structs.RolePermission

	database.DB.Where("role_id = ?", roleId).Find(&rolePermissions)

	var permissions []string

	for _, rolePermission := range rolePermissions {
		permissions = append(permissions, rolePermission.PermissionId)
	}

	return permissions
}

// Retrieve all roles with a list of all permissions for each role
func GetAdminAreaRolesPermissions() []structs.RolePermissions {
	roles := GetAllRoles()
	var rolesPermissions []structs.RolePermission

	database.DB.Find(&rolesPermissions)

	var rolePermissions []structs.RolePermissions

	for _, role := range roles {
		var permissions []string

		for _, rolePermission := range rolesPermissions {
			if rolePermission.RoleId == role.Id {
				permissions = append(permissions, rolePermission.PermissionId)
			}
		}

		rolePermissions = append(rolePermissions, structs.RolePermissions{
			RoleId:      role.Id,
			Permissions: permissions,
		})
	}

	return rolePermissions
}

func isRoleDisplayNameAvailable(roleDisplayName string) bool {
	var role structs.Role

	database.DB.Select("display_name").Where("display_name = ?", roleDisplayName).Find(&role)

	return role.DisplayName == ""
}

func AdminAreaUpdateRole(conn *websocket.Conn, body map[string]interface{}) {
	if body["RoleId"] == nil {
		log.Error().Msgf("No role id specified in update role %v", body)
		return
	}

	roleId := body["RoleId"].(string)
	changes := body["Changes"].(map[string]interface{})

	// user has nothing changed
	if changes["DisplayName"] == nil &&
		changes["Description"] == nil &&
		changes["AddedPermissions"] == nil &&
		changes["RemovedPermissions"] == nil {
		log.Error().Msgf("User has not specified anything to update the role %v", body)
		return
	}

	updatedRole := structs.Role{
		Id: roleId,
	}

	var changesResult = make(map[string]uint8)
	var updates = make(map[string]interface{})

	if changes["DisplayName"] != nil && isValueLenValid(changes["DisplayName"].(string), utils.MinRoleDisplayName, utils.MaxRoleDisplayName) {
		if isRoleDisplayNameAvailable(changes["DisplayName"].(string)) {
			updatedRole.DisplayName = changes["DisplayName"].(string)
			updates["DisplayName"] = changes["DisplayName"].(string)
		} else {
			changesResult["DisplayName"] = 1
		}
	}

	if changes["Description"] != nil {
		updatedRole.Description = changes["Description"].(string)
		updates["Description"] = changes["Description"].(string)
	}

	if changes["AddedPermissions"] != nil {
		addedPermissions := changes["AddedPermissions"].([]interface{})

		updates["AddedPermissions"] = addedPermissions

		var dbAddedPermissions []structs.RolePermission

		for _, addedPermission := range addedPermissions {
			dbAddedPermissions = append(dbAddedPermissions, structs.RolePermission{
				RoleId:       roleId,
				PermissionId: addedPermission.(string),
			})
		}

		database.DB.Create(dbAddedPermissions)
	}

	if changes["RemovedPermissions"] != nil {
		removedPermissions := changes["RemovedPermissions"].([]interface{})

		updates["RemovedPermissions"] = removedPermissions

		for _, removedPermission := range removedPermissions {
			database.DB.Where("role_id = ?", roleId).Where("permission_id = ?", removedPermission.(string)).Delete(&structs.RolePermission{})
		}
	}

	database.DB.Model(&structs.Role{}).Where("id = ?", roleId).Updates(&updatedRole)

	sessionId := conn.Locals("sessionId").(string)

	SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
		Cmd: utils.SentCmdAdminAreaRoleUpdated,
		Body: struct {
			RoleId  string
			Changes map[string]interface{}
			Result  map[string]uint8
		}{
			RoleId:  roleId,
			Changes: updates,
			Result:  changesResult,
		},
	})

	BroadcastMessageExceptUserSessionId(sessionId, structs.SendSocketMessage{
		Cmd: utils.SentCmdAdminAreaRoleUpdated,
		Body: struct {
			RoleId  string
			Changes map[string]interface{}
		}{
			RoleId:  roleId,
			Changes: updates,
		},
	})

	SendMessageToUsersByRoleId(roleId, structs.SendSocketMessage{
		Cmd:  utils.SentCmdRolePermissionsUpdated,
		Body: changes,
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   2,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: conn.Locals("userId").(string)},
			{Type: "roleId", Value: roleId},
			{Type: "changes", Value: utils.MarshalJson(updates)},
		},
	})
}

func AdminAreaMoveRoleToSortingOrder(conn *websocket.Conn, body map[string]interface{}) {
	roleId := body["RoleId"].(string)
	direction := int(body["Direction"].(float64))

	var role structs.Role

	if err := database.DB.First(&role, "id = ?", roleId).Error; err != nil {
		log.Error().Msgf("Failed to get role %s", err.Error())
		return
	}

	currentSortingOrder := role.SortingOrder
	var newSortingOrder int

	if direction == 1 {
		newSortingOrder = currentSortingOrder + 1

		if newSortingOrder > systempermissions.GetRoleSortingOrder()-1 {
			return
		}
	} else {
		newSortingOrder = currentSortingOrder - 1

		if newSortingOrder < 0 {
			return
		}
	}

	// If the new sorting order number is smaller than the current sorting order number,
	// increase the sorting order numbers of all roles between the new and current sorting order by 1
	if newSortingOrder < currentSortingOrder {
		if err := database.DB.Model(&structs.Role{}).Where("sorting_order >= ? AND sorting_order < ?", newSortingOrder, currentSortingOrder).Update("sorting_order", gorm.Expr("sorting_order + 1")).Error; err != nil {
			log.Error().Msgf("Failed to update sorting order upwards %s", err.Error())
			return
		}
	} else if newSortingOrder > currentSortingOrder {
		// If the new sorting order number is larger than the current sorting order number,
		// decrease the sorting order numbers of all roles between the current and new sorting order by 1.
		if err := database.DB.Model(&structs.Role{}).Where("sorting_order > ? AND sorting_order <= ?", currentSortingOrder, newSortingOrder).Update("sorting_order", gorm.Expr("sorting_order - 1")).Error; err != nil {
			log.Error().Msgf("Failed to update sorting order downwards %s", err.Error())
			return
		}
	}

	// Update the sorting order number of the moved role
	if err := database.DB.Model(&role).Update("sorting_order", newSortingOrder).Error; err != nil {
		log.Debug().Msgf("Failed to update sorting order %s", err.Error())
		return
	}

	BroadcastMessage(structs.SendSocketMessage{
		Cmd:  utils.SentCmdAdminAreaUpdateRoleSortingOrder,
		Body: body,
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   3,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: conn.Locals("userId").(string)},
			{Type: "roleId", Value: role.Id},
			{Type: "sortingOrder", Value: strconv.Itoa(newSortingOrder)},
		},
	})
}

func AdminAreaDeleteRole(conn *websocket.Conn, body map[string]interface{}) {
	roleId := body["RoleId"].(string)

	var role structs.Role

	database.DB.Where("id = ?", roleId).First(&role)

	if err := database.DB.Model(&structs.Role{}).Where("sorting_order > ?", role.SortingOrder).Update("sorting_order", gorm.Expr("sorting_order - 1")).Error; err != nil {
		log.Error().Msgf("Failed to update role sorting order after delete role %s", err.Error())
		return
	}

	database.DB.Where("id = ?", roleId).Delete(&structs.Role{})
	database.DB.Where("role_id = ?", roleId).Delete(&structs.RolePermission{})

	BroadcastMessage(structs.SendSocketMessage{
		Cmd:  utils.SentCmdAdminAreaRoleDeleted,
		Body: body,
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   4,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: conn.Locals("userId").(string)},
			{Type: "roleId", Value: role.Id},
		},
	})
}

func UpdateUserRole(conn *websocket.Conn, userId string, roleId string) {
	database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(structs.User{
		RoleId: roleId,
	})

	SendMessageToUser(userId, "", structs.SendSocketMessage{
		Cmd: utils.SentCmdAllUsersUserRoleUpdated,
		Body: struct {
			UserId      string
			RoleId      string
			Permissions []string
		}{
			UserId:      userId,
			RoleId:      roleId,
			Permissions: GetPermissionsByRoleId(roleId),
		},
	})

	BroadcastMessageExceptUserId(userId, structs.SendSocketMessage{
		Cmd: utils.SentCmdAllUsersUserRoleUpdated,
		Body: struct {
			UserId string
			RoleId string
		}{
			UserId: userId,
			RoleId: roleId,
		},
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   5,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: conn.Locals("userId").(string)},
			{Type: "roleId", Value: roleId},
			{Type: "targetUserId", Value: userId},
		},
	})
}

func HasPermission(userId string, permission string) bool {
	var user structs.User

	database.DB.Where("id = ?", userId).First(&user)

	var rolePermission structs.RolePermission

	database.DB.Where("role_id = ?", user.RoleId).Where("permission_id = ?", permission).Find(&rolePermission)

	return rolePermission.PermissionId == permission
}

func HasXYPermission(userId string, permission string, category string) bool {
	return HasPermission(userId, systempermissions.ConvertXYPermission(permission, category))
}

func SendErrorMessageNoPermissions(sessionId string) {
	SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
		Cmd: utils.SentCmdErrorNoPermissions,
	})
}

func AllUsersCreateNewUser(conn *websocket.Conn, body map[string]interface{}) {
	if body["Username"] == nil ||
		body["Email"] == nil ||
		body["Password"] == nil ||
		body["RoleId"] == nil {
		log.Error().Msgf("Invalid body provided for user creation: %v", body)
		return
	}

	username := body["Username"].(string)
	email := body["Email"].(string)
	password := body["Password"].(string)
	roleId := body["RoleId"].(string)

	if !isValueLenValid(username, utils.MinUsername, utils.MaxUsername) {
		log.Error().Msgf("Invalid username length: %s", username)
		return
	}

	sessionId := conn.Locals("sessionId").(string)

	if !isUsernameAvailable(username) {
		SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
			Cmd: utils.SentCmdAllUsersNewUserCreated,
			Body: struct {
				Result uint8
			}{
				Result: 0,
			},
		})
		return
	}

	if !isEmailAvailable(email) {
		SendMessageOnlyToSessionId(sessionId, structs.SendSocketMessage{
			Cmd: utils.SentCmdAllUsersNewUserCreated,
			Body: struct {
				Result uint8
			}{
				Result: 1,
			},
		})
		return
	}

	decodedPassword, err := base64.StdEncoding.DecodeString(password)

	if err != nil {
		log.Error().Msgf("Failed to decode base64 password, err: %s", err.Error())
		return
	}

	hashedPassword, err := bcrypt.GenerateFromPassword(decodedPassword, bcrypt.DefaultCost)

	if err != nil {
		log.Error().Msgf("Failed to generate password: %s", err.Error())
		return
	}

	newUser := structs.User{
		Id:        uuid.New().String(),
		RoleId:    roleId,
		Username:  username,
		Email:     email,
		Password:  string(hashedPassword),
		CreatedAt: time.Now(),
	}

	database.DB.Create(&newUser)

	BroadcastMessage(structs.SendSocketMessage{
		Cmd: utils.SentCmdAllUsersNewUserCreated,
		Body: struct {
			Id               string
			Username         string
			RoleId           string
			ConnectionStatus uint8
			Deactivated      bool
		}{
			Id:               newUser.Id,
			Username:         username,
			RoleId:           roleId,
			ConnectionStatus: utils.ConnectionStateOffline,
			Deactivated:      false,
		},
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   6,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: conn.Locals("userId").(string)},
			{Type: "targetUserId", Value: newUser.Id},
			{Type: "roleId", Value: roleId},
		},
	})
}

func AllUsersDeleteUser(conn *websocket.Conn, userId string) {
	var user structs.User

	database.DB.Select("avatar").Where("id = ?", userId).Find(&user)

	if user.Avatar != "" {
		os.Remove("./public/avatars/" + user.Avatar)
	}

	database.DB.Where("id = ?", userId).Delete(&structs.User{})
	database.DB.Where("user_id = ?", userId).Delete(&structs.UserSession{})

	var scannerInUsage structs.Scanner

	database.DB.Select("id").Where("used_by_user_id = ?", userId).Find(&scannerInUsage)

	if scannerInUsage.Id != "" {
		database.DB.Model(&structs.Scanner{}).Where("id = ?", scannerInUsage.Id).Updates(structs.Scanner{
			UsedByUserId: "",
		})
	}

	CloseAndDeleteAllUserConnections(userId)

	BroadcastMessage(structs.SendSocketMessage{
		Cmd: utils.SentCmdAllUsersUserDeleted,
		Body: struct {
			UserId    string
			ScannerId string
		}{
			UserId:    userId,
			ScannerId: scannerInUsage.Id,
		},
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   7,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: conn.Locals("userId").(string)},
			{Type: "targetUserId", Value: userId},
		},
	})
}

func AllUsersUserDeactivation(conn *websocket.Conn, userId string, deactivate bool) {
	database.DB.Model(&structs.User{}).Select("deactivated").Where("id = ?", userId).Updates(structs.User{
		Deactivated: deactivate,
	})

	if deactivate {
		CloseAndDeleteAllUserConnections(userId)
	}

	BroadcastMessage(structs.SendSocketMessage{
		Cmd: utils.SentCmdAllUsersUserDeactivation,
		Body: struct {
			UserId      string
			Deactivated bool
		}{
			UserId:      userId,
			Deactivated: deactivate,
		},
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   9,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: conn.Locals("userId").(string)},
			{Type: "targetUserId", Value: userId},
			{Type: "deactivated", Value: strconv.FormatBool(deactivate)},
		},
	})
}

func ScannersUpdateScannerUsedByUserId(userId string, scannerId string) {
	database.DB.Model(&structs.Scanner{}).Where("id = ?", scannerId).Updates(structs.Scanner{UsedByUserId: userId})

	BroadcastMessage(structs.SendSocketMessage{
		Cmd: utils.SentCmdUpdateScannerUsedBy,
		Body: struct {
			ScannerId    string
			UsedByUserId string
		}{
			ScannerId:    scannerId,
			UsedByUserId: userId,
		},
	})

	if userId != "" {
		logger.AddSystemLog(structs.LogMessage{
			Id:   10,
			Type: utils.LogTypeInfo,
			Messages: []structs.LogData{
				{Type: "scannerId", Value: scannerId},
				{Type: "userId", Value: userId},
			},
		})
	} else {
		logger.AddSystemLog(structs.LogMessage{
			Id:   11,
			Type: utils.LogTypeInfo,
			Messages: []structs.LogData{
				{Type: "scannerId", Value: scannerId},
			},
		})
	}
}

func GetUserApiKeys(userId string) []structs.UserApiKey {
	var apiKeys []structs.UserApiKey

	database.DB.Where("user_id = ?", userId).Find(&apiKeys)

	return apiKeys
}

func CreateNewUserApiKey(userId string, apiName string) {
	token, err := utils.GenerateSession()

	if err != nil {
		return
	}

	newApiKey := structs.UserApiKey{
		Id:         uuid.New().String(),
		Token:      token,
		UserId:     userId,
		Name:       apiName,
		UsageCount: 0,
		CreatedAt:  time.Now(),
	}

	database.DB.Create(&newApiKey)

	SendMessageToUser(userId, "", structs.SendSocketMessage{
		Cmd:  utils.SentCmdNewUserApiKeyCreated,
		Body: newApiKey,
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   23,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: userId},
		},
	})
}

func DeleteUserApiKey(userId string, apiKey string) {
	database.DB.Where("id = ?", apiKey).Where("user_id = ?", userId).Delete(&structs.UserApiKey{})

	SendMessageToUser(userId, "", structs.SendSocketMessage{
		Cmd:  utils.SentCmdDeletedUserApiKey,
		Body: apiKey,
	})

	logger.AddSystemLog(structs.LogMessage{
		Id:   24,
		Type: utils.LogTypeInfo,
		Messages: []structs.LogData{
			{Type: "userId", Value: userId},
		},
	})
}
