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 is already 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, 256],
					msg: 'description must be less than 256 characters'
				}
			}
		},
		color: {
			type: DataTypes.STRING,
			defaultValue () {
				return randomColor()
			}
		},
		theme: {
			type: DataTypes.STRING,
			defaultValue: 'light',
			values: ['light', 'dark'],
			isIn: {
				args: [['light', 'dark']],
				msg: "Theme can only be one of the pre-defined options"
			},
		},
		lastRewardDate: {
			type: DataTypes.DATE
		},
		contributor: {
			type: DataTypes.BOOLEAN,
			defaultValue: false,
			isBoolean: {
				msg: "Can only be a true or false value"
			}
		},
		userWallOptOut: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		cookieOptOut: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		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
		},
		executive: {
			type: DataTypes.BOOLEAN,
			defaultValue: false
		},
		developerMode: {
			type: DataTypes.BOOLEAN,
			defaultValue: false,
			validate: {
				isBoolean: {
					msg: 'Developer mode can only be true or 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
		},
		level: {
			type: DataTypes.BIGINT,
			required: true,
			default: 1
		},
		levelProgress: {
			type: DataTypes.BIGINT,
			required: true,
			default: 25
		},
		passwordResetExpiry: {
			type: DataTypes.DATE,
			required: false
		},
		passwordResetOptOut: {
			type: DataTypes.BOOLEAN,
			default: false,
			required: false
		},
		passwordResetEnabled: {
			type: DataTypes.BOOLEAN,
			required: false,
		},
		deleteCode: {
			type: DataTypes.TEXT,
			required: false,
			default: false,
			defaultValue: false
		},
		deleteEnabled: {
			type: DataTypes.BOOLEAN,
			default: false,
			defaultValue: false
		},
		jwtOffset: {
			type: DataTypes.BIGINT,
			defaultValue: 0
		},
		picture: {
			type: DataTypes.TEXT('long'),
			validate: {
				isString (val) {
					if(typeof val !== 'string') {
						throw new sequelize.ValidationError('avatar must be a string')
					}
				}
			}
		}
	}, {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
			}
		}
	})
	User.associate = function (models) {
		User.hasMany(models.Post)
		User.hasMany(models.Thread)
		User.hasMany(models.userWall)
		User.hasMany(models.Inventory)
		User.hasMany(models.Transaction)
		User.hasMany(models.AuditLog)
		User.hasMany(models.Item)
		User.belongsToMany(models.Conversation, { through: models.UserConversation })
		User.belongsToMany(models.Ip, { through: 'UserIp' })
	}
	User.prototype.getMeta = function (limit) {
		let Post = sequelize.models.Post
		let meta = {}

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

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

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

		return meta
	}
	User.includeWallOptions = function (from, limit) {
		let models = sequelize.models

		return [{
			model: models.userWall,
			limit,
			where: { postNumber: { $gte: from } },
			order: [['id', 'ASC']]
		}]
	}
	User.includeOptions = function (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']]
		}]
	}
	User.prototype.comparePassword = function (password) {
		return bcrypt.compare(password, this.hash)
	}
	User.prototype.destroyVerifyPassword = function (password) {
		if(typeof password !== 'string') {
			throw Errors.invalidLoginCredentials
		}

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

		if(correctPassword) {
			this.destroy()
		} else {
			throw Errors.invalidLoginCredentials
		}
	}
	User.prototype.removeKoins = function(amount) {
		if(this.koins >= amount) {
			this.update({koins: this.koins - amount})
		} else {
			throw Errors.insufficientKoins
		}
	}
	User.prototype.rand = function() {
		this.update({ emailToken: cryptoRandomString({length: 250})})
	}
	User.prototype.randPasswordReset = function() {
		if(User) {
			this.update({ passwordResetToken: cryptoRandomString({length: 250}), deleteEnabled: true })
		} else {
			throw Errors.accountDoesNotExist
		}
	}
	User.prototype.randAccountDelete = function() {
		if(User) {
			this.update({ deleteCode: cryptoRandomString({length: 1024}), deleteEnabled: true})
		} else {
			throw Errors.accountDoesNotExist
		}
	}
	User.prototype.emailVerify = function () {
		this.update({ emailVerified: true })
	}
	User.prototype.updatePassword = function (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 = bcrypt.compare(currentPassword, this.hash)

		if(correctPassword) {
			this.update({ hash: newPassword })
		} else {
			throw Errors.invalidLoginCredentials
		}
	}
	User.prototype.reward  = function () {
		let ms = Date.now() - this.lastRewardDate
		let dayMs = 1000*60*60*24

		//Has less than 1 day passed
		//since generating token?
		return ms / dayMs < 1
	}
	User.prototype.doReward  = function () {
		if(User.lastRewardDate) {
			if(User.lastRewardDate.reward()) {
				throw Errors.invalidToken
			} else {
				throw Errors.invalidPassKey
			}
		} else {
			console.log("idk")
		}
	}
	User.prototype.invalidateJWT  = function () {
		this.update({ jwtOffset: this.jwtOffset + 1 })
	}
	User.prototype.recoveryUpdatePassword = function (password) {
		if(typeof password !== 'string') {
			throw new sequelize.ValidationError('Please enter your password')
		}
		this.update({ hash: password, passwordResetEnabled: false })
	}
	/* User.prototype.GenerateJWT = function () {
        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)
        });
    }, */
	User.prototype.updateEmail = function (emailCurrentPassword, newEmail) {
		if(typeof emailCurrentPassword !== 'string' || typeof newEmail !== 'string') {
			throw new sequelize.ValidationError('input must be a string')
		}

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

		if(correctPassword) {
			this.update({ email: newEmail, emailVerified: false, emailToken: cryptoRandomString({length: 16})})
		} else {
			throw Errors.invalidLoginCredentials
		}
	}
	User.prototype.updateUsername = function (username, password) {
		if(typeof username !== 'string') {
			throw new sequelize.ValidationError('Please enter your new username')
		}
		if(typeof password !== 'string') {
			throw new sequelize.ValidationError('Please enter your password')
		}

		let correctPassword = bcrypt.compare(password, this.hash)
		if(correctPassword) {
			this.update({username: username, koins: this.koins - 200})
		} else {
			throw Errors.invalidLoginCredentials
		}
	}
	return User
}
