import { Request, Response } from 'express'
import { ILoggedinDevice, IUser, User } from '../users/models'
import helpers from '../../utils/helpers'
import constants from '../../utils/constants'
import jwt from 'jsonwebtoken'
import services from '../../utils/services'

async function postLogin(req: Request, res: Response) {
	interface Payload {
		email: string
		password: string
		phone?: boolean
		remember?: boolean
		platform: 'android' | 'ios' | 'web'
		fcmToken?: string
	}
	try {
		let { email, password, phone, remember, platform, fcmToken } = req.body as Payload
		if (typeof remember !== 'boolean') {
			remember = true
		}
		const user = await User.findOne({ email })
		if (!user) {
			return res.status(401).json({ message: 'authentication 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.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 hash = helpers.getHash(password)
		if (user.auth.password !== hash) {
			return res.status(401).json({ message: 'authentication failed' })
		}

		if (!user.auth.emailVerified) {
			return res.status(403).json({ message: 'Please verify your email first', verificationRequired: true })
		}

		if (user.auth.twoFactorAuthentication) {
			if (phone && !user.phone) {
				return res.status(404).json({ message: 'User with this phone don\'t exist' })
			}
			const payload = { _id: `${user._id}`, phone, remember, platform }
			// @ts-ignore
			const tfaToken = jwt.sign(payload, process.env.JWT_SECRET!, { expiresIn: '5m' })
			await services.sendOtp(user as IUser, phone ? 'phone' : 'email')
			return res.json({ '2faToken': tfaToken })
		}

		const token = await services.createLoggedinDevice(user as IUser, platform, fcmToken, remember)
		return res.json({ token, id: user._id })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postGoogleLogin(req: Request, res: Response) {
	interface Payload {
		googleToken: string
		remember?: boolean
		platform: 'android' | 'ios' | 'web'
		fcmToken?: string
	}
	try {
		let { googleToken, platform, remember, fcmToken } = req.body as Payload

		const decoded = await helpers.decodeGoogleToken(googleToken, process.env.GOOGLE_CLIENT_ID!)
		if (!decoded) {
			return res.status(401).json({ message: 'authentication failed' })
		}

		const user = await User.findOne({ email: decoded.email })
		if (!user) {
			return res.status(404).json({ message: 'user not found, registration required', decoded })
		}
		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.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 })
		}

		if (!user.auth.emailVerified) {
			return res.status(403).json({ message: 'Please verify your email first', verificationRequired: true, decoded })
		}

		const token = await services.createLoggedinDevice(user as IUser, platform, fcmToken, remember)
		return res.json({ token, id: user._id, decoded })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postRegister(req: Request, res: Response) {
	interface Payload {
		email: string
		dob: string
		password: string
	}
	try {
		const { dob, email, password } = req.body as Payload
		let user = await User.findOne({ email })
		if (user) {
			return res.status(409).json({ message: 'user already exist with this email' })
		}

		const otp = helpers.getRandomOtp()
		const now = new Date()
		now.setMinutes(now.getMinutes() + constants.emailOtpExpiry)

		user = await User.create({
			dob,
			email,
			auth: {
				password: helpers.getHash(password),
				emailOtp: otp,
				emailOtpExpiry: now,
				lastEmailOtpSentAt: new Date()
			}
		})

		await helpers.sendOauthGmail(user.email, 'Email Verification OTP', `OTP to verify your email is ${otp}`)

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

async function postSendOtp(req: Request, res: Response) {
	interface Payload {
		email: string
		phone?: boolean
	}
	try {
		const { email, phone } = req.body as Payload
		const user = await User.findOne({ email })
		if (!user) {
			return res.status(404).json({ message: 'user not found' })
		}
		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.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 })
		}
		if (phone && !user.phone) {
			return res.status(404).json({ message: 'user not found' })
		}

		await services.sendOtp(user as IUser, phone ? 'phone' : 'email')

		return res.json({ message: `${phone ? 'Phone' : 'Email'} OTP sent successfully` })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postCheckOtp(req: Request, res: Response) {
	interface Payload {
		email: string
		otp: string
		phone?: boolean
	}
	try {
		const { email, otp, phone } = req.body as Payload
		const user = await User.findOne({ email })
		if (!user) {
			return res.status(404).json({ message: 'user not found' })
		}
		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.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 })
		}
		if (phone && !user.phone) {
			return res.status(404).json({ message: 'user not found' })
		}

		const { status, message, error } = await services.checkAndResetOtp(user as IUser, otp, phone ? 'phone' : 'email')
		if (error) {
			return res.status(500).json({ message: 'server error' })
		}
		if (status) {
			return res.status(status).json({ message })
		}

		return res.json({ message: `${phone ? 'Phone' : 'Email'} OTP is valid` })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postVerify(req: Request, res: Response) {
	interface Payload {
		email: string
		otp: string
		phone?: boolean
		platform: 'android' | 'ios' | 'web'
		fcmToken?: string
	}
	try {
		const { email, otp, phone, platform, fcmToken } = req.body as Payload
		const user = await User.findOne({ email })
		if (!user) {
			return res.status(404).json({ message: 'user not found' })
		}
		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.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 })
		}
		if (phone && !user.phone) {
			return res.status(404).json({ message: 'user not found' })
		}

		if (phone && user.auth.phoneVerified) {
			return res.json({ message: 'phone already verified' })
		}

		if (!phone && user.auth.emailVerified) {
			return res.json({ message: 'email already verified' })
		}

		const { status, message, error } = await services.checkAndResetOtp(user as IUser, otp, phone ? 'phone' : 'email', true)
		if (error) {
			return res.status(500).json({ message: 'server error' })
		}
		if (status) {
			return res.status(status).json({ message })
		}

		const token = await services.createLoggedinDevice(user as IUser, platform, fcmToken)
		return res.json({ message: `${phone ? 'Phone' : 'Email'} verified successfully`, token, id: user._id })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postVerify2FA(req: Request, res: Response) {
	interface Payload {
		'2faToken': string
		otp: string
		platform: 'android' | 'ios' | 'web'
		fcmToken?: string
	}
	interface JwtPayload {
		_id: string
		platform: 'android' | 'ios' | 'web'
		phone?: boolean
		remember?: boolean
	}
	try {
		const { '2faToken': tfaToken, otp, fcmToken } = req.body as Payload
		let payload: any
		try {
			payload = jwt.verify(tfaToken, process.env.JWT_SECRET!)
		} catch (error) {
			return res.status(401).json({ message: 'authentication failed' })
		}
		const { _id, platform, phone, remember } = payload as JwtPayload

		const user = await User.findById(_id)
		if (!user) {
			return res.status(404).json({ message: 'user not found' })
		}
		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.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 })
		}
		if (phone && !user.phone) {
			return res.status(404).json({ message: 'user not found' })
		}

		const { status, message, error } = await services.checkAndResetOtp(user as IUser, otp, phone ? 'phone' : 'email', true)
		if (error) {
			return res.status(500).json({ message: 'server error' })
		}
		if (status) {
			return res.status(status).json({ message })
		}

		const token = await services.createLoggedinDevice(user as IUser, platform, fcmToken, remember)
		return res.json({ token, id: user._id })
	} catch (error) {
		console.log(error)
		return res.status(500).json({ message: 'server error' })
	}
}

async function postResetPassword(req: Request, res: Response) {
	interface Payload {
		email: string
		otp: string
		password: string
		phone?: boolean
	}
	try {
		const { email, otp, password, phone } = req.body as Payload
		const user = await User.findOne({ email })
		if (!user) {
			return res.status(404).json({ message: 'user not found' })
		}
		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.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 })
		}
		if (phone && !user.phone) {
			return res.status(404).json({ message: 'user not found' })
		}

		// use services helper function
		const { status, message, error } = await services.checkAndResetOtp(user as IUser, otp, phone ? 'phone' : 'email', true)
		if (error) {
			return res.status(500).json({ message: 'server error' })
		}
		if (status) {
			return res.status(status).json({ message })
		}

		user.auth.password = helpers.getHash(password)
		await user.save()

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

async function postLogout(req: Request, res: Response) {
	try {
		const user = res.locals.user as IUser
		const device = res.locals.device as ILoggedinDevice
		const index = user.auth.loggedinDevices.findIndex(e => e === device)

		user.auth.loggedinDevices.splice(index, 1)
		await user.save()

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

const controllers = {
	postLogin,
	postGoogleLogin,
	postRegister,
	postSendOtp,
	postCheckOtp,
	postVerify,
	postVerify2FA,
	postResetPassword,
	postLogout
}

export default controllers
