import { Request, Response } from 'express'
import { IUser } from '../users/models'
import { Event } from './models'
import helpers from '../../utils/helpers'
import { IFile } from '../../utils/types'
import { Chat, IChat } from '../chats/models'
import constants from '../../utils/constants'
import { IScore } from '../scores/models'
import services from '../../utils/services'

async function getEvents(req: Request, res: Response) {
	interface Payload {
    limit?: string | number
    page?: string | number
    type?: 'others' | 'mine'
    search?: string
    categories?: string
  }

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

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

    const match: any = {
			endedAt: {$gt: new Date()},
		}

		if(type !== 'mine' && constants.locationMode && !user.location) {
			return res.json({ events: [], count: 0 })
		}

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

    // search filter
    if (search) {
      match.$or = [
        { title: { $regex: search, $options: 'i' } },
        { description: { $regex: search, $options: 'i' } },
      ]
    }

    // category filter
    if (categories) {
      match.category = { $in: categories.split(',').filter((e) => e) }
    }

    // Main aggregation
    const pipeline: any[] = [
      { $match: match },
      { $sort: { createdAt: -1 } },
      {
        $lookup: {
          from: 'chats',
          localField: 'chat',
          foreignField: '_id',
          as: 'chat',
        },
      },
      { $unwind: '$chat' },
      {
        $addFields: {
          participantIds: '$chat.participants.user',
        },
      },
    ]

		// Conditional filter for "mine" or "others"
    if (type === 'mine') {
      pipeline.push({
        $match: {
          participantIds: user._id,
        },
      })
    } else if (type === 'others') {
      pipeline.push({
        $match: {
          participantIds: { $ne: user._id },
        },
      })
    }

    // Pagination and count
    pipeline.push({
      $facet: {
        events: [{ $skip: skip }, { $limit: limit }],
        count: [{ $count: 'total' }],
      },
    })

    pipeline.push({
      $project: {
        events: 1,
        count: { $ifNull: [{ $arrayElemAt: ['$count.total', 0] }, 0] },
      },
    })

    const result = await Event.aggregate(pipeline)
    const { events, count } = result[0]
		for(const event of events) {
			delete event.participantIds
			const p = event.chat.participants.find((e: any) => `${e.user}` === `${user._id}`)
			event.joined = !!p
			event.chat.areYouAdmin = !!p?.isAdmin
		}

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

async function getEvent(req: Request, res: Response) {
	try {
		const id = req.params.id

		const event = await Event.findById(id)
			.populate({ path: 'user', select: 'name photo username email' })
		if (!event) {
			return res.status(404).json({ message: 'event not found' })
		}

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

async function postEvent(req: Request, res: Response) {
	interface Payload {
		title: string
		description: string
		date: string
		time: string
		locationString: string
		latitude: string | number
		longitude: string | number
		category: string
	}
	try {
		const user = res.locals.user as IUser
		const { title, description, date, time, locationString, latitude, longitude, category } = req.body as Payload
		const cover = req.file as unknown as IFile

		const {startedAt, endedAt} = getEventTime(date, time)
		if(startedAt < new Date()) {
			return res.status(403).json({message: 'date and time combination must produce a future timestamp'})
		}

		const chat = new Chat({
			creator: user._id,
			isGroup: true,
			name: title,
			allowPublicPost: true,
			tags: [],
			participants: [
				{
					user: user._id,
					isAdmin: true,
				}
			],
			category,
		})

		const event = new Event({
			user: user._id,
			title,
			description,
			date,
			time,
			location: {
				type: 'Point',
				coordinates: [+longitude, +latitude]
			},
			locationString,
			chat: chat._id,
			category,
			startedAt,
			endedAt,
		})

		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'})
			}
		}

		// @ts-ignore
		chat.event = event._id
		chat.photo = url
		event.cover = url
		await chat.save()
		await event.save()

		return res.status(201).json({ event, chat })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server 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
	}
	try {
		const user = res.locals.user as IUser
		const id = req.params.id
		const { title, description, date, time, locationString, latitude, longitude, category } = req.body as Payload
		const cover = req.file as unknown as IFile

		let startedAt: Date | undefined, endedAt: Date | undefined;
		if(date && time) {
			const data = getEventTime(date, time)
			startedAt = data.startedAt
			endedAt = data.endedAt
			if(startedAt < new Date()) {
				return res.status(403).json({message: 'date and time combination must produce a future timestamp'})
			}
		}

		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'})
		}
		if(event.endedAt < new Date()) {
			return res.status(403).json({message: 'event is expired'})
		}

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

		const admin = chat.participants.find(e => `${e.user}` === `${user._id}` && e.isAdmin)
		if(!admin) {
			return res.status(403).json({message: 'only admins can update event'})
		}

		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)
			}
		}

		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
		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
		await event.save()

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

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

function getEventTime(date: string, time: string) {
	const startedAt = new Date(`${date} ${time}`)
	const endedAt = new Date(startedAt.getTime() + 5 * 60 * 60 * 1000)  // 5 hour later
	return { startedAt, endedAt }
}

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

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

const controllers = {
	getEvents,
	getEvent,
	postEvent,
	patchEvent,
	postOpenedViewAll
}

export default controllers
