import buildFunctionHandler from "@utils/buildFunctionHandler"

import { ChatMessage } from "@db_models"

export default class Room {
	constructor(io, roomID, options) {
		if (!io) {
			throw new OperationError(500, "io is required")
		}

		this.io = io

		if (!roomID) {
			throw new OperationError(500, "roomID is required")
		}

		this.roomID = `room:${roomID}`
		this.roomSocket = this.io.to(`room:${this.roomID}`)
		this.options = {
			owner_user_id: null,
			...options,
		}
	}

	connections = new Set()

	limitations = {
		maxMessageLength: 540,
	}

	roomEvents = {
		"room:change:owner": async (client, payload) => {
			throw new OperationError(500, "Not implemented")
		},
		"room:send:message": async (client, payload) => {
			console.log(
				`[${this.roomID}] [@${client.userData.username}] sent message >`,
				payload,
			)

			let { message } = payload

			if (!message || typeof message !== "string") {
				throw new Error("Invalid message")
			}

			if (message.length > this.limitations.maxMessageLength) {
				message = message.substring(
					0,
					this.limitations.maxMessageLength,
				)
			}

			const created_at = new Date().getTime()

			const id = `msg:${client.userData._id}:${created_at}`

			this.handlers.broadcastToMembers("room:message", {
				_id: id,
				timestamp: payload.timestamp ?? Date.now(),
				content: String(message),
				user: {
					user_id: client.userData._id,
					username: client.userData.username,
					fullName: client.userData.fullName,
					avatar: client.userData.avatar,
				},
			})

			if (payload.route) {
				const routeValues = payload.route.split(":")

				console.log(routeValues)

				if (routeValues.length > 0) {
					const [type, to_id] = routeValues

					switch (type) {
						case "user": {
							const doc = await ChatMessage.create({
								type: type,
								from_user_id: client.userData._id,
								to_user_id: to_id,
								content: message,
								created_at: created_at,
							})

							console.log(doc)
						}

						default:
							break
					}
				}
			}
		},
	}

	handlers = {
		join: (client) => {
			if (!client.userData) {
				return false
			}

			if (client.connectedRoomID) {
				console.warn(
					`[${client.id}][@${client.userData.username}] already connected to room ${client.connectedRoomID}`,
				)

				client.leave(client.connectedRoomID)
			}

			console.log(
				`[${client.id}][@${client.userData.username}] joined room ${this.roomID}`,
			)

			client.connectedRoomID = this.roomID

			this.connections.add(client)

			for (let [event, handler] of Object.entries(this.roomEvents)) {
				handler = buildFunctionHandler(handler, client)

				if (!Array.isArray(client.handlers)) {
					client.handlers = []
				}

				client.handlers.push([event, handler])

				client.on(event, handler)
			}

			// emit to self
			client.emit("room:joined", {
				roomID: this.roomID,
				limitations: this.limitations,
				connectedUsers: this.getConnectedUsers(),
			})

			// emit to others
			this.roomSocket.emit("room:user:joined", {
				user: {
					user_id: client.userData._id,
					username: client.userData.username,
					fullName: client.userData.fullName,
					avatar: client.userData.avatar,
				},
			})
		},
		leave: (client) => {
			if (!client.connectedRoomID) {
				console.warn(
					`[${client.id}][@${client.userData.username}] not connected to any room`,
				)
				return
			}

			if (client.connectedRoomID !== this.roomID) {
				console.warn(
					`[${client.id}][@${client.userData.username}] not connected to room ${this.roomID}, cannot leave`,
				)
				return false
			}

			this.connections.delete(client)

			client.emit("room:left", {
				room: this.roomID,
			})

			this.roomSocket.emit("room:user:left", {
				user: {
					user_id: client.userData._id,
					username: client.userData.username,
					fullName: client.userData.fullName,
					avatar: client.userData.avatar,
				},
			})

			for (const [event, handler] of client.handlers) {
				client.off(event, handler)
			}

			console.log(
				`[${client.id}][@${client.userData.username}] left room ${this.roomID}`,
			)
		},
		broadcastToMembers: (event, payload) => {
			for (const client of this.connections) {
				client.emit(event, payload)
			}
		},
	}

	getConnectedUsers = () => {
		let users = {}

		for (const client of this.connections) {
			users[client.userData._id] = client.userData
		}

		return users
	}
}
