import { HttpException, HttpStatus, Injectable, Res } from '@nestjs/common';
import { User } from 'src/users/entities/user.entity';
import { UsersService } from 'src/users/users.service';
import { Chatroom } from './entities/chatroom.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { roomDto } from './dto/room.dto';
import { messagesDto } from './dto/messages.dto';
import { socketDto } from './dto/socket.dto';


@Injectable()
export class ChatService {

	constructor(
		private usersService: UsersService,
		@InjectRepository(User)
		private readonly userRepository: Repository<User>,
		@InjectRepository(Chatroom)
		private readonly chatroomRepository: Repository<Chatroom>,
	) {}

	// temp for test
	sleep(ms) {
		return new Promise(resolve => setTimeout(resolve, ms));
	}


	/* GETTERS ************************************************
	*/

	async getMyRooms(username: string, fieldsToReturn: string[] = null): Promise<Chatroom[]>
	{
		console.log("-- in getMyRooms service");

		const queryBuilder = this.chatroomRepository
			.createQueryBuilder('chatroom')
			.where('chatroom.users LIKE :user_name', { user_name: `%${username}%` });

		if (fieldsToReturn)
		{
			let fields = fieldsToReturn.map(field => `chatroom.${field}`);
			queryBuilder.select(fields);
		}

		const rooms = await queryBuilder.getMany();
		console.log("--- rooms:", rooms);

		console.log("-- out getMyRooms service");
		return rooms;
	}

	async getMyDirects(username: string): Promise<Chatroom[]>
	{
		console.log("-- in getAllNotMyRooms service");

		const my_rooms = await this.getMyRooms(username);
		const directs = my_rooms.filter(room => room.type === 'direct');

		console.log("-- out getAllNotMyRooms service");
		return directs;
	}

	async getAllRooms(): Promise<Chatroom[]>
	{
		console.log("-- in getAllRooms service");

		const rooms = await this.chatroomRepository
			.createQueryBuilder('chatroom')
			.getMany();
		console.log("--- rooms:", rooms);

		console.log("-- out getAllRooms service");
		return rooms;
	}

	async getAllNotMyRooms(username: string): Promise<Chatroom[]>
	{
		console.log("-- in getAllNotMyRooms service");

		const user_db = await this.getUserByName(username);
		const rooms = await this.chatroomRepository
			.createQueryBuilder('chatroom')
			.where('chatroom.type != :type', { type: 'private' })
			.andWhere('chatroom.users NOT LIKE :user_name', { user_name: `%${username}%` })
			.getMany();
		console.log("--- rooms:", rooms);

		console.log("-- out getAllNotMyRooms service");
		return rooms;
	}

	async getAllOtherRoomsAndUsers(username: string): Promise<roomDto[]>
	{
		console.log("-- in getAllOtherRoomsAndUsers service");

		const all_rooms = await this.getAllNotMyRooms(username);
		const all_users = await this.getAllUsersNotMyRooms(username);

		let row_rooms = all_rooms.map(room => {
			return {
				name: room.name,
				type: room.type,
			};
		});
		let users = all_users.map(user => {
			return {
				name: user.username,
				type: "user",
			};
		});
		let rooms = row_rooms.concat(users);
		console.log("--- rooms:", rooms);

		console.log("-- in getAllOtherRoomsAndUsers service");
		return rooms;
	}

	async getMessagesFromCurrentRoom(username: string): Promise<messagesDto[]>
	{
		console.log("-- in getMessagesFromCurrentRoom service");

		const user_db = await this.getUserByName(username);
		const currentRoom = await this.getRoomByName(user_db.currentRoom);
		let messages = null;
		if (currentRoom)
			messages = currentRoom.messages;

		console.log("-- out getMessagesFromCurrentRoom service");
		return messages;
	}

	async getCurrentRoomName(username: string): Promise<string>
	{
		console.log("-- in getCurrentRoomName service");

		const user_db = await this.getUserByName(username);

		console.log("-- out getCurrentRoomName service");
		return user_db.currentRoom;
	}

	async getRoomByName(room_name: string): Promise<Chatroom>
	{
		console.log("-- in getRoomByName service");

		const room = await this.chatroomRepository
			.createQueryBuilder('chatroom')
			.where('chatroom.name = :name', { name: room_name })
			.getOne();
		console.log("--- room:", room);

		console.log("-- out getRoomByName service");
		return room;
	}

	async getRoomById(id: number): Promise<Chatroom>
	{
		console.log("-- in getRoomById service");

		const room = await this.chatroomRepository
			.createQueryBuilder('chatroom')
			.where('chatroom.id = :id', { id: id })
			.getOne();
		console.log("--- room:", room);

		console.log("-- out getRoomById service");
		return room;
	}


	/* SETTERS ************************************************
	*/

	async setCurrentRoom(username: string, room_name: string): Promise<string>
	{
		console.log("-- in setCurrentRoom service");

		const user_db = await this.getUserByName(username);
		user_db.currentRoom = room_name;
		this.userRepository.save(user_db);
		
		console.log("-- out setCurrentRoom service");
		return `room "${room_name}" is now current room`;
	}


	/* ADDERS *************************************************
	*/

	async addUserToNewRoom(username: string, room: roomDto): Promise<void>
	{
		console.log("-- in addUserToNewRoom service");

		const find_room = await this.getRoomByName(room.name);
		if (find_room)
			throw new HttpException(`This room name already exist`, HttpStatus.CONFLICT);

		// create chatroom
		const newChatroom = new Chatroom();
		newChatroom.name = room.name;
		newChatroom.type = room.type;
		newChatroom.owner = username;
		newChatroom.users = [username];
		if (room.type === 'direct')
			newChatroom.users = room.users;
		newChatroom.messages = [{ name: "SERVER", message: `creation of room ${room.name}` }];
		await this.chatroomRepository.save(newChatroom);

		console.log("-- out addUserToNewRoom service");
	}

	async addUserToRoom(username: string, room_name: string): Promise<void>
	{
		console.log("-- in addUserToRoom service");

		const room = await this.getRoomByName(room_name);
		if (room.users.includes(username))
			throw new HttpException(`your have already joined this room`, HttpStatus.CONFLICT);

		// update room with new user
		room.users.push(username);
		this.chatroomRepository.save(room);

		console.log("-- out addUserToRoom service");
	}

	async addMessageToRoom(room_name: string, username: string, message: string): Promise<void>
	{
		console.log("-- in addMessageToRoom service");

		const my_room = await this.getRoomByName(room_name);
		let chat_message = {
			name: username,
			message: message,
		};
		my_room.messages.push(chat_message);
		this.chatroomRepository.save(my_room);

		console.log("-- out addMessageToRoom service");
	}


	/* REMOVERS ***********************************************
	*/

	async removeUserFromRoom(username: string, room_name: string): Promise<string>
	{
		console.log("-- in removeUserFromRoom service");

		const room = await this.getRoomByName(room_name);
		if (!room.users.includes(username))
			throw new HttpException(`your are not in this room`, HttpStatus.CONFLICT);

		// delete user from room
		room.users.push(username);
		room.users = room.users.filter(name => name !== username);
		this.chatroomRepository.save(room);

		// set current room to nothing
		await this.setCurrentRoom(username, "");

		console.log("-- out removeUserFromRoom service");
		return "successfully leaving room";
	}


	/* SEARCH IN USER *****************************************
	*/

	async getUserByName(username: string): Promise<User>
	{
		console.log("-- in getUserByName service");

		const user = await this.userRepository
			.createQueryBuilder('user')
			.where('user.username = :name', { name: username })
			.getOne();
		console.log("--- user:", user);

		console.log("-- out getUserByName service");
		return user;
	}

	async getAllUsersNotMyRooms(username: string): Promise<User[]>
	{
		console.log("-- in getAllUsersNotMyRooms service");

		const directs = await this.getMyDirects(username);

		// get all users from directs
		let usernames = directs.map(room => {
			let user = room.users[0];
			if (user === username)
				user = room.users[1];
			return user;
		});
		usernames.push(username);

		const users = await this.userRepository
			.createQueryBuilder('user')
			.where('user.username NOT IN (:...usernames)', { usernames: usernames })
			.getMany();
		console.log("--- users:", users);

		console.log("-- out getAllUsersNotMyRooms service");
		return users;
	}


	/* GATEWAY EVENTS *****************************************
	*/

	async socketIncommingMessage(socket: socketDto, message: string): Promise<void>
	{
		console.log("-- in handleSocketIncommingMessage service");

		socket.to(socket.room).emit('message', socket.username, message);
		let room_name = await this.getCurrentRoomName(socket.username);
		await this.addMessageToRoom(room_name, socket.username, message);

		console.log("-- out handleSocketIncommingMessage service");
	}

	async socketChangeRoom(socket: socketDto, room_name: string): Promise<void>
	{
		console.log('-- in socketChangeRoom service');

		socket.leave(socket.room);
		socket.join(room_name);
		socket.room = room_name;

		console.log('-- out socketChangeRoom service');
	}

	async socketJoinRoom(socket: socketDto, room_name: string): Promise<void>
	{
		console.log('- in socketJoinRoom service');

		socket.leave(socket.room);
		socket.join(room_name);
		socket.room = room_name;
		let message = `${socket.username} has join the room`;
		await socket.to(socket.room).emit('message', "SERVER", message);
		await this.addMessageToRoom(room_name, "SERVER", message);

		console.log('- out socketJoinRoom service');
	}

}

