import { Request, Response } from 'express'
import { Types } from 'mongoose'
import { User } from '../../users/models'
import { Chat, IChat } from '../../chats/models'
import { Event } from '../models'
import * as eventServices from '../services'
import { IFile } from '../../../utils/types'
import helpers from '../../../utils/helpers'
import constants from '../../../utils/constants'
import services from '../../../utils/services'

async function getEvents(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		interface Payload {
			limit?: string | number
			page?: string | number
		}
		let { limit = '20', page = '1' } = req.query as Payload
		limit = +limit
		page = +page
		const skip = (page - 1) * limit

		const events = await Event.find()
			.sort({ createdAt: -1 })
			.skip(skip)
			.limit(limit)
			.populate({ path: 'user', select: 'name username photo' })
			.lean()

		const count = await Event.countDocuments()

		return res.json({ events, count })
	})
}

async function postEvent(req: Request, res: Response) {
	interface Payload {
		user: string
		title: string
		description: string
		date: string
		time: string
		locationString: string
		latitude: string | number
		longitude: string | number
		category: string
		url?: string
		communities?: string[]
		joiningCost?: number | string
		membersLimit?: number | string
		featured?: 'true' | 'false'
	}
	await helpers.runApi(res, async () => {
		try {
			const body = req.body as Payload
			const { user: userRef, ...rest } = body
			const communitiesArray = Array.isArray(body.communities)
				? body.communities
				: typeof body.communities === 'string'
					? (body.communities ? [body.communities] : [])
					: undefined
			const payload = {
				...rest,
				category: body.category ?? 'Events',
				communities: communitiesArray?.length ? communitiesArray : undefined,
				joiningCost: body.joiningCost != null ? Number(body.joiningCost) : undefined,
				membersLimit: body.membersLimit != null ? Number(body.membersLimit) : undefined,
				...(body.featured === 'true' ? { featured: true } : { featured: undefined }),
			}
			const cover = req.file as unknown as IFile

			const targetUser = Types.ObjectId.isValid(userRef)
				? await User.findById(userRef)
				: await User.findOne({ username: userRef })

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

			const { startedAt } = eventServices.getEventTime(payload.date, payload.time)
			const startOfToday = new Date()
			startOfToday.setHours(0, 0, 0, 0)
			if (startedAt < startOfToday) {
				return res.status(403).json({ message: 'date must be today or a future date' })
			}

			const { event, chat } = await eventServices.createEvent(targetUser._id, payload, undefined, cover)
			return res.status(201).json({ event, chat })
		} catch (error: any) {
			console.log(error)
			if (error?.message === 'unable to upload file') {
				return res.status(500).json({ message: 'unable to upload file' })
			}
			throw error
		}
	})
}

async function patchEvent(req: Request, res: Response) {
	interface Payload {
		title?: string
		description?: string
		date?: string
		time?: string
		locationString?: string
		latitude?: string | number
		longitude?: string | number
		category?: string
		url?: string
		communities?: string[]
		joiningCost?: number | string
		membersLimit?: number | string
		featured?: 'true' | 'false'
	}
	await helpers.runApi(res, async () => {
		const id = req.params.id
		const { title, description, date, time, locationString, latitude, longitude, category, url: eventUrl, communities, joiningCost, membersLimit, featured } = req.body as Payload
		const cover = req.file as unknown as IFile

		let startedAt: Date | undefined, endedAt: Date | undefined
		if (date && time) {
			const data = eventServices.getEventTime(date, time)
			startedAt = data.startedAt
			endedAt = data.endedAt
			const startOfToday = new Date()
			startOfToday.setHours(0, 0, 0, 0)
			if (startedAt < startOfToday) {
				return res.status(403).json({ message: 'date must be today or a future date' })
			}
		}

		let location: [number, number] | undefined
		if (latitude && longitude) {
			location = [+longitude, +latitude]
		}

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

		const chat = event.chat as unknown as IChat
		if (!chat) {
			return res.status(404).json({ message: 'event chat not found' })
		}

		if(!event.joiningCost && joiningCost) {
			return res.status(400).json({ message: 'joining cost can only be updated for existing paid events' })
		}

		let url: string | undefined = undefined
		if (cover) {
			url = await helpers.uploadFile(cover, `chats/${chat._id}/${cover.filename}`)
			if (!url) {
				return res.status(500).json({ message: 'unable to upload file' })
			}
			if (event.cover) {
				await helpers.deleteR2File(event.cover)
			}
		}

		if (typeof eventUrl === 'string') {
			event.url = eventUrl || undefined
		}

		event.title = title ?? event.title
		event.description = description ?? event.description
		event.date = date ?? event.date
		event.time = time ?? event.time
		event.locationString = locationString ?? event.locationString
		let oldLocationCoordinated = event.location.coordinates
		event.location.coordinates = location ?? event.location.coordinates
		event.category = category ?? event.category
		event.cover = url ?? event.cover
		event.startedAt = startedAt ?? event.startedAt
		event.endedAt = endedAt ?? event.endedAt
		if (communities !== undefined) {
			// @ts-ignore
			event.communities = communities.map((id) => new Types.ObjectId(id))
		}
		if (joiningCost !== undefined && event.joiningCost) {
			event.joiningCost = Number(joiningCost)
		}
		if (membersLimit !== undefined) {
			event.membersLimit = Number(membersLimit)
		}
		if (featured) {
			event.featured = (featured === 'true') || undefined
		}
		await event.save()

		chat.name = title ?? chat.name
		chat.photo = url ?? chat.photo
		await chat.save()

		helpers.runInBackground(async () => {
			if (!event.location) {
				return
			}
			const users = await User.find({
				name: { $exists: true },
				isOnline: true,
				$and: [
					{
						$or: [
							{ terminated: false },
							{ terminated: { $exists: false } }
						]
					},
					{
						$or: [
							{
								location: {
									$geoWithin: {
										$centerSphere: [
											event.location.coordinates,
											constants.locationDistance / constants.earthRadius
										]
									}
								}
							},
							...(location ? [{
								location: {
									$geoWithin: {
										$centerSphere: [
											oldLocationCoordinated,
											constants.locationDistance / constants.earthRadius
										]
									}
								}
							}] : []),
						]
					}
				],
			}, { _id: 1 })
			for (const u of users) {
				services.socketNotifyUser({
					userId: `${u._id}`,
					event: 'event-updated',
					data: { event: `${event._id}` }
				})
			}
		})

		return res.json({ cover: url, message: 'event updated successfully' })
	})
}

async function deleteEvent(req: Request, res: Response) {
	await helpers.runApi(res, async () => {
		const id = req.params.id

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

		const chat = eventDoc.chat as unknown as IChat
		if (!chat) {
			return res.status(404).json({ message: 'event chat not found' })
		}

		const eventId = eventDoc._id
		const locationCoords = eventDoc.location?.coordinates

		await Chat.updateOne({ _id: chat._id }, { $unset: { event: '' } })
		await Event.deleteOne({ _id: eventId })

		helpers.runInBackground(async () => {
			const payload = { event: `${eventId}` }

			for (const participant of chat.participants) {
				services.socketNotifyUser({
					userId: `${participant.user}`,
					event: 'event-deleted',
					data: payload
				})
			}

			if (locationCoords) {
				const users = await User.find({
					name: { $exists: true },
					isOnline: true,
					$and: [
						{
							$or: [
								{ terminated: false },
								{ terminated: { $exists: false } }
							]
						}
					],
					location: {
						$geoWithin: {
							$centerSphere: [
								locationCoords,
								constants.locationDistance / constants.earthRadius
							]
						}
					}
				}, { _id: 1 })
				for (const u of users) {
					services.socketNotifyUser({
						userId: `${u._id}`,
						event: 'event-deleted',
						data: payload
					})
				}
			}
		})

		return res.json({ message: 'event deleted successfully' })
	})
}

const controllers = {
	getEvents,
	postEvent,
	patchEvent,
	deleteEvent
}

export default controllers
