import { Request, Response } from 'express'
import { Types } from 'mongoose'
import { IUser } from '../users/models'
import { User } from '../users/models'
import { SmartMatch } from './models'
import { Chat } from '../chats/models'
import { Message } from '../messages/models'
import { Notification } from '../notifications/models'
import helpers from '../../utils/helpers'
import services from '../../utils/services'
import smartMatchServices from './services'
import constants from '../../utils/constants'

async function getSmartMatches(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		const user = res.locals.user as IUser

		const matches = await SmartMatch.find({
			$or: [{ 'userA.user': user._id }, { 'userB.user': user._id }],
			suggested: true,
			status: { $in: ['pending', 'accepted'] },
		})
			.populate('userA.user', 'name username photo bio')
			.populate('userB.user', 'name username photo bio')

		return res.json({ smartMatches: matches })
	})
}

async function getSmartMatch(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		const user = res.locals.user as IUser
		const matchId = new Types.ObjectId(req.params.id)

		const match = await SmartMatch.findOne({
			_id: matchId,
			$or: [{ 'userA.user': user._id }, { 'userB.user': user._id }],
			suggested: true,
			status: { $in: ['pending', 'accepted'] },
		})
			.populate('userA.user', 'name username photo bio')
			.populate('userB.user', 'name username photo bio')

		if (!match) {
			return res.status(404).json({ message: 'smart match not found' })
		}

		return res.json({ smartMatch: match })
	})
}

async function postRejectSmartMatch(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		const user = res.locals.user as IUser
		const matchId = new Types.ObjectId(req.params.id)

		const match = await SmartMatch.findOne({
			_id: matchId,
			$or: [{ 'userA.user': user._id }, { 'userB.user': user._id }],
			status: 'pending',
		})

		if (!match) {
			return res.status(404).json({ message: 'smart match not found' })
		}

		const isUserA = match.userA.user.equals(user._id)
		const userKey = isUserA ? 'userA' : 'userB'
		const otherUserId = isUserA ? match.userB.user : match.userA.user

		match[userKey].response = 'no'
		match[userKey].responseAt = new Date()
		match.status = 'declined'
		match.declinedAt = new Date()
		await match.save()

		services.socketNotifyUser({ event: 'smart-match-declined', userId: `${user._id}`, data: { smartMatch: `${match._id}` } })
		services.socketNotifyUser({ event: 'smart-match-declined', userId: `${otherUserId}`, data: { smartMatch: `${match._id}` } })

		return res.json({ message: 'smart match rejected' })
	})
}

async function postAcceptSmartMatch(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		const user = res.locals.user as IUser
		const matchId = new Types.ObjectId(req.params.id)

		const match = await SmartMatch.findOne({
			_id: matchId,
			$or: [{ 'userA.user': user._id }, { 'userB.user': user._id }],
			status: 'pending',
		})

		if (!match) {
			return res.status(404).json({ message: 'smart match not found' })
		}

		const isUserA = match.userA.user.equals(user._id)
		const userKey = isUserA ? 'userA' : 'userB'
		const otherUserId = isUserA ? match.userB.user : match.userA.user
		const otherUserKey = isUserA ? 'userB' : 'userA'
		const otherHasAccepted = match[otherUserKey].response === 'yes'

		match[userKey].response = 'yes'
		match[userKey].responseAt = new Date()

		const bothAccepted = otherHasAccepted

		if (bothAccepted) {
			match.status = 'accepted'
			match.acceptedAt = new Date()
		}

		await match.save()

		if(!bothAccepted) {
			services.socketNotifyUser({ event: 'smart-match-accepted', userId: `${user._id}`, data: { smartMatch: `${match._id}` } })
			services.socketNotifyUser({ event: 'smart-match-accepted', userId: `${otherUserId}`, data: { smartMatch: `${match._id}` } })
		}

		// push notify other user
		helpers.runInBackground(async () => {
			const otherUser = await User.findById(otherUserId)
			if (!otherUser) return
			await services.pushNotifyUser({
				user: otherUser as unknown as IUser,
				title: 'Your Vibe Match accepted! ✦',
				body: `${user.username} accepted the smart match`,
				event: 'smart-match-accepted',
				data: { smartMatch: `${match._id}` },
			})
		})

		helpers.runInBackground(async () => {
			if (!bothAccepted) {
				return
			}
			// find or create the one-to-one chat
			const participantIds = [`${match.userA.user}`, `${match.userB.user}`]
			let chat = await Chat.findOne({
				$and: [
					{
						$or: [
							{ creator: match.userA.user, user: match.userB.user },
							{ creator: match.userB.user, user: match.userA.user },
						]
					},
					{ $or: [{ isGroup: false }, { isGroup: { $exists: false } }] },
				],
			})

			let isNewChat = false
			if (chat) {
				if (!chat.isVibeon) {
					chat.isVibeon = true
					await chat.save()
				}
			} else {
				chat = await Chat.create({
					creator: match.userA.user,
					user: match.userB.user,
					isVibeon: true,
					community: match.community,
					communities: [match.community],
					participants: [
						{ user: match.userA.user },
						{ user: match.userB.user },
					],
				})
				isNewChat = true
			}

			// link chat to smart match
			match.chat = chat._id
			await match.save()

			// socket notify both participants: chat-created (only for new chats) or chat-updated
			const chatEvent = isNewChat ? 'chat-created' : 'chat-updated'
			for (const pid of participantIds) {
				const isCurrentUser = pid === `${user._id}`
				services.socketNotifyUser({
          event: chatEvent,
          userId: pid,
          data: {
            chat: `${chat._id}`,
            ...(isCurrentUser ? { routeToChatPage: true } : {}),
          },
        })
			}

			// helper to notify all participants about a new message
			const notifyMessage = (msg: any) => {
				const payload = {
					message: {
						_id: `${msg._id}`,
						chat: `${chat._id}`,
						isGroup: false,
						content: msg.content,
						isVibeon: msg.isVibeon,
						isConversation: msg.isConversation,
						system: msg.system,
						at: msg.at.toISOString(),
					}
				}
				for (const pid of participantIds) {
					services.socketNotifyUser({ event: 'message-received', userId: pid, data: payload })
				}
			}

			// Fetch user data for personalized intro message
			const userA = await User.findById(match.userA.user)
			const userB = await User.findById(match.userB.user)

			// message 1: personalized vibeon intro message based on common traits
			let introContent = 'Hey there! 👋 Please meet each other.'
			if (userA && userB) {
				introContent = smartMatchServices.generateSmartMatchIntroMessage(userA, userB)
			}
			const msg1 = await Message.create({
				chat: chat._id,
				content: helpers.encryptAes(introContent),
				isVibeon: true,
			})
			msg1.content = introContent
			notifyMessage(msg1)

			// message 2: vibeon conversation message
			let conversationContent = match.conversationStarter || "What's a topic you could talk about for hours without getting bored?"
			const msg2 = await Message.create({
				chat: chat._id,
				content: helpers.encryptAes(conversationContent),
				isVibeon: true,
				isConversation: true,
			})
			msg2.content = conversationContent
			notifyMessage(msg2)

			// message 3: "I'll leave it to you", by vibeon
			conversationContent = "I'll leave it to you"
			const msg3 = await Message.create({
				chat: chat._id,
				content: helpers.encryptAes(conversationContent),
				isVibeon: true,
			})
			msg3.content = conversationContent
			notifyMessage(msg3)

			// message 4: system message — chat-access-removed
			const msg4 = await Message.create({
				chat: chat._id,
				content: helpers.encryptAes('Vibeon left the chat'),
				system: 'chat-access-removed',
			})
			msg4.content = 'Vibeon left the chat'
			// @ts-ignore
			chat.lastMessage = msg4._id
			chat.messages = await Message.countDocuments({ chat: chat._id })
			await chat.save()
			notifyMessage(msg4)

			// socket notify both users
			services.socketNotifyUser({ event: 'smart-match-accepted', userId: `${user._id}`, data: { smartMatch: `${match._id}`, chat: `${chat._id}` } })
			services.socketNotifyUser({ event: 'smart-match-accepted', userId: `${otherUserId}`, data: { smartMatch: `${match._id}`, chat: `${chat._id}` } })

			// schedule smart-match-feedback after 36 hours
			const feedbackAt = new Date(chat.createdAt.getTime() + constants.maxSmartMatchFeedbackHours * 60 * 60 * 1000)
			await services.createScheduler('smart-match-feedback', feedbackAt, {
				smartMatch: `${match._id}`,
				chat: `${chat._id}`,
				userA: `${match.userA.user}`,
				userB: `${match.userB.user}`,
				community: `${match.community}`,
			})
		})

		return res.json({ message: 'smart match accepted' })
	})
}

async function postFeedbackSmartMatch(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		const user = res.locals.user as IUser
		const matchId = new Types.ObjectId(req.params.id)
		const { feedback } = req.body as { feedback: 'good' | 'ok' | 'bad' }

		const match = await SmartMatch.findOne({
			_id: matchId,
			$or: [{ 'userA.user': user._id }, { 'userB.user': user._id }],
			status: 'accepted',
		})

		if (!match) {
			return res.status(404).json({ message: 'smart match not found' })
		}

		const userKey = match.userA.user.equals(user._id) ? 'userA' : 'userB'

		if (match[userKey].feedback) {
			return res.status(409).json({ message: 'feedback already submitted' })
		}

		match[userKey].feedback = feedback
		match[userKey].feedbackAt = new Date()
		await match.save()

		// delete the smart-match-feedback notification and create smart-match-feedback-submitted
		helpers.runInBackground(async () => {
			await Notification.deleteOne({
				user: user._id,
				smartMatch: match._id,
				event: 'smart-match-feedback',
			})
			await services.notifyUser({
				user,
				title: 'Thanks for your feedback! ✦',
				description: 'Your feedback helps us improve your Vibe Match experience.',
				event: 'smart-match-feedback-submitted',
				smartMatch: match._id,
			})
		})

		// update user's smartMatch weight for the dominant score dimension
		helpers.runInBackground(async () => {
			const scores = match.scores
			if (!scores) return

			const dims = ['energy', 'values', 'activity', 'intent'] as const
			const dominant = dims.reduce((best, dim) => scores[dim] > scores[best] ? dim : best, dims[0])

			const delta = feedback === 'bad' ? -0.1 : 0.1
			await User.updateOne(
				{ _id: user._id },
				{ $inc: { [`smartMatch.weights.${dominant}`]: delta } }
			)
		})

		return res.json({ message: 'feedback submitted' })
	})
}

async function postCreateSmartMatches(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		
		await smartMatchServices.createSmartMatches()
		await smartMatchServices.suggestSmartMatches()

		return res.json({ message: 'done' })
	})
}

const smartMatchControllers = {
	getSmartMatches,
	getSmartMatch,
	postRejectSmartMatch,
	postAcceptSmartMatch,
	postFeedbackSmartMatch,
	postCreateSmartMatches
}

export default smartMatchControllers
