/**
 * @class BaseMod 数据模型基类，提供基础服务支持
 */
const {
	getConfig
} = require('../../shared')
//基类
module.exports = class BaseMod {
	constructor() {
		//配置信息
		this.config = getConfig('config')
		//开启/关闭debug
		this.debug = this.config.debug
		//主键
		this.primaryKey = '_id'
		//单次查询最多返回 500 条数据(阿里云500，腾讯云1000，这里取最小值)
		this.selectMaxLimit = 500
		//数据表前缀
		this.tablePrefix = 'uni-stat'
		//数据表连接符
		this.tableConnectors = '-'
		//数据表名
		this.tableName = ''
		//参数
		this.params = {}
		//数据库连接
		this._dbConnection()
		//redis连接
		this._redisConnection()
	}

	/**
	 * 建立uniCloud数据库连接
	 */
	_dbConnection() {
		if (!this.db) {
			try {
				this.db = uniCloud.database()
				this.dbCmd = this.db.command
				this.dbAggregate = this.dbCmd.aggregate
			} catch (e) {
				console.error('database connection failed: ' + e)
				throw new Error('database connection failed: ' + e)
			}
		}
	}

	/**
	 * 建立uniCloud redis连接
	 */
	_redisConnection() {
		if (this.config.redis && !this.redis) {
			try {
				this.redis = uniCloud.redis()
			} catch (e) {
				console.log('redis server connection failed: ' + e)
			}
		}
	}

	/**
	 * 获取uni统计配置项
	 * @param {String} key
	 */
	getConfig(key) {
		return this.config[key]
	}

	/**
	 * 获取带前缀的数据表名称
	 * @param {String} tab 表名
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	getTableName(tab, useDBPre = true) {
		tab = tab || this.tableName
		const table = (useDBPre && this.tablePrefix && tab.indexOf(this.tablePrefix) !== 0) ? this.tablePrefix + this
			.tableConnectors + tab : tab
		return table
	}

	/**
	 * 获取数据集
	 * @param {String} tab表名
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	getCollection(tab, useDBPre = true) {
		return this.db.collection(this.getTableName(tab, useDBPre))
	}

	/**
	 * 获取reids缓存
	 * @param {String} key reids缓存键值
	 */
	async getCache(key) {
		if (!this.redis || !key) {
			return false
		}
		let cacheResult = await this.redis.get(key)

		if (this.debug) {
			console.log('get cache result by key:' + key, cacheResult)
		}

		if (cacheResult) {
			try {
				cacheResult = JSON.parse(cacheResult)
			} catch (e) {
				if (this.debug) {
					console.log('json parse error: ' + e)
				}
			}
		}
		return cacheResult
	}

	/**
	 * 设置redis缓存
	 * @param {String} key 键值
	 * @param {String} val 值
	 * @param {Number} expireTime 过期时间
	 */
	async setCache(key, val, expireTime) {
		if (!this.redis || !key) {
			return false
		}

		if (val instanceof Object) {
			val = JSON.stringify(val)
		}

		if (this.debug) {
			console.log('set cache result by key:' + key, val)
		}

		return await this.redis.set(key, val, 'EX', expireTime || this.config.cachetime)
	}

	/**
	 * 清除redis缓存
	 * @param {String} key 键值
	 */
	async clearCache(key) {
		if (!this.redis || !key) {
			return false
		}

		if (this.debug) {
			console.log('delete cache by key:' + key)
		}

		return await this.redis.del(key)
	}

	/**
	 * 通过数据表主键（_id）获取数据
	 * @param {String} tab 表名
	 * @param {String} id 主键值
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async getById(tab, id, useDBPre = true) {
		const condition = {}
		condition[this.primaryKey] = id
		const info = await this.getCollection(tab, useDBPre).where(condition).get()
		return (info && info.data.length > 0) ? info.data[0] : []
	}

	/**
	 * 插入数据到数据表
	 * @param {String} tab 表名
	 * @param {Object} params 字段参数
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async insert(tab, params, useDBPre = true) {
		params = params || this.params
		return await this.getCollection(tab, useDBPre).add(params)
	}

	/**
	 * 修改数据表数据
	 * @param {String} tab 表名
	 * @param {Object} params 字段参数
	 * @param {Object} condition 条件
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async update(tab, params, condition, useDBPre = true) {
		params = params || this.params
		return await this.getCollection(tab).where(condition).update(params)
	}

	/**
	 * 删除数据表数据
	 * @param {String} tab 表名
	 * @param {Object} condition 条件
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async delete(tab, condition, useDBPre = true) {
		if (!condition) {
			return false
		}
		return await this.getCollection(tab, useDBPre).where(condition).remove()
	}

	/**
	 * 批量插入 - 云服务空间对单条mongo语句执行时间有限制，所以批量插入需限制每次执行条数
	 * @param {String} tab 表名
	 * @param {Object} data 数据集合
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async batchInsert(tab, data, useDBPre = true) {
		let batchInsertNum = this.getConfig('batchInsertNum') || 3000
		batchInsertNum = Math.min(batchInsertNum, 5000)
		const insertNum = Math.ceil(data.length / batchInsertNum)
		let start;
		let end;
		let fillData;
		let insertRes;
		const res = {
			code: 0,
			msg: 'success',
			data: {
				inserted: 0
			}
		}
		for (let p = 0; p < insertNum; p++) {
			start = p * batchInsertNum
			end = Math.min(start + batchInsertNum, data.length)
			fillData = []
			for (let i = start; i < end; i++) {
				fillData.push(data[i])
			}
			if (fillData.length > 0) {
				insertRes = await this.insert(tab, fillData, useDBPre)
				if (insertRes && insertRes.inserted) {
					res.data.inserted += insertRes.inserted
				}
			}
		}
		return res
	}

	/**
	 * 批量删除 - 云服务空间对单条mongo语句执行时间有限制，所以批量删除需限制每次执行条数
	 * @param {String} tab 表名
	 * @param {Object} condition 条件
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async batchDelete(tab, condition, useDBPre = true) {
		const batchDeletetNum = 5000;
		let deleteIds;
		let delRes;
		let thisCondition
		const res = {
			code: 0,
			msg: 'success',
			data: {
				deleted: 0
			}
		}
		let run = true
		while (run) {
			const dataRes = await this.getCollection(tab).where(condition).limit(batchDeletetNum).get()
			if (dataRes && dataRes.data.length > 0) {
				deleteIds = []
				for (let i = 0; i < dataRes.data.length; i++) {
					deleteIds.push(dataRes.data[i][this.primaryKey])
				}
				if (deleteIds.length > 0) {
					thisCondition = {}
					thisCondition[this.primaryKey] = {
						$in: deleteIds
					}
					delRes = await this.delete(tab, thisCondition, useDBPre)
					if (delRes && delRes.deleted) {
						res.data.deleted += delRes.deleted
					}
				}
			} else {
				run = false
			}
		}
		return res
	}

	/**
	 * 基础查询
	 * @param {String} tab 表名
	 * @param {Object} params 查询参数 where：where条件，field：返回字段，skip：跳过的文档数，limit：返回的记录数，orderBy：排序，count：返回查询结果的数量
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async select(tab, params, useDBPre = true) {
		const {
			where,
			field,
			skip,
			limit,
			orderBy,
			count
		} = params

		const query = this.getCollection(tab, useDBPre)

		//拼接where条件
		if (where) {
			if (where.length > 0) {
				where.forEach(key => {
					query.where(where[key])
				})
			} else {
				query.where(where)
			}
		}

		//排序
		if (orderBy) {
			Object.keys(orderBy).forEach(key => {
				query.orderBy(key, orderBy[key])
			})
		}

		//指定跳过的文档数
		if (skip) {
			query.skip(skip)
		}

		//指定返回的记录数
		if (limit) {
			query.limit(limit)
		}

		//指定返回字段
		if (field) {
			query.field(field)
		}

		//指定返回查询结果数量
		if (count) {
			return await query.count()
		}

		//返回查询结果数据
		return await query.get()
	}

	/**
	 * 查询并返回全部数据
	 * @param {String} tab 表名
	 * @param {Object} condition 条件
	 * @param {Object} field 指定查询返回字段
	 * @param {Boolean} useDBPre 是否使用数据表前缀
	 */
	async selectAll(tab, condition, field = {}, useDBPre = true) {
		const countRes = await this.getCollection(tab, useDBPre).where(condition).count()
		if (countRes && countRes.total > 0) {
			const pageCount = Math.ceil(countRes.total / this.selectMaxLimit)
			let res, returnData
			for (let p = 0; p < pageCount; p++) {
				res = await this.getCollection(tab, useDBPre).where(condition).orderBy(this.primaryKey, 'asc').skip(p *
					this.selectMaxLimit).limit(this.selectMaxLimit).field(field).get()
				if (!returnData) {
					returnData = res
				} else {
					returnData.affectedDocs += res.affectedDocs
					for (const i in res.data) {
						returnData.data.push(res.data[i])
					}
				}
			}
			return returnData
		}
		return {
			affectedDocs: 0,
			data: []
		}
	}

	/**
	 * 聚合查询
	 * @param {String} tab 表名
	 * @param {Object} params 聚合参数
	 */
	async aggregate(tab, params) {
		let {
			project,
			match,
			lookup,
			group,
			skip,
			limit,
			sort,
			getAll,
			useDBPre,
			addFields
		} = params
		//useDBPre 是否使用数据表前缀
		useDBPre = (useDBPre !== null && useDBPre !== undefined) ? useDBPre : true
		const query = this.getCollection(tab, useDBPre).aggregate()

		//设置返回字段
		if (project) {
			query.project(project)
		}

		//设置匹配条件
		if (match) {
			query.match(match)
		}

		//数据表关联
		if (lookup) {
			query.lookup(lookup)
		}

		//分组
		if (group) {
			if (group.length > 0) {
				for (const gi in group) {
					query.group(group[gi])
				}
			} else {
				query.group(group)
			}
		}

		//添加字段
		if (addFields) {
			query.addFields(addFields)
		}

		//排序
		if (sort) {
			query.sort(sort)
		}

		//分页
		if (skip) {
			query.skip(skip)
		}
		if (limit) {
			query.limit(limit)
		} else if (!getAll) {
			query.limit(this.selectMaxLimit)
		}

		//如果未指定全部返回则直接返回查询结果
		if (!getAll) {
			return await query.end()
		}

		//若指定了全部返回则分页查询全部结果后再返回
		const resCount = await query.group({
			_id: {},
			aggregate_count: {
				$sum: 1
			}
		}).end()

		if (resCount && resCount.data.length > 0 && resCount.data[0].aggregate_count > 0) {
			//分页查询
			const total = resCount.data[0].aggregate_count
			const pageCount = Math.ceil(total / this.selectMaxLimit)
			let res, returnData
			params.limit = this.selectMaxLimit
			params.getAll = false
			//结果合并
			for (let p = 0; p < pageCount; p++) {
				params.skip = p * params.limit
				res = await this.aggregate(tab, params)
				if (!returnData) {
					returnData = res
				} else {
					returnData.affectedDocs += res.affectedDocs
					for (const i in res.data) {
						returnData.data.push(res.data[i])
					}
				}
			}
			return returnData
		} else {
			return {
				affectedDocs: 0,
				data: []
			}
		}
	}
}
