import { Request, Response } from 'express'
import { ILoggedinDevice, IPurpose, IStudy, IUser, User } from './models'
import helpers from '../../utils/helpers'
import { Follower } from '../followers/models'
import { Connection } from '../connections/models'
import { Status } from '../statuses/models'
import services from '../../utils/services'
import { Chat } from '../chats/models'
import { SuperLike } from '../super-likes/models'
import constants from '../../utils/constants'

async function getUsers(req: Request, res: Response) {
	interface Payload {
		limit?: string | number
		page?: string | number
		search?: string
		categories?: string | string[]
	}

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

		limit = +limit
		page = +page

		const matchStage: any = {
			_id: { $ne: u._id },
			name: { $exists: true },
			$and: [
				{
					$or: [
						{ terminated: false },
						{ terminated: { $exists: false } }
					]
				}
			]
		}

		if (search) {
			matchStage.$and.push({
				$or: [
					{ name: RegExp(search, 'i') },
					{ username: RegExp(search, 'i') },
				]
			})
		}

		if (categories) {
			const categoryArray = (categories as string).split(',')
			matchStage.categories = { $in: categoryArray }
		}

		const pipeline: any[] = [
			{ $match: matchStage },
			{
				// Lookup connection between current user and this user
				$lookup: {
					from: 'connections',
					let: { otherUserId: '$_id' },
					pipeline: [
						{
							$match: {
								$expr: {
									$or: [
										{ $and: [{ $eq: ['$from', u._id] }, { $eq: ['$to', '$$otherUserId'] }] },
										{ $and: [{ $eq: ['$to', u._id] }, { $eq: ['$from', '$$otherUserId'] }] }
									]
								}
							}
						},
						{ $limit: 1 }
					],
					as: 'connection'
				}
			},
			{ $unwind: { path: '$connection', preserveNullAndEmptyArrays: true } },
			{
				$project: {
					name: 1,
					username: 1,
					bio: 1,
					auth: {
						emailVerified: 1,
						phoneVerified: 1,
					},
					photo: 1,
					createdAt: 1,
					updatedAt: 1,
					connection: 1,
					verified: 1,
				}
			},
			{ $sort: { createdAt: -1 } },
			{
				$facet: {
					users: [
						{ $skip: (page - 1) * limit },
						{ $limit: limit }
					],
					totalCount: [
						{ $count: 'count' }
					]
				}
			}
		]

		const result = await User.aggregate(pipeline)
		const users = result[0]?.users || []
		const count = result[0]?.totalCount[0]?.count || 0

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

// explore search
async function getExploreUsersAndChats(req: Request, res: Response) {
	interface Payload {
		limit?: string | number
		search?: string
	}

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

		limit = +limit

		let pipeline: any[] = [
			{
				$match: {
					_id: { $ne: u._id },
					name: { $exists: true },
					$and: [
						{
							$or: [
								{ terminated: false },
								{ terminated: { $exists: false } }
							]
						},
						{
							$or: [
								{ name: RegExp(search, 'i') },
								{ username: RegExp(search, 'i') },
							]
						}
					]
				}
			},
			{
				// Lookup connection between current user and this user
				$lookup: {
					from: 'connections',
					let: { otherUserId: '$_id' },
					pipeline: [
						{
							$match: {
								$expr: {
									$or: [
										{ $and: [{ $eq: ['$from', u._id] }, { $eq: ['$to', '$$otherUserId'] }] },
										{ $and: [{ $eq: ['$to', u._id] }, { $eq: ['$from', '$$otherUserId'] }] }
									]
								}
							}
						},
						{ $limit: 1 }
					],
					as: 'connection'
				}
			},
			{ $unwind: { path: '$connection', preserveNullAndEmptyArrays: true } },
			{
				$project: {
					name: 1,
					username: 1,
					bio: 1,
					auth: {
						emailVerified: 1,
						phoneVerified: 1,
					},
					photo: 1,
					createdAt: 1,
					updatedAt: 1,
					connection: 1,
					verified: 1,
				}
			},
			{ $sort: { followers: -1 } },
			{ $limit: limit }
		]
		const users = await User.aggregate(pipeline)

		const chats = await Chat.aggregate([
			{
				$match: {
					name: { $regex: search, $options: 'i' },
					isGroup: true
				}
			},
			// populate request
			{
				$lookup: {
					from: 'requests',
					let: { chatId: '$_id', userId: u._id },
					pipeline: [
						{
							$match: {
								$expr: {
									$and: [
										{ $eq: ['$chat', '$$chatId'] },
										{ $eq: ['$user', '$$userId'] },
										{ $eq: ['$status', 'pending'] },
									]
								}
							}
						},
						{ $limit: 1 }
					],
					as: 'request'
				}
			},
			{ $unwind: { path: '$request', preserveNullAndEmptyArrays: true } },
			{
				$addFields: {
					participantsCount: { $size: '$participants' }
				}
			},
			{
				$project: {
					isGroup: 1,
					name: 1,
					event: 1,
					photo: 1,
					allowPublicPost: 1,
					restricted: 1,
					request: 1,
					participants: 1,
					participantsCount: 1,
					createdAt: 1,
					updatedAt: 1
				}
			},
			{
				$sort: { participantsCount: -1 }
			},
			{
				$limit: limit
			}
		])
		for (const chat of chats) {
			const p = chat.participants.find((e: any) => `${e.user}` === `${u._id}`)
			chat.isJoined = !!p
			chat.areYouAdmin = !!p?.isAdmin
		}

		return res.json({ users, chats })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

// meetme online users
async function getMeetmeOnlineUsers(req: Request, res: Response) {
	interface Payload {
		limit?: string | number
		page?: string | number
	}

	try {
		const u = res.locals.user as IUser
		let { limit = '10', page = '1' } = req.query as unknown as Payload
		limit = +limit
		page = +page
		const skip = (page - 1) * limit
		const onlineMinutesAgo = new Date(Date.now() - constants.maxOnlineVisibilityInMinutes * 60 * 1000)

		const match: Record<string, any> = {
			_id: { $ne: u._id },
			name: { $exists: true },
			$and: [
				{
					$or: [
						{ terminated: false },
						{ terminated: { $exists: false } }
					],
				},
				{
					$or: [
						{ isOnline: true },
						{
							isOnline: { $ne: true },
							lastOnlineAt: { $gte: onlineMinutesAgo }
						}
					],
				}
			]
		}
		if (constants.locationMode && u.location) {
			match.$and.push({
				$or: [
					{
						location: {
							$geoWithin: {
								$centerSphere: [
									u.location.coordinates, // [lng, lat]
									constants.locationDistance / constants.earthRadius // meters → radians
								]
							}
						}
					},
					{ location: { $exists: false } }
				]
			})
		}

		const result = await User.aggregate([
			// STEP 1: online filter
			{ $match: match },

			// STEP 2: ensure mutual follow exists
			{
				$lookup: {
					from: 'mutual-follows',
					let: { uid: '$_id' },
					pipeline: [
						{
							$match: {
								$expr: {
									$and: [
										{ $in: [u._id, '$users'] },
										{ $in: ['$$uid', '$users'] }
									]
								}
							}
						}
					],
					as: 'mutual'
				}
			},
			{
				$match: {
					'mutual.0': { $exists: true } // must be mutual
				}
			},

			// STEP 3: FACET → apply count + pagination + chat lookup
			{
				$facet: {
					// total count
					metadata: [
						{ $count: 'total' }
					],

					users: [
						// pagination
						{ $skip: skip },
						{ $limit: limit },

						// chat lookup
						{
							$lookup: {
								from: 'chats',
								let: { uid: '$_id' },
								pipeline: [
									{
										$match: {
											isGroup: { $ne: true },
											$expr: {
												$and: [
													{ $in: [u._id, '$participants.user'] },
													{ $in: ['$$uid', '$participants.user'] }
												]
											}
										}
									},
									{ $limit: 1 }
								],
								as: 'chat'
							}
						},
						{
							$addFields: {
								chat: { $arrayElemAt: ['$chat', 0] }
							}
						},

						// projection
						{
							$project: {
								name: 1,
								username: 1,
								photo: 1,
								isOnline: 1,
								lastOnlineAt: 1,
								chat: 1
							}
						},

						// sort users by recent online activity
						{ $sort: { lastOnlineAt: -1 } }
					]
				}
			},

			// STEP 4: flatten metadata
			{
				$addFields: {
					total: { $ifNull: [{ $arrayElemAt: ['$metadata.total', 0] }, 0] }
				}
			},

			{
				$project: {
					metadata: 0
				}
			}
		])
		const { total: count, users } = result[0] as unknown as { total: number, users: IUser[] }

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

async function getProfile(req: Request, res: Response) {
	try {
		const user = res.locals.user as IUser

		if (user.isManuallyOffline) {
			user.isOnline = false
		} else if (!user.isOnline && user.lastOnlineAt) {
			user.lastOnlineAt.setMinutes(user.lastOnlineAt.getMinutes() + constants.maxOnlineVisibilityInMinutes)
			if (user.lastOnlineAt >= new Date()) {
				user.isOnline = true
			}
		}

		if (user.status) {
			const status = await Status.findById(user.status)
			if (status && status.endedAt < new Date()) {
				user.status = undefined
				await user.save()
			} else if (status) {
				// @ts-ignore
				user.status = status
			}
		}

		// @ts-ignore
		user.auth = {
			twoFactorAuthentication: user.auth.twoFactorAuthentication,
			emailVerified: user.auth.emailVerified,
			phoneVerified: user.auth.phoneVerified,
		}

		const lastDay = new Date()
		lastDay.setHours(lastDay.getHours() - 24)
		const count = await SuperLike.countDocuments({
			user: user._id,
			at: { $gt: lastDay }
		})
		// @ts-ignore
		const doc = user._doc
		doc.isSuperLikeAvailable = count < 1

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

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

		const user = await User.findById(id, {
			name: 1,
			username: 1,
			bio: 1,
			url: 1,
			school: 1,
			courses: 1,
			email: 1,
			phone: 1,
			dob: 1,
			auth: {
				emailVerified: 1,
				phoneVerified: 1,
			},
			photo: 1,
			terminated: 1,
			purposes: 1,
			studies: 1,
			categories: 1,
			isManuallyOffline: 1,
			isOnline: 1,
			lastOnlineAt: 1,
			followers: 1,
			followings: 1,
			posts: 1,
			status: 1,
			location: 1,
			locationString: 1,
			verified: 1,
			createdAt: 1,
			updatedAt: 1
		}).populate('status')
		if (!user || user.terminated) {
			return res.status(404).json({ message: 'User not found' })
		}

		if (user.isManuallyOffline) {
			user.isOnline = false
		} else if (!user.isOnline && user.lastOnlineAt) {
			user.lastOnlineAt.setMinutes(user.lastOnlineAt.getMinutes() + constants.maxOnlineVisibilityInMinutes)
			if (user.lastOnlineAt >= new Date()) {
				user.isOnline = true
			}
		}

		const isBlocked = await services.isUserBlocked(user._id, u._id)
		if (isBlocked) {
			return res.status(403).json({ message: "Unable to get this user's data as its blocked" })
		}

		// Check if target user follows me
		const isFollower = await Follower.exists({
			follower: id,
			following: u._id
		})

		// Check if I follow target user
		const isFollowing = await Follower.exists({
			follower: u._id,
			following: id
		})

		const status = user.status
		user.status = undefined

		const connection = await Connection.findOne({
			$or: [
				{ from: user._id, to: u._id },
				{ to: user._id, from: u._id }
			]
		})
			.populate({
				path: 'from',
				select: { name: 1, username: 1, bio: 1, photo: 1 }
			})
			.populate({
				path: 'to',
				select: { name: 1, username: 1, bio: 1, photo: 1 }
			})

		const chat = await Chat.findOne({
			$or: [
				{ creator: u._id, user: user._id },
				{ user: u._id, creator: user._id },
			]
		}, { _id: 1 })

		return res.json({
			user,
			isFollower: !!isFollower,
			isFollowing: !!isFollowing,
			status,
			connection,
			chatId: chat?._id
		})
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function patchUser(req: Request, res: Response) {
	interface Payload {
		name?: string
		username?: string
		bio?: string
		url?: string | null
		school?: string
		courses?: string
		dob?: string
		phone?: string
		password?: string
		oldPassword?: string
		twoFactorAuthentication?: boolean
		isManuallyOffline?: boolean
		purposes?: IPurpose[]
		studies?: IStudy[]
		categories?: string[]
		platform?: 'android' | 'ios' | 'web'
		fcmToken?: string
		locationString?: string
		latitude?: number
		longitude?: number
	}
	try {
		const { name, username, bio, url, school, courses, dob, phone, password, oldPassword, twoFactorAuthentication, isManuallyOffline, purposes, studies, categories, platform, fcmToken, latitude, longitude, locationString } = req.body as Payload
		let user = res.locals.user as IUser
		const device = res.locals.device as ILoggedinDevice

		let token
		if (password && oldPassword) {
			if (user.auth.password !== helpers.getHash(oldPassword)) {
				return res.status(401).json({ message: 'Old password is incorrect' })
			}
			user.auth.password = helpers.getHash(password)
			token = helpers.getToken(user)
		}
		if (phone && user.phone !== phone) {
			user.phone = phone
			user.auth.phoneVerified = false
			user.auth.phoneOtp = undefined
			user.auth.phoneOtpExpiry = undefined
			user.auth.lastPhoneOtpSentAt = undefined
		}
		if (username && user.username !== username) {
			const u = await User.findOne({ username })
			if (u) {
				return res.status(409).json({ message: 'username is not available' })
			}
		}
		user.dob = dob ? new Date(dob) : user.dob
		user.name = name ?? user.name
		user.bio = bio ?? user.bio
		user.url = url ?? user.url
		if (url === null) {
			user.url = undefined
		}
		user.school = school ?? user.school
		user.courses = courses ?? user.courses
		user.purposes = purposes ?? user.purposes
		user.studies = studies ?? user.studies
		user.username = username ?? user.username
		user.categories = categories ?? user.categories
		user.locationString = locationString ?? user.locationString
		if (typeof latitude === 'number' && typeof longitude === 'number') {
			user.location = {
				type: 'Point',
				coordinates: [longitude, latitude]
			}
		}
		user.auth.twoFactorAuthentication = twoFactorAuthentication ?? user.auth.twoFactorAuthentication
		if (!user.school && studies) {
			user.school = studies.flatMap(e => e.value).join(', ')
		}
		if (typeof isManuallyOffline === 'boolean') {
			user.isManuallyOffline = isManuallyOffline || undefined
		}
		device.fcmToken = fcmToken ?? device.fcmToken
		device.platform = platform ?? device.platform
		await user.save()

		if (typeof isManuallyOffline === 'boolean') {
			services.socketNotifyOnUserPresenceUpdated(user, isManuallyOffline ? 'offline' : 'online')
		}

		return res.json({ token, message: 'user updated successfully' })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postCheckUsername(req: Request, res: Response) {
	try {
		const username = req.body.username as string

		const user = await User.findOne({ username })
		if (user) {
			return res.status(409).json({ message: `The username ${username} is already taken` })
		}

		return res.json({ message: 'username is available' })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function putProfilePhoto(req: Request, res: Response) {
	try {
		let user = res.locals.user as IUser
		const file = req.file!
		const path = `users/${user._id}/${file.filename}`
		if (user.photo) {
			await helpers.deleteR2File(user.photo!)
		}
		user.photo = await helpers.uploadFile(file, path)
		await user.save()
		helpers.deleteFile(file)
		return res.status(201).json({ message: 'photo updated successfully', photo: user.photo })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function deleteUser(req: Request, res: Response) {
	try {
		const user = res.locals.user as IUser

		user.auth.loggedinDevices = []
		user.deactivatedAt = new Date()
		await user.save()

		await helpers.sendOauthGmail(user.email, 'Account scheduled for deletion', `Account is successfully scheduled for deletion, It will be automatically deleted after 7 days`)

		return res.json({ message: 'Account is successfully scheduled for deletion, It will be automatically deleted after 7 days' })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

const controllers = {
	getUsers,
	getExploreUsersAndChats,
	getMeetmeOnlineUsers,
	getProfile,
	getUser,
	patchUser,
	postCheckUsername,
	putProfilePhoto,
	deleteUser
}

export default controllers
