import { Request, Response } from 'express'
import { IUser, User } from '../users/models'
import { Status } from './models'
import { Event } from '../events/models'
import { ILocation } from '../../utils/types'
import constants from '../../utils/constants'
import { IScore } from '../scores/models'
import services from '../../utils/services'

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

	try {
		const user = 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: user._id },
			name: { $exists: true },
			status: { $exists: true },
			$or: [
				{ terminated: false },
				{ terminated: { $exists: false } }
			]
		}

		if (search) {
			matchStage.name = { $regex: search, $options: 'i' }
		}

		const categoryFilter = categories
			? { 'status.categories': { $in: (categories as string).split(',') } }
			: {}

		const now = new Date()

		const afterUnwindMatch: any = {
			...categoryFilter,
			'status.visibleAt': { $lte: now },
			'status.endedAt': { $gte: now }
		}

		if (constants.locationMode && user.location) {
			afterUnwindMatch.$or = [
				{
					'status.location': {
						$geoWithin: {
							$centerSphere: [
								user.location.coordinates, // [lng, lat]
								constants.locationDistance / constants.earthRadius // meters → radians
							]
						}
					}
				},
				{ 'status.location': { $exists: false } }
			]
		}

		const pipeline: any = [
			{ $match: matchStage },
			{
				$lookup: {
					from: 'statuses',
					localField: 'status',
					foreignField: '_id',
					as: 'status'
				}
			},
			{ $unwind: { path: '$status', preserveNullAndEmptyArrays: false } },
			{ $match: afterUnwindMatch },
			{
				$lookup: {
					from: 'connections',
					let: { otherUserId: '$_id' },
					pipeline: [
						{
							$match: {
								$expr: {
									$or: [
										{ $and: [{ $eq: ['$from', user._id] }, { $eq: ['$to', '$$otherUserId'] }] },
										{ $and: [{ $eq: ['$to', user._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
					},
					status: 1,
					connection: 1,
					photo: 1,
					createdAt: 1,
					updatedAt: 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;

		(async () => {
			try {
				if(!categories) {
					return
				}
				const score = await services.getOrCreateTodayScore(user._id)
				if (!score) {
					return
				}
				const action = score.social.actions.find(e => e.type === 'search-with-meetme-category-filter')
				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({ users, count })
	} catch (error) {
		console.error(error)
		return res.status(500).json({ message: 'server error' })
	}
}

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

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

		limit = +limit

		// fetching users with their status
		const matchStage: any = {
			_id: { $ne: user._id },
			name: { $exists: true },
			status: { $exists: true },
			$or: [
				{ terminated: false },
				{ terminated: { $exists: false } }
			]
		}

		const now = new Date()
		const afterUnwindMatch: any = {
			'status.visibleAt': { $lte: now },
			'status.endedAt': { $gte: now },
			$and: [
				// search
				{
					$or: [
						{ name: { $regex: search, $options: 'i' } },
						{ 'status.title': { $regex: search, $options: 'i' } },
					]
				},
			]
		}

		if (constants.locationMode && user.location) {
			afterUnwindMatch.$and.push({
				// location
				$or: [
					{
						'status.location': {
							$geoWithin: {
								$centerSphere: [
									user.location.coordinates, // [lng, lat]
									constants.locationDistance / constants.earthRadius // meters → radians
								]
							}
						}
					},
					{ 'status.location': { $exists: false } }
				]
			})
		}

		let pipeline: any = [
			{ $match: matchStage },
			{
				$lookup: {
					from: 'statuses',
					localField: 'status',
					foreignField: '_id',
					as: 'status'
				}
			},
			{ $unwind: { path: '$status', preserveNullAndEmptyArrays: false } },
			{ $match: afterUnwindMatch },
			{
				$lookup: {
					from: 'connections',
					let: { otherUserId: '$_id' },
					pipeline: [
						{
							$match: {
								$expr: {
									$or: [
										{ $and: [{ $eq: ['$from', user._id] }, { $eq: ['$to', '$$otherUserId'] }] },
										{ $and: [{ $eq: ['$to', user._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
					},
					status: 1,
					connection: 1,
					photo: 1,
					createdAt: 1,
					updatedAt: 1
				}
			},
			{ $sort: { createdAt: -1 } },
			{ $limit: limit },
		]
		const users = await User.aggregate(pipeline)

		// fetching events
		const match: any = {
			endedAt: { $gt: new Date() },
			$or: [
				{ title: { $regex: search, $options: 'i' } },
				{ description: { $regex: search, $options: 'i' } },
			]
		}

		if (constants.locationMode && user.location) {
			match.location = {
				$geoWithin: {
					$centerSphere: [
						user.location.coordinates, // [lng, lat]
						constants.locationDistance / constants.earthRadius // meters → radians
					]
				}
			}
		}

		// Main aggregation
		pipeline = [
			{ $match: match },
			{ $sort: { createdAt: -1 } },
			{
				$lookup: {
					from: 'chats',
					localField: 'chat',
					foreignField: '_id',
					as: 'chat',
				},
			},
			{ $unwind: '$chat' },
			{
				$addFields: {
					participantIds: '$chat.participants.user',
					'chat.participantsCount': { $size: '$chat.participants' }
				},
			},
			{
				$match: {
					participantIds: { $ne: user._id },
				},
			},
			{ $limit: limit }
		]
		const events = await Event.aggregate(pipeline)
		for (const event of events) {
			delete event.participantIds
			event.joined = event.chat.participants.some((e: any) => `${e.user}` === `${user._id}`)
			const p = event.chat.participants.find((e: any) => `${e.user}` === `${user._id}`)
			event.chat.isJoined = !!p
			event.chat.areYouAdmin = !!p?.isAdmin
		}

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

async function postStatus(req: Request, res: Response) {
	interface Payload {
		categories: string[]
		schedule: 'now' | 'later'
		startDateType: 'today' | 'tomorrow' | 'custom'
		startDate: string
		startTime: string
		title: string
		locationString?: string
		latitude?: number
		longitude?: number
	}
	try {
		const user = res.locals.user as IUser
		const { categories, title = '', schedule, startDateType, startDate, startTime, latitude, longitude, locationString } = req.body as Payload

		let _status = await Status.findOne({ user: user._id })
		if (_status && _status.endedAt > new Date()) {
			return res.status(409).json({ message: 'status already exist' })
		}

		if (_status) {
			user.status = undefined
			await _status.deleteOne()
		}

		let location: ILocation | undefined = user.location
		if (typeof latitude === 'number' && typeof longitude === 'number') {
			location = {
				type: 'Point',
				coordinates: [longitude, latitude]
			}
		}

		const { startedAt, visibleAt, endedAt } = getStartedEndedStatusTime({ startDate, startTime })
		const status = await Status.create({
			user: user._id,
			categories,
			title,
			schedule,
			startDateType,
			startDate,
			startTime,
			startedAt,
			visibleAt,
			endedAt,
			locationString: locationString ?? user.locationString,
			location,
		})
		user.status = status._id
		await user.save();

		(async () => {
			try {
				const score = await services.getOrCreateTodayScore(user._id)
				if (!score) {
					return
				}
				const action = score.social.actions.find(e => e.type === 'create-meetme-status')
				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.status(201).json({ status })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function patchStatus(req: Request, res: Response) {
	interface Payload {
		categories?: string[]
		schedule?: 'now' | 'later'
		startDateType?: 'today' | 'tomorrow' | 'custom'
		startDate?: string
		startTime?: string
		title?: string
		locationString?: string
		latitude?: number
		longitude?: number
	}
	try {
		const user = res.locals.user as IUser
		const { categories, title, schedule, startDateType, startDate, startTime, latitude, longitude, locationString } = req.body as Payload

		const status = await Status.findOne({ user: user._id })
		if (!status) {
			return res.status(404).json({ message: 'status not found' })
		}

		if (startDate || startTime) {
			const { startedAt, visibleAt, endedAt } = getStartedEndedStatusTime({ startDate: startDate ?? status.startDate, startTime: startTime ?? status.startTime })
			status.startedAt = startedAt
			status.visibleAt = visibleAt
			status.endedAt = endedAt
		}
		status.categories = categories ?? status.categories
		if(title !== undefined) {
			status.title = title
		}
		status.schedule = schedule ?? status.schedule
		status.startDateType = startDateType ?? status.startDateType
		status.locationString = locationString ?? status.locationString
		if (typeof latitude === 'number' && typeof longitude === 'number') {
			status.location = {
				type: 'Point',
				coordinates: [longitude, latitude]
			}
		}
		await status.save()

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

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

		const status = await Status.findOne({ user: user._id })
		if (!status) {
			return res.status(404).json({ message: 'status not found' })
		}

		await status.deleteOne()
		user.status = undefined
		await user.save()

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

function getStartedEndedStatusTime(data: { startDate: string, startTime: string }) {
	const { startDate, startTime } = data
	const startedAt = new Date(`${startDate} ${startTime}`)
	// const visibleAt = new Date(startedAt.getTime() - 12 * 60 * 60 * 1000) // 12 hours earlier
	const visibleAt = new Date(startedAt) // same as startedAt
	const endedAt = new Date(startedAt.getTime() + 3 * 60 * 60 * 1000)  // 3 hour late
	return { startedAt, visibleAt, endedAt }
}

const controllers = {
	getStatuses,
	getStatusesAndEvents,
	postStatus,
	patchStatus,
	deleteStatus
}

export default controllers
