let bcrypt = require('bcryptjs')
let randomColor = require('randomcolor')
var passportLocalSequelize = require('passport-local-sequelize');
let pagination = require('../lib/pagination.js')
const Errors = require('../lib/errors.js')
var crypto = require("crypto");
var cryptoRandomString = require("crypto-random-string");
module.exports = (sequelize, DataTypes) => {
	let User = sequelize.define('User', {
		username: {
			type: DataTypes.STRING(191),
			unique: {
				msg: 'Username has already been taken, try another',
				fields: ['username']
			},
			validate: {
				is: {
					args: [/^[a-zA-Z0-9_]*$/],
					msg: 'Username can only contain numbers and letters'
				},
				len: {
					args: [3, 16],
					msg: 'username must be between 3 and 16 characters'
				},
				isString (val) {
					if(typeof val !== 'string') {
						throw new sequelize.ValidationError('username must be a string')
					}
				},
				containsNoBlankCharacters (val) {
					if(/\s/g.test(val)) {
						throw new sequelize.ValidationError('username can\'t contain blank characters')
					}
				}
			}
		},
		email: {
			type: DataTypes.TEXT,
			unique: {
				msg: 'This email is in use by another Kaverti account',
				fields: ['email']
			},
			validate: {
				isEmail: {
					args: true,
					msg: 'Email is not formatted correctly'
				},
				isString (val) {
					if(typeof val !== 'string') {
						throw new sequelize.ValidationError('email must be a string')
					}
				},
				len: {
					args: [5, 100],
					msg: 'email must be between 5 and 100 characters'
				}
			}
		},
		description: {
			type: DataTypes.TEXT,
			validate: {
				isString (val) {
					if(typeof val !== 'string') {
						throw new sequelize.ValidationError('description must be a string')
					}
				},
				len: {
					args: [0, 1024],
					msg: 'description must be less than 1024 characters'
				}
			}
		},
		color: {
			type: DataTypes.STRING,
			defaultValue () {
				return randomColor()
			}
		},
		theme: {
			type: DataTypes.STRING,
			defaultValue: 'light'
		},
		hash: {
			type: DataTypes.STRING,
			allowNull: false,
			validate: {
				len: {
					args: [6, 50],
					msg: 'password must be between 6 and 50 characters'
				},
				isString (val) {
					if(typeof val !== 'string') {
						throw new sequelize.ValidationError('Please enter your password')
					}
				}
			}
		},
		admin: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		developerMode: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		experimentMode: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		bot: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		booster: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		koins: {
			type: DataTypes.BIGINT,
			defaultValue: "250"
		},
		currency2: {
			type: DataTypes.BIGINT,
			defaultValue: "0"
		},
		system: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		hidden: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		bodyColor: {
			type: DataTypes.STRING(191),
			defaultValue: false
		},
		headColor: {
			type: DataTypes.STRING(191),
			defaultValue: false
		},
		leftLegColor: {
			type: DataTypes.STRING(191),
			defaultValue: false
		},
		rightLegColor: {
			type: DataTypes.STRING(191),
			defaultValue: false
		},
		leftArmColor: {
			type: DataTypes.STRING(191),
			defaultValue: false
		},
		rightArmColor: {
			type: DataTypes.STRING(191),
			defaultValue: false
		},
		emailVerified: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		emailToken: {
			type: DataTypes.STRING,
			defaultValue: false
		},
		passwordResetToken: {
			type: DataTypes.STRING,
			required: false
		},

		passwordResetExpiry: {
			type: DataTypes.DATE,
			required: false
		},
		picture: {
			type: DataTypes.TEXT('long'),
			validate: {
				isString (val) {
					if(typeof val !== 'string') {
						throw new sequelize.ValidationError('avatar must be a string')
					}
				}
			}
		}
	}, {
		instanceMethods: {
			async rand() {
				await this.update({ emailToken: cryptoRandomString({length: 16})})
			},
			async emailVerify() {
				await this.update({ emailVerified: true })
			},
			async updatePassword (currentPassword, newPassword) {
				if(currentPassword === newPassword) {
					throw Errors.passwordSame
				} else if(typeof currentPassword !== 'string' || typeof newPassword !== 'string') {
					throw new sequelize.ValidationError('Please enter your password')
				}

				let correctPassword = await bcrypt.compare(currentPassword, this.hash)

				if(correctPassword) {
					await this.update({ hash: newPassword })
				} else {
					throw Errors.invalidLoginCredentials
				}
			},
			/* async GenerateJWT() {
				const today = new Date();
				const expirationDate = new Date(today);
				expirationDate.setDate(today.getDate() + 60);

				let payload = {
					id: this.id,
					email: this.email,
					username: this.username,
				};

				return jwt.sign(payload, "AUSDHIASDHAHDAiyrgy3476rty734we6yrgwesyufeyhurfehyrurgty7346ertg645e37t6rgyu", {
					expiresIn: parseInt(expirationDate.getTime() / 1000, 10)
				});
			}, */
			async updateEmail (emailCurrentPassword, newEmail) {
				if(typeof emailCurrentPassword !== 'string' || typeof newEmail !== 'string') {
					throw new sequelize.ValidationError('input must be a string')
				}

				let correctPassword = await bcrypt.compare(emailCurrentPassword, this.hash)

				if(correctPassword) {
					await this.update({ email: newEmail, emailVerified: false, emailToken: cryptoRandomString({length: 16})})
				} else {
					throw Errors.invalidLoginCredentials
				}
			},
			async comparePassword (password) {
				return await bcrypt.compare(password, this.hash)
			},
			async destroyVerifyPassword (password) {
				if(typeof password !== 'string') {
					throw Errors.invalidLoginCredentials
				}

				let correctPassword = await bcrypt.compare(password, this.hash)

				if(correctPassword) {
					await this.destroy()
				} else {
					throw Errors.invalidLoginCredentials
				}
			},
			async getMeta (limit) {
				let Post = sequelize.models.Post
				let meta = {}

				let nextId = await pagination.getNextIdDesc(Post, { userId: this.id }, this.Posts)

				if(nextId === null) {
					meta.nextURL = null
					meta.nextPostsCount = 0
				} else {
					meta.nextURL =
						`/api/v1/user/${this.username}?posts=true&limit=${limit}&from=${nextId - 1}`

					meta.nextPostsCount = await pagination.getNextCount(
						Post, this.Posts, limit,
						{ UserId: this.id },
						true
					)
				}

				return meta
			}
		},
		classMethods: {
			associate (models) {
				User.hasMany(models.Post)
				User.hasMany(models.Thread)
				User.belongsToMany(models.Ip, { through: 'UserIp' })
			},
			includeOptions (from, limit) {
				let models = sequelize.models
				let options = models.Post.includeOptions()

				return [{
					model: models.Post,
					include: options,
					limit,
					where: { postNumber: { $gte: from } },
					order: [['id', 'ASC']]
				}]
			},
			async canBeUser (passkey) {
				let { User, PassKey } = sequelize.models

				if(User) {
					if(PassKey) {
						let passkey = await PassKey.findOne({ where: { passkey } })

						if(passkey && PassKey.isValid()) {
							await passkey.destroy()

							return true
						} else {
							throw Errors.invalidPassKey
						}
					} else {
						throw Errors.sequelizeValidation(sequelize, {
							error: 'Invalid PassKey',
							path: 'passkey'
						})
					}

				} else {
					return true
				}
			}
		},
		hooks: {
			async afterValidate(user, options) {
				if(user.changed('hash') && user.hash.length <= 50) {
					user.hash = await bcrypt.hash(user.hash, 12)
				}

				options.hooks = false
				return options
			}
		}
	})

	return User
}
