import jwt from 'jsonwebtoken'
import { Request, Response, NextFunction } from 'express'
import { User } from '../api/users/models'
import { Admin } from '../api/admin/models'
import helpers from './helpers'

interface UserPayload {
	_id: string
	hash: string
}

async function authorizeUser(req: Request, res: Response, next: NextFunction) {
	try {
		const { authorization } = req.headers
		if (!authorization) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		if (!authorization.startsWith('Bearer ')) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		const token = authorization.substring('Bearer '.length)
		let payload: UserPayload
		try {
			payload = jwt.verify(token, process.env.JWT_SECRET!) as UserPayload
		} catch (error) {
			// check if tokenHash exist in users devices, if yes then remove it
			try {
				const payload = jwt.decode(token)
				if(typeof payload !== 'object' || !payload?._id) {
					return
				}
				const user = await User.findById(payload._id)
				if(!user) {
					return
				}
				const tokenHash = helpers.getHash(token)
				const deviceIndex = user.auth.loggedinDevices.findIndex(e => e.tokenHash === tokenHash)
				if(deviceIndex !== -1) {
					return
				}
				user.auth.loggedinDevices.splice(deviceIndex, 1)
				await user.save();
			} catch (error) {
				console.log(error)
			}
			return res.status(401).json({ message: 'authorization failed' })
		}
		const { _id, hash } = payload
		const user = await User.findById(_id)
		if (!user) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		if(user.deactivatedAt) {
			return res.status(403).json({ message: 'Account is currently scheduled for deletion, It will be automatically deleted within 7 days' })
		}
		if (user.auth.password.slice(-10) !== hash) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		if (!user.auth.emailVerified) {
			return res.status(403).json({ message: 'Please verify your email first', verificationRequired: true })
		}
		if (user.terminated) {
			return res.status(403).json({ message: 'Your account has been terminated due to a violation of our Terms and Conditions and in accordance with our Privacy Policy.', terminated: true })
		}
		const tokenHash = helpers.getHash(token)
		const device = user.auth.loggedinDevices.find(e => e.tokenHash === tokenHash)
		if (!device) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		// update device lastAccessedAt
		try {
			device.lastAccessedAt = new Date()
			await user.save()
		} catch (error) {
			console.log(error)
		}
		res.locals.device = device
		res.locals.user = user
		return next()
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function optionalAuthorizeUser(req: Request, res: Response, next: NextFunction) {
	try {
		const { authorization } = req.headers
		if (!authorization) {
			return next()
		}
		if (!authorization.startsWith('Bearer ')) {
			return next()
		}
		const token = authorization.substring('Bearer '.length)
		let payload: UserPayload
		try {
			payload = jwt.verify(token, process.env.JWT_SECRET!) as UserPayload
		} catch (error) {
			// check if tokenHash exist in users devices, if yes then remove it
			try {
				const payload = jwt.decode(token)
				if(typeof payload !== 'object' || !payload?._id) {
					return next()
				}
				const user = await User.findById(payload._id)
				if(!user) {
					return next()
				}
				const tokenHash = helpers.getHash(token)
				const deviceIndex = user.auth.loggedinDevices.findIndex(e => e.tokenHash === tokenHash)
				if(deviceIndex !== -1) {
					return next()
				}
				user.auth.loggedinDevices.splice(deviceIndex, 1)
				await user.save();
			} catch (error) {
				console.log(error)
			}
			return next()
		}
		const { _id, hash } = payload
		const user = await User.findById(_id)
		if (!user) {
			return next()
		}
		if(user.deactivatedAt) {
			return next()
		}
		if (user.auth.password.slice(-10) !== hash) {
			return next()
		}
		if (!user.auth.emailVerified) {
			return next()
		}
		if (user.terminated) {
			return next()
		}
		const tokenHash = helpers.getHash(token)
		const device = user.auth.loggedinDevices.find(e => e.tokenHash === tokenHash)
		if (!device) {
			return next()
		}
		// update device lastAccessedAt
		try {
			device.lastAccessedAt = new Date()
			await user.save()
		} catch (error) {
			console.log(error)
		}
		res.locals.device = device
		res.locals.user = user
		return next()
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

interface AdminPayload {
	_id: string
	hash: string
}

async function authorizeAdmin(req: Request, res: Response, next: NextFunction) {
	try {
		const { authorization } = req.headers
		if (!authorization) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		if (!authorization.startsWith('Bearer ')) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		const token = authorization.substring('Bearer '.length)
		const { _id, hash } = jwt.verify(token, process.env.JWT_SECRET!) as AdminPayload
		const admin = await Admin.findById(_id)
		if (!admin) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		if (admin.auth.password.slice(-10) !== hash) {
			return res.status(401).json({ message: 'authorization failed' })
		}
		res.locals.admin = admin
		return next()
	} catch (error) {
		console.log(error)
		return res.status(401).json({ message: 'authorization failed' })
	}
}

const auth = {
	authorizeUser,
	optionalAuthorizeUser,
	authorizeAdmin
}

export default auth
