package socketserver

import (
	"encoding/json"
	"fmt"
	"jannex/admin-dashboard-backend/modules/cache"
	"jannex/admin-dashboard-backend/modules/database"
	"jannex/admin-dashboard-backend/modules/grouptasks"
	"jannex/admin-dashboard-backend/modules/logger"
	"jannex/admin-dashboard-backend/modules/structs"
	"jannex/admin-dashboard-backend/modules/systempermissions"
	"jannex/admin-dashboard-backend/modules/utils"
	"jannex/admin-dashboard-backend/socketclients"
	"time"

	"github.com/gofiber/websocket/v2"
	"github.com/google/uuid"
	"github.com/rs/zerolog/log"
)

var register = make(chan *structs.SocketClient)
var broadcast = make(chan structs.SocketMessage)
var unregister = make(chan *websocket.Conn)

func RunHub() {
	for {
		select {
		case newSocketClient := <-register:
			userId := fmt.Sprintf("%v", newSocketClient.Conn.Locals("userId"))
			sessionId := fmt.Sprintf("%v", newSocketClient.Conn.Locals("sessionId"))

			// close connection instantly if sessionId is empty
			if sessionId == "<nil>" {
				newSocketClient.SendUnauthorizedCloseMessage()
				continue
			}

			newSocketClient.SessionId = sessionId
			newSocketClient.UserId = userId

			cache.AddSocketClient(sessionId, newSocketClient)

			// check that user session is not expired
			var userSession structs.UserSession

			database.DB.First(&userSession, "id = ?", sessionId)

			if userSession.Id != "" && time.Now().After(userSession.ExpiresAt) {
				newSocketClient.SendUnauthorizedCloseMessage()
				database.DB.Delete(&structs.UserSession{}, "id = ?", sessionId)
				continue
			}

			// update session last used time
			database.DB.Model(&structs.UserSession{}).Where("id = ?", sessionId).Updates(structs.UserSession{
				LastUsed:  time.Now(),
				ExpiresAt: utils.GetSessionExpiresAtTime(),
			})

			// init data message
			var user structs.User

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

			newSocketClient.SendMessage(structs.SendSocketMessage{
				Cmd: utils.SentCmdInitUserSocketConnection,
				Body: structs.InitUserSocketConnection{
					User: structs.UserData{
						Id:          user.Id,
						RoleId:      user.RoleId,
						Username:    user.Username,
						Email:       user.Email,
						Sessions:    socketclients.GetUserSessions(userId),
						Permissions: socketclients.GetPermissionsByRoleId(user.RoleId),
						ApiKeys:     socketclients.GetUserApiKeys(userId),
					},
					CategoryGroups:  cache.GetCategoryGroupsSorted(),
					GroupTasks:      grouptasks.GetAllGroupTasks(),
					GroupTasksSteps: grouptasks.GetAllGroupTasksSteps(),
					AllUsers:        socketclients.GetAllUsers(),
					Scanners:        socketclients.GetAllScanners(),
					AllRoles:        socketclients.GetAllRoles(),
					AdminArea: structs.InitUserSocketConnectionAdminArea{
						RolesPermissions: socketclients.GetAdminAreaRolesPermissions(),
					},
				},
			})

			socketclients.UpdateConnectedUsers(userId)
			socketclients.UpdateUserSessionsForUser(userId, sessionId)

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

		case data := <-broadcast:
			var receivedMessage structs.ReceivedMessage

			if err := json.Unmarshal(data.Msg, &receivedMessage); err != nil {
				log.Error().Msgf("Failed to unmarshal received msg, err: %s", err)
				continue
			}

			log.Debug().Msgf("Received message: %v %v", receivedMessage, receivedMessage.Cmd)

			switch receivedMessage.Cmd {
			case utils.ReceivedCmdStartGroupTasks:
				category := receivedMessage.Body["category"].(string)

				if !socketclients.HasXYPermission(data.Conn.Locals("userId").(string), utils.PermissionGroupTasksOverviewXYNewTask, category) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				groupId := receivedMessage.Body["id"].(string)

				globalInputsJsonString := utils.MarshalJson(receivedMessage.Body["globalInputs"])

				grouptasks.StartGroupTask(data.Conn.Locals("userId").(string),
					structs.GroupTasks{
						Category:      category,
						GroupId:       groupId,
						GroupName:     receivedMessage.Body["groupName"].(string),
						Description:   receivedMessage.Body["description"].(string),
						NumberOfSteps: uint8(receivedMessage.Body["numberOfSteps"].(float64)),
						GlobalInputs:  globalInputsJsonString,
						RememberId:    receivedMessage.Body["rememberId"].(string),
					})

				//groupTaskId := uuid.New().String()

				/*
					groupTasks := structs.GroupTasks{
						Id:               groupTaskId,
						CreatorUserId:    data.Conn.Locals("userId").(string),
						Category:         category,
						GroupId:          groupId,
						GroupName:        receivedMessage.Body["groupName"].(string),
						Description:      receivedMessage.Body["description"].(string),
						CurrentTasksStep: 1,
						NumberOfSteps:    uint8(receivedMessage.Body["numberOfSteps"].(float64)),
						Status:           utils.GroupTasksStatusRunning,
						GlobalInputs:     globalInputsJsonString,
						StartedAt:        time.Now(),
						RememberId:       receivedMessage.Body["rememberId"].(string),
					}

					database.DB.Create(&groupTasks)

					socketclients.BroadcastMessage(structs.SendSocketMessage{
						Cmd:  utils.SentCmdNewGroupTaskStarted,
						Body: groupTasks,
					})

					go grouptasks.RunGroupTask(grouptasks.RunGroupTaskArgs{
						CreatorUserId: data.Conn.Locals("userId").(string),
						StartType:     grouptasks.RunGroupTaskStartTypeNormal,
						GroupTaskId:   groupTaskId,
						Category:      category,
						GroupId:       groupId,
						Step:          1,
						TaskStepId:    "",
						GlobalInputs:  globalInputsJsonString,
					})

					logger.AddGroupTasksLog(structs.LogMessage{
						Id:   0,
						Type: utils.LogTypeInfo,
						Messages: []structs.LogData{
							{Type: "userId", Value: data.Conn.Locals("userId").(string)},
							{Type: "groupTaskId", Value: groupTaskId},
							{Type: "groupTaskName", Value: groupTasks.GroupName},
						},
					}) */

				break
			case utils.ReceivedCmdTaskFailedTryAgainRunTaskStep:
				groupTaskArgs := grouptasks.RunGroupTaskArgs{
					CreatorUserId: data.Conn.Locals("userId").(string),
					StartType:     grouptasks.RunGroupTaskStartTypeTryAgain,
					GroupTaskId:   receivedMessage.Body["groupTaskId"].(string),
					Category:      receivedMessage.Body["category"].(string),
					GroupId:       receivedMessage.Body["groupId"].(string),
					Step:          uint8(receivedMessage.Body["step"].(float64)),
					TaskStepId:    receivedMessage.Body["taskStepId"].(string),
				}

				go grouptasks.RunGroupTask(groupTaskArgs)

				logger.AddGroupTasksLog(structs.LogMessage{
					Id:   4,
					Type: utils.LogTypeInfo,
					Messages: []structs.LogData{
						{Type: "taskStepId", Value: groupTaskArgs.TaskStepId},
						{Type: "groupTaskId", Value: groupTaskArgs.GroupTaskId},
						{Type: "userId", Value: groupTaskArgs.CreatorUserId},
					},
				})
				break
			case utils.ReceivedCmdTaskContinueTaskStep:
				groupTaskArgs := grouptasks.RunGroupTaskArgs{
					CreatorUserId: data.Conn.Locals("userId").(string),
					StartType:     grouptasks.RunGroupTaskStartTypeTryAgain,
					GroupTaskId:   receivedMessage.Body["groupTaskId"].(string),
					Category:      receivedMessage.Body["category"].(string),
					GroupId:       receivedMessage.Body["groupId"].(string),
					Step:          uint8(receivedMessage.Body["step"].(float64)),
					TaskStepId:    receivedMessage.Body["taskStepId"].(string),
					TaskInputs:    utils.MarshalJson(receivedMessage.Body["taskInputs"]),
				}

				go grouptasks.RunGroupTask(groupTaskArgs)

				logger.AddGroupTasksLog(structs.LogMessage{
					Id:   3,
					Type: utils.LogTypeInfo,
					Messages: []structs.LogData{
						{Type: "taskStepId", Value: groupTaskArgs.TaskStepId},
						{Type: "groupTaskId", Value: groupTaskArgs.GroupTaskId},
						{Type: "userId", Value: groupTaskArgs.CreatorUserId},
					},
				})
				break
			case utils.ReceivedCmdReloadGroupTasks:
				category := receivedMessage.Body["category"].(string)

				if !socketclients.HasXYPermission(data.Conn.Locals("userId").(string), utils.PermissionGroupTasksOverviewXYReloadGroupConfig, category) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.BroadcastMessageToUsersWithPermission(
					systempermissions.ConvertXYPermission(utils.PermissionGroupTasksOverviewXYView, category),
					structs.SendSocketMessage{
						Cmd:  utils.SentCmdReloadingGroupTasks,
						Body: category,
					})

				grouptasks.ReloadCategoryGroups(category)

				logger.AddGroupTasksLog(structs.LogMessage{
					Id:   1,
					Type: utils.LogTypeInfo,
					Messages: []structs.LogData{
						{Type: "userId", Value: data.Conn.Locals("userId").(string)},
						{Type: "category", Value: category},
					},
				})
				break
			case utils.ReceivedCmdTaskLocking:
				cache.AddLockedGroupTaskStep(structs.LockedGroupTaskSteps{
					LockedByUserId: receivedMessage.Body["lockedByUserId"].(string),
					GroupTaskId:    receivedMessage.Body["groupTaskId"].(string),
					Step:           uint8(receivedMessage.Body["step"].(float64)),
					LockedAt:       time.Now(),
					RememberId:     receivedMessage.Body["rememberId"].(string),
				})

				socketclients.BroadcastMessage(structs.SendSocketMessage{
					Cmd:  utils.SentCmdTaskLocked,
					Body: receivedMessage.Body,
				})

				cache.AddGroupTaskStepsInput(structs.GroupTaskStepsInput{
					GroupTaskId:   receivedMessage.Body["groupTaskId"].(string),
					Step:          uint8(receivedMessage.Body["step"].(float64)),
					ParameterName: receivedMessage.Body["parameterName"].(string),
					Value:         receivedMessage.Body["value"],
				})
				break
			case utils.ReceivedCmdUpdateUserProfile:
				socketclients.UpdateUserProfile(data.Conn, receivedMessage.Body["changes"].(map[string]interface{}))
				break
			case utils.ReceivedCmdAdminAreaCreateNewRole:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAdminAreaCreateNewRole) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				role := structs.Role{
					Id:           uuid.New().String(),
					DisplayName:  "New Role " + uuid.New().String(),
					Description:  "Role description",
					CreatedAt:    time.Now(),
					SortingOrder: systempermissions.GetRoleSortingOrder(),
				}

				database.DB.Create(&role)

				socketclients.BroadcastMessage(structs.SendSocketMessage{
					Cmd:  utils.SentCmdAdminAreaNewRoleCreated,
					Body: role,
				})

				logger.AddSystemLog(structs.LogMessage{
					Id:   1,
					Type: utils.LogTypeInfo,
					Messages: []structs.LogData{
						{Type: "userId", Value: data.Conn.Locals("userId").(string)},
						{Type: "roleId", Value: role.Id},
					},
				})
				break
			case utils.ReceivedCmdAdminAreaUpdateRole:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAdminAreaUpdateRole) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.AdminAreaUpdateRole(data.Conn, receivedMessage.Body)
				break
			case utils.ReceivedCmdAdminAreaUpdateRoleSortingOrder:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAdminAreaMoveRoleUpDown) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.AdminAreaMoveRoleToSortingOrder(data.Conn, receivedMessage.Body)
				break
			case utils.ReceivedCmdAdminAreaDeleteRole:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAdminAreaDeleteRole) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.AdminAreaDeleteRole(data.Conn, receivedMessage.Body)
				break
			case utils.ReceivedCmdAllUsersUpdateUserRole:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAllUsersActionChangeRole) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.UpdateUserRole(data.Conn, receivedMessage.Body["UserId"].(string), receivedMessage.Body["RoleId"].(string))
				break
			case utils.ReceivedCmdAllUsersCreateNewUser:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAllUsersCreateNewUser) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.AllUsersCreateNewUser(data.Conn, receivedMessage.Body)
				break
			case utils.ReceivedCmdAllUsersDeleteUser:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAllUsersActionDeleteUser) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.AllUsersDeleteUser(data.Conn, receivedMessage.Body["UserId"].(string))
				break
			case utils.ReceivedCmdAllUsersUserDeactivation:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionAllUsersActionUserDeactivation) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.AllUsersUserDeactivation(data.Conn, receivedMessage.Body["UserId"].(string), receivedMessage.Body["Deactivation"].(bool))
				break
			case utils.ReceivedCmdScannersUseScanners:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionScannerUseScanners) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.ScannersUpdateScannerUsedByUserId(data.Conn.Locals("userId").(string), receivedMessage.Body["ScannerId"].(string))
				break
			case utils.ReceivedCmdScannersDisconnectScanner:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionScannerUseScanners) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				socketclients.ScannersUpdateScannerUsedByUserId("", receivedMessage.Body["ScannerId"].(string))
				break
			case utils.ReceivedCmdGroupTasksCheckingForCategoryGroupChanges:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionGroupTasksCheckingForCategoryGroupChanges) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				grouptasks.LookingForCategoryGroupChanges(data.Conn.Locals("userId").(string))
				break
			case utils.ReceivedCmdHandleUserActionTaskStep:
				if !socketclients.HasXYPermission(data.Conn.Locals("userId").(string), utils.PermissionGroupTasksOverviewXYReloadGroupConfig, receivedMessage.Body["category"].(string)) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				grouptasks.HandleUserActionTaskStep(data.Conn.Locals("userId").(string), receivedMessage.Body)
				break
			case utils.ReceivedCmdCreateNewUserApiKey:
				socketclients.CreateNewUserApiKey(data.Conn.Locals("userId").(string), receivedMessage.Body["Name"].(string))
				break
			case utils.ReceivedCmdDeleteUserApiKey:
				socketclients.DeleteUserApiKey(data.Conn.Locals("userId").(string), receivedMessage.Body["Id"].(string))
				break
			case utils.ReceivedCmdGroupTasksInstallPythonPackages:
				if !socketclients.HasXYPermission(data.Conn.Locals("userId").(string), utils.PermissionGroupTasksOverviewXYInstallPythonPackages, receivedMessage.Body["category"].(string)) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				grouptasks.InstallPythonPackages(data.Conn.Locals("userId").(string), receivedMessage.Body["category"].(string), receivedMessage.Body["groupId"].(string))
				break
			case utils.ReceivedCmdGroupTasksInstallGlobalPythonPackages:
				if !socketclients.HasPermission(data.Conn.Locals("userId").(string), utils.PermissionGroupTasksInstallGlobalPythonPackages) {
					socketclients.SendErrorMessageNoPermissions(data.Conn.Locals("sessionId").(string))
					break
				}

				grouptasks.InstallGlobalPythonPackages(data.Conn.Locals("userId").(string))
				break
			default:
				log.Error().Msgf("Received unknown message: %v", receivedMessage)
				break
			}

		case connection := <-unregister:
			cache.DeleteClientByConn(connection)

			if connection.Locals("userId") != nil && connection.Locals("sessionId") != nil {
				userId := connection.Locals("userId").(string)
				sessionId := connection.Locals("sessionId").(string)

				database.DB.Model(&structs.User{}).Where("id = ?", userId).Updates(structs.User{
					LastOnline: time.Now(),
				})

				socketclients.UpdateUserSessionsForUser(userId, sessionId)
				socketclients.UpdateConnectedUsers(userId)

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