import { Request as IRequest, Response } from 'express'
import { IUser, User } from '../users/models'
import { Request } from './models'
import { Notification } from '../notifications/models'
import { Chat, IChat, IChatParticipant } from '../chats/models'
import services from '../../utils/services'
import { IScore } from '../scores/models'

async function getRequests(req: IRequest, res: Response) {
	interface Payload {
		limit?: string | number
		page?: string | number
		status?: 'pending' | 'accepted' | 'rejected'
	}

	try {
		const user = res.locals.user as IUser
		let { limit = '10', page = '1', status } = req.query as unknown as Payload

		limit = +limit
		page = +page
		const skip = (page - 1) * limit

		const query: any = {
			user: user._id,
			...(status ? { status } : {})
		}

		let requests = await Request.find(query)
			.sort({ createdAt: -1 })
			.skip(skip)
			.limit(limit)
			.populate({
				path: 'user',
				select: { name: 1, username: 1, bio: 1, photo: 1 }
			})
			.populate('chat')
		const count = await Request.countDocuments(query)
		// @ts-ignore
		requests = requests.map(e => e.toJSON())

		for(const request of requests) {
			const chat = request.chat as IChat
			const p = chat.participants.find(e => `${e.user}` === `${user._id}`)
			// @ts-ignore
			chat.areYouAdmin = !!p?.isAdmin
		}

		return res.json({ requests, count })
	} catch (error) {
		console.error(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function getRequest(req: IRequest, res: Response) {
	try {
		const user = res.locals.user as IUser
		const id = req.params.id

		let request = await Request.findOne({ _id: id, user: user._id })
			.populate({
				path: 'user',
				select: { name: 1, username: 1, bio: 1, photo: 1 }
			})
			.populate('chat')
		if (!request) {
			return res.status(404).json({ message: 'request not found' })
		}
		// @ts-ignore
		request = request.toJSON()
		const chat = request!.chat as IChat
		const p = chat.participants.find(e => `${e.user}` === `${user._id}`)
		// @ts-ignore
		chat.areYouAdmin = !!p?.isAdmin

		return res.json({ request })
	} catch (error) {
		console.error(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postRequest(req: IRequest, res: Response) {
	try {
		const user = res.locals.user as IUser
		const chatId = req.body.chat as string

		const chat = await Chat.findById(chatId)
		if (!chat) {
			return res.status(404).json({ message: 'chat not found' })
		}
		if (!chat.restricted) {
			return res.status(403).json({ message: 'only allowed for restricted chat' })
		}
		if (!chat.isGroup) {
			return res.status(403).json({ message: 'only allowed for group chats' })
		}
		const participant = chat.participants.find(e => `${e.user}` === `${user._id}`)
		if (participant) {
			return res.status(409).json({ message: 'you already exist in this chat as a participant' })
		}

		let request = await Request.findOne({
			user: user._id,
			status: 'pending',
			chat: chat._id
		})
		if (request) {
			return res.status(409).json({ message: 'a pending request for this chat already exist' })
		}

		request = await Request.create({
			user: user._id,
			chat: chat._id,
		});

		(async () => {
			try {
				const adminIds = chat.participants.filter(e => e.isAdmin).map(e => `${e.user}`)
				const admins = await User.find({ _id: { $in: adminIds } })
				for (const adminId of adminIds) {
					const admin = admins.find(e => `${e._id}` === adminId)
					if (!admin) {
						continue
					}
					await services.notifyUser({
						title: 'A user wants to join the chat',
						description: `${user.username} wants to join ${chat.name || 'your chat'}`,
						event: 'chat-join-request',
						user: admin as IUser,
						from: user._id,
						chat: chat._id,
						request: request._id
					})
				}
			} catch (error) {
				console.log(error)
			}
		})()

		return res.status(201).json({ request })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function patchRequest(req: IRequest, res: Response) {
	try {
		const user = res.locals.user as IUser
		const id = req.params.id as string
		const status = req.body.status as 'accepted' | 'rejected'

		const request = await Request.findById(id).populate('chat').populate('user')
		if (!request) {
			return res.status(404).json({ message: 'request not found' })
		}

		const chat = request.chat as unknown as IChat
		const participant = chat.participants.find(e => `${e.user}` === `${user._id}`)
		if (!participant) {
			return res.status(404).json({ message: 'request not found' })
		}
		if (!participant.isAdmin) {
			return res.status(403).json({ message: 'admin permission is required' })
		}

		if (request.status !== 'pending') {
			return res.status(403).json({ message: 'Request status must be pending' })
		}

		request.status = status
		if (status === 'accepted') {
			request.acceptedAt = new Date()
		} else {
			request.rejectedAt = new Date()
		}
		await request.save()

		if (status === 'accepted') {
			chat.participants.push({ user: (request.user as any)._id } as IChatParticipant)
			await chat.save()
		}

		; (async () => {
			try {
				// mark the notification as dismissed
				const notification = await Notification.findOne({
					request: request._id,
					event: 'chat-join-request'
				})
				if (!notification) {
					return
				}
				notification.dismissed = true
				await notification.save()

				// notify the user, in case of accepted
				if (status === 'accepted') {
					await services.notifyUser({
						title: 'Your join request has been accepted',
						description: `You are now a member of ${chat.name || 'the chat'}`,
						event: 'chat-request-accepted',
						user: request.user as IUser,
						from: user._id,
						chat: chat._id,
						request: request._id
					})
				}
			} catch (error) {
				console.log(error)
			}
		})();

		(async () => {
			try {
				if (status !== 'accepted') {
					return
				}
				const score = await services.getOrCreateTodayScore((request.user as any)._id)
				if (!score) {
					return
				}
				const action = score.social.actions.find(e => e.type === 'join-group-chat')
				if(!action || action.completed) {
					return
				}
				action.value = 1
				action.completed = true
				await services.updateScore(score as unknown as IScore)
			} catch (error) {
				console.log(error)
			}
		})()

		return res.json({ message: `request ${status} successfully` })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

const controllers = {
	getRequests,
	getRequest,
	postRequest,
	patchRequest
}

export default controllers
