/**
 * @class StatResult 基础数据结果统计模型
 */
const BaseMod = require('./base')
const Platform = require('./platform')
const Channel = require('./channel')
const Version = require('./version')
const SessionLog = require('./sessionLog')
const UserSessionLog = require('./userSessionLog')
const ErrorLog = require('./errorLog')
const ActiveDevices = require('./activeDevices')
const ActiveUsers = require('./activeUsers')
const UniIDUsers = require('./uniIDUsers')
const {
	DateTime
} = require('../lib')
module.exports = class StatResult extends BaseMod {
	constructor() {
		super()
		this.tableName = 'result'
		this.platforms = []
		this.channels = []
		this.versions = []
	}

	/**
	 * 基础数据统计
	 * @param {String} type 统计类型 hour：实时统计 day：按天统计，week：按周统计 month：按月统计
	 * @param {Date|Time} date 指定日期或时间戳
	 * @param {Boolean} reset 是否重置，为ture时会重置该批次数据
	 */
	async stat(type, date, reset) {
		const allowedType = ['hour', 'day', 'week', 'month']
		if (!allowedType.includes(type)) {
			return {
				code: 1002,
				msg: 'This type is not allowed'
			}
		}

		if (this.debug) {
			console.log('result --type:' + type + ', date:' + date + ', reset:' + reset)
		}

		this.fillType = type
		const dateTime = new DateTime()
		const dateDimension = dateTime.getTimeDimensionByType(type, -1, date)
		this.startTime = dateDimension.startTime
		this.endTime = dateDimension.endTime
		if (this.debug) {
			console.log('dimension time', this.startTime + '--' + this.endTime)
		}

		// 查看当前时间段日志是否已存在,防止重复生成
		if (!reset) {
			const checkRes = await this.getCollection(this.tableName).where({
				dimension: this.fillType,
				start_time: this.startTime,
				end_time: this.endTime
			}).get()
			if (checkRes.data.length > 0) {
				console.log('log have existed')
				return {
					code: 1003,
					msg: 'This log have existed'
				}
			}
		} else {
			const delRes = await this.delete(this.tableName, {
				start_time: this.startTime,
				end_time: this.endTime
			})
			console.log('delete old data result:', JSON.stringify(delRes))
		}

		// 周月数据单独统计
		if (['week', 'month'].includes(this.fillType)) {
			return await this.statWeekOrMonth()
		}

		// 数据获取
		this.sessionLog = new SessionLog()
		const statRes = await this.aggregate(this.sessionLog.tableName, {
			project: {
				appid: 1,
				version: 1,
				platform: 1,
				channel: 1,
				is_first_visit: 1,
				page_count: 1,
				duration: 1,
				create_time: 1
			},
			match: {
				create_time: {
					$gte: this.startTime,
					$lte: this.endTime
				}
			},
			group: {
				_id: {
					appid: '$appid',
					version: '$version',
					platform: '$platform',
					channel: '$channel'
				},
				new_device_count: {
					$sum: '$is_first_visit'
				},
				page_view_count: {
					$sum: '$page_count'
				},
				total_duration: {
					$sum: '$duration'
				},
				session_times: {
					$sum: 1
				}
			},
			sort: {
				new_device_count: 1,
				page_view_count: 1,
				session_times: 1
			},
			getAll: true
		})

		let res = {
			code: 0,
			msg: 'success'
		}
		if (this.debug) {
			console.log('statRes', JSON.stringify(statRes))
		}

		this.fillData = []
		this.composes = []
		if (statRes.data.length > 0) {
			for (const i in statRes.data) {
				await this.fill(statRes.data[i])
			}
		}
		//补充数据
		await this.replenishStat()
		if (this.fillData.length > 0) {
			res = await this.batchInsert(this.tableName, this.fillData)
		}
		return res
	}

	/**
	 * 按周/月统计
	 */
	async statWeekOrMonth() {
		const statRes = await this.aggregate(this.tableName, {
			project: {
				appid: 1,
				version_id: 1,
				platform_id: 1,
				channel_id: 1,
				new_device_count: 1,
				new_user_count: 1,
				page_visit_count: 1,
				user_visit_times: 1,
				app_launch_count: 1,
				error_count: 1,
				bounce_times: 1,
				duration: 1,
				user_duration: 1,
				dimension: 1,
				start_time: 1
			},
			match: {
				dimension: 'day',
				start_time: {
					$gte: this.startTime,
					$lte: this.endTime
				}
			},
			group: {
				_id: {
					appid: '$appid',
					version_id: '$version_id',
					platform_id: '$platform_id',
					channel_id: '$channel_id'
				},
				new_device_count: {
					$sum: '$new_device_count'
				},
				new_user_count: {
					$sum: '$new_user_count'
				},
				error_count: {
					$sum: '$error_count'
				},
				page_count: {
					$sum: '$page_visit_count'
				},
				total_duration: {
					$sum: '$duration'
				},
				total_user_duration: {
					$sum: '$user_duration'
				},
				total_user_session_times: {
					$sum: '$user_session_times'
				},
				session_times: {
					$sum: '$app_launch_count'
				},
				total_bounce_times: {
					$sum: '$bounce_times'
				}
			},
			sort: {
				new_device_count: 1,
				error_count: 1,
				page_count: 1
			},
			getAll: true
		})

		let res = {
			code: 0,
			msg: 'success'
		}
		if (this.debug) {
			console.log('statRes', JSON.stringify(statRes))
		}

		this.activeDevices = new ActiveDevices()
		this.activeUsers = new ActiveUsers()
		this.fillData = []
		this.composes = []
		if (statRes.data.length > 0) {
			for (const i in statRes.data) {
				await this.getWeekOrMonthData(statRes.data[i])
			}
		}
		//补充数据
		await this.replenishStat()
		if (this.fillData.length > 0) {
			res = await this.batchInsert(this.tableName, this.fillData)
		}
		return res
	}

	/**
	 * 获取周/月维度的填充数据
	 * @param {Object} data 统计数据
	 */
	async getWeekOrMonthData(data) {

		const matchCondition = {
			...data._id,
			create_time: {
				$gte: this.startTime,
				$lte: this.endTime
			}
		}

		// 查询活跃设备数
		const statVisitDeviceRes = await this.getCollection(this.activeDevices.tableName).where({
			...matchCondition,
			dimension: this.fillType
		}).count()

		let activeDeviceCount = 0
		if (statVisitDeviceRes && statVisitDeviceRes.total > 0) {
			activeDeviceCount = statVisitDeviceRes.total
		}

		// 设备次均停留时长
		let avgSessionTime = 0
		if (data.total_duration > 0 && data.session_times > 0) {
			avgSessionTime = Math.round(data.total_duration / data.session_times)
		}

		// 设均停留时长
		let avgDeviceTime = 0
		if (data.total_duration > 0 && activeDeviceCount > 0) {
			avgDeviceTime = Math.round(data.total_duration / activeDeviceCount)
		}

		// 跳出率
		let bounceRate = 0
		if (data.total_bounce_times > 0 && data.session_times > 0) {
			bounceRate = data.total_bounce_times * 100 / data.session_times
			bounceRate = parseFloat(bounceRate.toFixed(2))
		}

		// 累计设备数
		let totalDevices = data.new_device_count
		const totalDeviceRes = await this.getCollection(this.tableName).where({
			...matchCondition,
			dimension: this.fillType,
			start_time: {
				$lt: this.startTime
			}
		}).orderBy('start_time', 'desc').limit(1).get()
		if (totalDeviceRes && totalDeviceRes.data.length > 0) {
			totalDevices += totalDeviceRes.data[0].total_devices
		}

		//活跃用户数
		const statVisitUserRes = await this.getCollection(this.activeUsers.tableName).where({
			...matchCondition,
			dimension: this.fillType
		}).count()
		let activeUserCount = 0
		if (statVisitUserRes && statVisitUserRes.total > 0) {
			activeUserCount = statVisitUserRes.total
		}

		// 平台信息
		let platformInfo = null
		if (this.platforms && this.platforms[data._id.platform_id]) {
			platformInfo = this.platforms[data._id.platform_id]
		} else {
			const platform = new Platform()
			platformInfo = await this.getById(platform.tableName, data._id.platform_id)
			if (!platformInfo || platformInfo.length === 0) {
				platformInfo.code = ''
			}
			this.platforms[data._id.platform_id] = platformInfo
		}

		// 渠道信息
		let channelInfo = null
		if (this.channels && this.channels[data._id.channel_id]) {
			channelInfo = this.channels[data._id.channel_id]
		} else {
			const channel = new Channel()
			channelInfo = await this.getById(channel.tableName, data._id.channel_id)
			if (!channelInfo || channelInfo.length === 0) {
				channelInfo.channel_code = ''
			}
			this.channels[data._id.channel_id] = channelInfo
		}

		// 版本信息
		let versionInfo = null
		if (this.versions && this.versions[data._id.version_id]) {
			versionInfo = this.versions[data._id.version_id]
		} else {
			const version = new Version()
			versionInfo = await this.getById(version.tableName, data._id.version_id, false)
			if (!versionInfo || versionInfo.length === 0) {
				versionInfo.version = ''
			}
			this.versions[data._id.version_id] = versionInfo
		}

		//总用户数
		const uniIDUsers = new UniIDUsers()
		let totalUserCount = await uniIDUsers.getUserCount(data._id.appid, platformInfo.code, channelInfo.channel_code, versionInfo.version, {
			$lte: this.endTime
		})

		//人均停留时长
		let avgUserTime = 0
		if (data.total_user_duration > 0 && activeUserCount > 0) {
			avgUserTime = Math.round(data.total_user_duration / activeUserCount)
		}

		//用户次均访问时长
		let avgUserSessionTime = 0
		if (data.total_user_duration > 0 && data.total_user_session_times > 0) {
			avgUserSessionTime = Math.round(data.total_user_duration / data.total_user_session_times)
		}

		//安卓平台活跃设备数需要减去sdk更新后device_id发生变更的设备数
		if(platformInfo.code === 'android') {
			try{
				const statVisitOldDeviceRes = await this.getCollection(this.activeDevices.tableName).where({
					...data._id,
					create_time: {
						$gte: this.startTime,
						$lte: this.endTime
					},
					dimension: this.fillType + '-old'
				}).count()
				if (statVisitOldDeviceRes && statVisitOldDeviceRes.total > 0) {
					// 活跃设备留存数
					activeDeviceCount -= statVisitOldDeviceRes.total
					//平均设备停留时长
					avgDeviceTime = Math.round(data.total_duration / activeDeviceCount)
				}
			}  catch (e) {
				console.log('server error: ' + e)
			}
		}

		const insertParam = {
			appid: data._id.appid,
			platform_id: data._id.platform_id,
			channel_id: data._id.channel_id,
			version_id: data._id.version_id,
			//总设备数
			total_devices: totalDevices,
			//本时间段新增设备数
			new_device_count: data.new_device_count,
			//登录用户会话次数
			user_session_times: data.total_user_session_times,
			//活跃设备数
			active_device_count: activeDeviceCount,
			//总用户数
			total_users: totalUserCount,
			//新增用户数
			new_user_count: data.new_user_count,
			//活跃用户数
			active_user_count: activeUserCount,
			//应用启动次数 = 设备会话次数
			app_launch_count: data.session_times,
			//页面访问次数
			page_visit_count: data.page_view_count,
			//错误次数
			error_count: data.error_count,
			//会话总访问时长
			duration: data.total_duration,
			//用户会话总访问时长
			user_duration: data.total_user_duration,
			avg_device_session_time: avgSessionTime,
			avg_user_session_time: avgUserSessionTime,
			avg_device_time: avgDeviceTime,
			avg_user_time: avgUserTime,
			bounce_times: data.total_bounce_times,
			bounce_rate: bounceRate,
			retention: {},
			dimension: this.fillType,
			stat_date: new DateTime().getDate('Ymd', this.startTime),
			start_time: this.startTime,
			end_time: this.endTime
		}

		this.fillData.push(insertParam)
		this.composes.push(data._id.appid + '_' + data._id.platform_id + '_' + data._id.channel_id + '_' + data
			._id.version_id)

		return insertParam
	}

	/**
	 * 基础填充数据-目前只有小时和天维度的数据
	 * @param {Object} data 统计数据
	 */
	async fill(data) {
		// 平台信息
		let platformInfo = null
		if (this.platforms && this.platforms[data._id.platform]) {
			//暂存下数据，减少读库
			platformInfo = this.platforms[data._id.platform]
		} else {
			const platform = new Platform()
			platformInfo = await platform.getPlatformAndCreate(data._id.platform, null)
			if (!platformInfo || platformInfo.length === 0) {
				platformInfo._id = ''
			}
			this.platforms[data._id.platform] = platformInfo
			if (this.debug) {
				console.log('platformInfo', JSON.stringify(platformInfo))
			}
		}

		// 渠道信息
		let channelInfo = null
		const channelKey = data._id.appid + '_' + platformInfo._id + '_' + data._id.channel
		if (this.channels && this.channels[channelKey]) {
			channelInfo = this.channels[channelKey]
		} else {
			const channel = new Channel()
			channelInfo = await channel.getChannelAndCreate(data._id.appid, platformInfo._id, data._id.channel)
			if (!channelInfo || channelInfo.length === 0) {
				channelInfo._id = ''
			}
			this.channels[channelKey] = channelInfo
			if (this.debug) {
				console.log('channelInfo', JSON.stringify(channelInfo))
			}
		}

		// 版本信息
		let versionInfo = null
		const versionKey = data._id.appid + '_' + data._id.platform + '_' + data._id.version
		if (this.versions && this.versions[versionKey]) {
			versionInfo = this.versions[versionKey]
		} else {
			const version = new Version()
			versionInfo = await version.getVersionAndCreate(data._id.appid, data._id.platform, data._id.version)
			if (!versionInfo || versionInfo.length === 0) {
				versionInfo._id = ''
			}
			this.versions[versionKey] = versionInfo
			if (this.debug) {
				console.log('versionInfo', JSON.stringify(versionInfo))
			}
		}

		// 访问设备数统计
		const matchCondition = data._id
		Object.assign(matchCondition, {
			create_time: {
				$gte: this.startTime,
				$lte: this.endTime
			}
		})
		const statVisitDeviceRes = await this.aggregate(this.sessionLog.tableName, {
			project: {
				appid: 1,
				version: 1,
				platform: 1,
				channel: 1,
				device_id: 1,
				create_time: 1
			},
			match: matchCondition,
			group: [{
				_id: {
					device_id: '$device_id'
				}
			}, {
				_id: {},
				total_devices: {
					$sum: 1
				}
			}]
		})
		let activeDeviceCount = 0
		if (statVisitDeviceRes.data.length > 0) {
			activeDeviceCount = statVisitDeviceRes.data[0].total_devices
		}

		//安卓平台活跃设备数需要减去sdk更新后device_id发生变更的设备数
		if(platformInfo.code === 'android') {
			const oldDeviceRes = await this.aggregate(this.sessionLog.tableName, {
				project: {
					appid: 1,
					version: 1,
					platform: 1,
					channel: 1,
					old_device_id: 1,
					create_time: 1
				},
				match: matchCondition,
				group: {
					_id: {
						device_id: '$old_device_id'
					},
					create_time: {
						$min: '$create_time'
					},
					sessionCount: {
						$sum: 1
					}
				},
				sort: {
					create_time: 1,
					sessionCount: 1
				},
				getAll: true
			})

			if(oldDeviceRes.data.length) {
				const thisOldDeviceIds = []
				for (const tau in oldDeviceRes.data) {
					if(oldDeviceRes.data[tau]._id.device_id) {
						thisOldDeviceIds.push(oldDeviceRes.data[tau]._id.device_id)
					}
				}
				const statVisitOldDeviceRes = await this.aggregate(this.sessionLog.tableName, {
					project: {
						appid: 1,
						version: 1,
						platform: 1,
						channel: 1,
						device_id: 1,
						create_time: 1
					},
					match: {
						...matchCondition,
						device_id: {
							$in: thisOldDeviceIds
						}
					},
					group: [{
						_id: {
							device_id: '$device_id'
						}
					}, {
						_id: {},
						total_devices: {
							$sum: 1
						}
					}]
				})

				if (this.debug) {
					console.log('statVisitOldDeviceRes', JSON.stringify(statVisitOldDeviceRes))
				}
				if (statVisitOldDeviceRes && statVisitOldDeviceRes.data.length > 0) {
					// 活跃设备留存数
					activeDeviceCount -= statVisitOldDeviceRes.data[0].total_devices
				}
			}
		}

		// 错误数量统计
		const errorLog = new ErrorLog()
		const statErrorRes = await this.getCollection(errorLog.tableName).where(matchCondition).count()
		let errorCount = 0
		if (statErrorRes && statErrorRes.total > 0) {
			errorCount = statErrorRes.total
		}

		// 设备的次均停留时长，设备访问总时长/设备会话次数
		let avgSessionTime = 0
		if (data.total_duration > 0 && data.session_times > 0) {
			avgSessionTime = Math.round(data.total_duration / data.session_times)
		}

		// 设均停留时长
		let avgDeviceTime = 0
		if (data.total_duration > 0 && activeDeviceCount > 0) {
			avgDeviceTime = Math.round(data.total_duration / activeDeviceCount)
		}

		// 跳出率
		let bounceTimes = 0
		const bounceRes = await this.getCollection(this.sessionLog.tableName).where({
			...matchCondition,
			page_count: 1
		}).count()
		if (bounceRes && bounceRes.total > 0) {
			bounceTimes = bounceRes.total
		}
		let bounceRate = 0
		if (bounceTimes > 0 && data.session_times > 0) {
			bounceRate = bounceTimes * 100 / data.session_times
			bounceRate = parseFloat(bounceRate.toFixed(2))
		}

		// 应用启动次数 = 会话次数
		const launchCount = data.session_times

		// 累计设备数
		let totalDevices = data.new_device_count
		const totalDeviceRes = await this.getCollection(this.tableName).where({
			appid: data._id.appid,
			platform_id: platformInfo._id,
			channel_id: channelInfo._id,
			version_id: versionInfo._id,
			dimension: this.fillType,
			start_time: {
				$lt: this.startTime
			}
		}).orderBy('start_time', 'desc').limit(1).get()

		if (totalDeviceRes && totalDeviceRes.data.length > 0) {
			totalDevices += totalDeviceRes.data[0].total_devices
		}

		//活跃用户数
		const userSessionLog = new UserSessionLog()
		let activeUserCount = 0
		const activeUserCountRes = await this.aggregate(userSessionLog.tableName, {
			project: {
				appid: 1,
				version: 1,
				platform: 1,
				channel: 1,
				uid: 1,
				create_time: 1
			},
			match: matchCondition,
			group: [{
				_id: {
					uid: '$uid'
				}
			}, {
				_id: {},
				total_users: {
					$sum: 1
				}
			}]
		})

		if (activeUserCountRes && activeUserCountRes.data.length > 0) {
			activeUserCount = activeUserCountRes.data[0].total_users
		}

		//新增用户数
		const uniIDUsers = new UniIDUsers()
		let newUserCount = await uniIDUsers.getUserCount(matchCondition.appid, matchCondition.platform,
			matchCondition.channel, matchCondition.version, {
				$gte: this.startTime,
				$lte: this.endTime
			})

		//总用户数
		let totalUserCount = await uniIDUsers.getUserCount(matchCondition.appid, matchCondition.platform,
			matchCondition.channel, matchCondition.version, {
				$lte: this.endTime
			})


		//用户停留总时长及总会话次数
		let totalUserDuration = 0
		let totalUserSessionTimes = 0
		const totalUserDurationRes = await this.aggregate(userSessionLog.tableName, {
			project: {
				appid: 1,
				version: 1,
				platform: 1,
				channel: 1,
				duration: 1,
				create_time: 1
			},
			match: matchCondition,
			group: [{
				_id: {},
				total_duration: {
					$sum: "$duration"
				},
				total_session_times: {
					$sum: 1
				}
			}]
		})
		if (totalUserDurationRes && totalUserDurationRes.data.length > 0) {
			totalUserDuration = totalUserDurationRes.data[0].total_duration
			totalUserSessionTimes = totalUserDurationRes.data[0].total_session_times
		}

		//人均停留时长
		let avgUserTime = 0
		//用户次均访问时长
		let avgUserSessionTime = 0
		if (totalUserDuration > 0 && activeUserCount > 0) {
			avgUserTime = Math.round(totalUserDuration / activeUserCount)
			avgUserSessionTime = Math.round(totalUserDuration / totalUserSessionTimes)
		}

		//设置填充数据
		const datetime = new DateTime()
		const insertParam = {
			appid: data._id.appid,
			platform_id: platformInfo._id,
			channel_id: channelInfo._id,
			version_id: versionInfo._id,
			total_devices: totalDevices,
			new_device_count: data.new_device_count,
			active_device_count: activeDeviceCount,
			total_users: totalUserCount,
			new_user_count: newUserCount,
			active_user_count: activeUserCount,
			user_session_times: totalUserSessionTimes,
			app_launch_count: launchCount,
			page_visit_count: data.page_view_count,
			error_count: errorCount,
			duration: data.total_duration,
			user_duration: totalUserDuration,
			avg_device_session_time: avgSessionTime,
			avg_user_session_time: avgUserSessionTime,
			avg_device_time: avgDeviceTime,
			avg_user_time: avgUserTime,
			bounce_times: bounceTimes,
			bounce_rate: bounceRate,
			retention: {},
			dimension: this.fillType,
			stat_date: datetime.getDate('Ymd', this.startTime),
			start_time: this.startTime,
			end_time: this.endTime
		}

		//数据填充
		this.fillData.push(insertParam)
		this.composes.push(data._id.appid + '_' + platformInfo._id + '_' + channelInfo._id + '_' + versionInfo
			._id)
		return insertParam
	}

	/**
	 * 基础统计数据补充，防止累计数据丢失
	 */
	async replenishStat() {
		if (this.debug) {
			console.log('composes data', this.composes)
		}

		const datetime = new DateTime()
		// const {
		// 	startTime
		// } = datetime.getTimeDimensionByType(this.fillType, -1, this.startTime)

		//上一次统计时间
		let preStatRes = await this.getCollection(this.tableName).where({
			start_time: {$lt: this.startTime},
			dimension: this.fillType
		}).orderBy('start_time', 'desc').limit(1).get()
		let preStartTime = 0
		if(preStatRes && preStatRes.data.length > 0) {
			preStartTime = preStatRes.data[0].start_time
		}

		if (this.debug) {
			console.log('replenishStat-preStartTime', preStartTime)
		}

		if(!preStartTime) {
			return false
		}

		// 上一阶段数据
		const preStatData = await this.selectAll(this.tableName, {
			start_time: preStartTime,
			dimension: this.fillType
		}, {
			appid: 1,
			platform_id: 1,
			channel_id: 1,
			version_id: 1,
			total_devices: 1,
			total_users: 1
		})

		if (!preStatData || preStatData.data.length === 0) {
			return false
		}

		if (this.debug) {
			console.log('preStatData', JSON.stringify(preStatData))
		}

		let preKey
		const preKeyArr = []
		for (const pi in preStatData.data) {
			preKey = preStatData.data[pi].appid + '_' + preStatData.data[pi].platform_id + '_' + preStatData
				.data[pi].channel_id + '_' + preStatData.data[pi].version_id
			if (!this.composes.includes(preKey) && !preKeyArr.includes(preKey)) {
				preKeyArr.push(preKey)
				if (this.debug) {
					console.log('preKey -add', preKey)
				}
				this.fillData.push({
					appid: preStatData.data[pi].appid,
					platform_id: preStatData.data[pi].platform_id,
					channel_id: preStatData.data[pi].channel_id,
					version_id: preStatData.data[pi].version_id,
					total_devices: preStatData.data[pi].total_devices,
					new_device_count: 0,
					active_device_count: 0,
					total_users: preStatData.data[pi].total_users,
					new_user_count: 0,
					active_user_count: 0,
					user_session_times: 0,
					app_launch_count: 0,
					page_visit_count: 0,
					error_count: 0,
					duration: 0,
					user_duration: 0,
					avg_device_session_time: 0,
					avg_user_session_time: 0,
					avg_device_time: 0,
					avg_user_time: 0,
					bounce_times: 0,
					bounce_rate: 0,
					retention: {},
					dimension: this.fillType,
					stat_date: datetime.getDate('Ymd', this.startTime),
					start_time: this.startTime,
					end_time: this.endTime
				})
			} else if (this.debug) {
				console.log('preKey -have', preKey)
			}
		}

		return true
	}

	/**
	 * 留存数据统计
	 * @param {String} type 统计类型 hour：实时统计 day：按天统计，week：按周统计 month：按月统计
	 * @param {Date|Time} date 指定日期或时间戳
	 * @param {String} mod 统计模块 device:设备，user:用户
	 */
	async retentionStat(type, date, mod = 'device') {
		date = date ? date : new DateTime().getTimeBySetDays(-1, date)
		const allowedType = ['day', 'week', 'month']
		if (!allowedType.includes(type)) {
			return {
				code: 1002,
				msg: 'This type is not allowed'
			}
		}
		let days = []
		switch (type) {
			case 'week':
			case 'month':
				days = [1, 2, 3, 4, 5, 6, 7, 8, 9]
				break
			default:
				days = [1, 2, 3, 4, 5, 6, 7, 14, 30]
				break
		}

		let res = {
			code: 0,
			msg: 'success'
		}

		for (const day in days) {
			//留存统计数据库查询较为复杂，因此这里拆分为多个维度
			if (mod && mod === 'user') {
				//用户留存统计
				if (type === 'day') {
					res = await this.userRetentionFillDayly(type, days[day], date)
				} else {
					res = await this.userRetentionFillWeekOrMonth(type, days[day], date)
				}
			} else {
				//设备留存统计
				if (type === 'day') {
					res = await this.deviceRetentionFillDayly(type, days[day], date)
				} else {
					res = await this.deviceRetentionFillWeekOrMonth(type, days[day], date)
				}
			}
		}
		return res
	}

	/**
	 * 设备日留存统计数据填充
	 * @param {String} type 统计类型 hour：实时统计 day：按天统计，week：按周统计 month：按月统计
	 * @param {Date|Time} date 指定日期或时间戳
	 * @param {Boolean} reset 是否重置，为ture时会重置该批次数据
	 */
	async deviceRetentionFillDayly(type, day, date) {
		if (type !== 'day') {
			return {
				code: 301,
				msg: 'Type error:' + type
			}
		}
		const dateTime = new DateTime()
		const {
			startTime,
			endTime
		} = dateTime.getTimeDimensionByType(type, 0 - day, date)

		if (!startTime || !endTime) {
			return {
				code: 1001,
				msg: 'The statistic time get failed'
			}
		}

		// 截止时间范围
		const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)

		// 获取当时批次的统计日志
		const resultLogRes = await this.selectAll(this.tableName, {
			dimension: type,
			start_time: startTime,
			end_time: endTime
		})
		// const resultLogRes = await this.getCollection(this.tableName).where({
		//   dimension: type,
		//   start_time: startTime,
		//   end_time: endTime
		// }).get()

		if (this.debug) {
			console.log('resultLogRes', JSON.stringify(resultLogRes))
		}

		if (!resultLogRes || resultLogRes.data.length === 0) {
			if (this.debug) {
				console.log('Not found this log --' + type + ':' + day + ', start:' + startTime + ',endTime:' +
					endTime)
			}
			return {
				code: 1000,
				msg: 'Not found this log'
			}
		}

		const sessionLog = new SessionLog()
		const platform = new Platform()
		const channel = new Channel()
		const version = new Version()
		let res = null
		for (const resultIndex in resultLogRes.data) {
			const resultLog = resultLogRes.data[resultIndex]
			// 平台信息
			let platformInfo = null
			if (this.platforms && this.platforms[resultLog.platform_id]) {
				platformInfo = this.platforms[resultLog.platform_id]
			} else {
				platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
				if (!platformInfo || platformInfo.length === 0) {
					platformInfo.code = ''
				}
				this.platforms[resultLog.platform_id] = platformInfo
				if (this.debug) {
					console.log('platformInfo', JSON.stringify(platformInfo))
				}
			}
			// 渠道信息
			let channelInfo = null
			if (this.channels && this.channels[resultLog.channel_id]) {
				channelInfo = this.channels[resultLog.channel_id]
			} else {
				channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
				if (!channelInfo || channelInfo.length === 0) {
					channelInfo.channel_code = ''
				}
				this.channels[resultLog.channel_id] = channelInfo
				if (this.debug) {
					console.log('channelInfo', JSON.stringify(channelInfo))
				}
			}
			// 版本信息
			let versionInfo = null
			if (this.versions && this.versions[resultLog.version_id]) {
				versionInfo = this.versions[resultLog.version_id]
			} else {
				versionInfo = await this.getById(version.tableName, resultLog.version_id, false)
				if (!versionInfo || versionInfo.length === 0) {
					versionInfo.version = ''
				}
				this.versions[resultLog.version_id] = versionInfo
				if (this.debug) {
					console.log('versionInfo', JSON.stringify(versionInfo))
				}
			}

			// 获取该批次的活跃设备数
			const activeDeviceRes = await this.aggregate(sessionLog.tableName, {
				project: {
					appid: 1,
					version: 1,
					platform: 1,
					channel: 1,
					device_id: 1,
					create_time: 1
				},
				match: {
					appid: resultLog.appid,
					version: versionInfo.version,
					platform: platformInfo.code,
					channel: channelInfo.channel_code,
					create_time: {
						$gte: startTime,
						$lte: endTime
					}
				},
				group: {
					_id: {
						device_id: '$device_id'
					},
					create_time: {
						$min: '$create_time'
					},
					sessionCount: {
						$sum: 1
					}
				},
				sort: {
					create_time: 1,
					sessionCount: 1
				},
				getAll: true
			})

			if (this.debug) {
				console.log('activeDeviceRes', JSON.stringify(activeDeviceRes))
			}
			let activeDeviceRate = 0
			let activeDevices = 0
			if (activeDeviceRes && activeDeviceRes.data.length > 0) {
				const thisDayActiveDevices = activeDeviceRes.data.length
				const thisDayActiveDeviceIds = []
				for (const tau in activeDeviceRes.data) {
					thisDayActiveDeviceIds.push(activeDeviceRes.data[tau]._id.device_id)
				}
				if (this.debug) {
					console.log('thisDayActiveDeviceIds', JSON.stringify(thisDayActiveDeviceIds))
				}

				// 留存活跃设备查询
				const retentionActiveDeviceRes = await this.aggregate(sessionLog.tableName, {
					project: {
						appid: 1,
						version: 1,
						platform: 1,
						channel: 1,
						device_id: 1,
						create_time: 1
					},
					match: {
						appid: resultLog.appid,
						version: versionInfo.version,
						platform: platformInfo.code,
						channel: channelInfo.channel_code,
						device_id: {
							$in: thisDayActiveDeviceIds
						},
						create_time: {
							$gte: lastTimeInfo.startTime,
							$lte: lastTimeInfo.endTime
						}
					},
					group: [{
						_id: {
							device_id: '$device_id'
						}
					}, {
						_id: {},
						total_devices: {
							$sum: 1
						}
					}]
				})

				if (this.debug) {
					console.log('retentionActiveDeviceRes', JSON.stringify(retentionActiveDeviceRes))
				}
				if (retentionActiveDeviceRes && retentionActiveDeviceRes.data.length > 0) {
					// 活跃设备留存数
					activeDevices = retentionActiveDeviceRes.data[0].total_devices
					// 活跃设备留存率
					activeDeviceRate = parseFloat((activeDevices * 100 / thisDayActiveDevices).toFixed(2))
				}

				//安卓平台留存需要增加sdk更新后device_id发生变更的设备数
				if(platformInfo.code === 'android') {
					const retentionActiveOldDeviceRes = await this.aggregate(sessionLog.tableName, {
						project: {
							appid: 1,
							version: 1,
							platform: 1,
							channel: 1,
							old_device_id: 1,
							create_time: 1
						},
						match: {
							appid: resultLog.appid,
							version: versionInfo.version,
							platform: platformInfo.code,
							channel: channelInfo.channel_code,
							old_device_id: {
								$in: thisDayActiveDeviceIds
							},
							create_time: {
								$gte: lastTimeInfo.startTime,
								$lte: lastTimeInfo.endTime
							}
						},
						group: [{
							_id: {
								device_id: '$old_device_id'
							}
						}, {
							_id: {},
							total_devices: {
								$sum: 1
							}
						}]
					})

					if (this.debug) {
						console.log('retentionActiveOldDeviceRes', JSON.stringify(retentionActiveOldDeviceRes))
					}
					if (retentionActiveOldDeviceRes && retentionActiveOldDeviceRes.data.length > 0) {
						// 活跃设备留存数
						activeDevices += retentionActiveOldDeviceRes.data[0].total_devices
						// 活跃设备留存率
						activeDeviceRate = parseFloat((activeDevices * 100 / thisDayActiveDevices).toFixed(2))
					}
				}
			}

			// 获取该批次新增设备数
			const newDeviceRes = await this.aggregate(sessionLog.tableName, {
				project: {
					appid: 1,
					version: 1,
					platform: 1,
					channel: 1,
					is_first_visit: 1,
					device_id: 1,
					create_time: 1
				},
				match: {
					appid: resultLog.appid,
					version: versionInfo.version,
					platform: platformInfo.code,
					channel: channelInfo.channel_code,
					is_first_visit: 1,
					create_time: {
						$gte: startTime,
						$lte: endTime
					}
				},
				group: {
					_id: {
						device_id: '$device_id'
					},
					create_time: {
						$min: '$create_time'
					},
					sessionCount: {
						$sum: 1
					}
				},
				sort: {
					create_time: 1,
					sessionCount: 1
				},
				getAll: true
			})

			let newDeviceRate = 0
			let newDevices = 0
			if (newDeviceRes && newDeviceRes.data.length > 0) {
				const thisDayNewDevices = newDeviceRes.data.length
				const thisDayNewDeviceIds = []
				for (const tau in newDeviceRes.data) {
					thisDayNewDeviceIds.push(newDeviceRes.data[tau]._id.device_id)
				}

				if (this.debug) {
					console.log('thisDayNewDeviceIds', JSON.stringify(thisDayNewDeviceIds))
				}

				// 留存的设备查询
				const retentionNewDeviceRes = await this.aggregate(sessionLog.tableName, {
					project: {
						appid: 1,
						version: 1,
						platform: 1,
						channel: 1,
						device_id: 1,
						create_time: 1
					},
					match: {
						appid: resultLog.appid,
						version: versionInfo.version,
						platform: platformInfo.code,
						channel: channelInfo.channel_code,
						device_id: {
							$in: thisDayNewDeviceIds
						},
						create_time: {
							$gte: lastTimeInfo.startTime,
							$lte: lastTimeInfo.endTime
						}
					},
					group: [{
						_id: {
							device_id: '$device_id'
						}
					}, {
						_id: {},
						total_devices: {
							$sum: 1
						}
					}]
				})

				if (retentionNewDeviceRes && retentionNewDeviceRes.data.length > 0) {
					// 新增设备留存数
					newDevices = retentionNewDeviceRes.data[0].total_devices
					// 新增设备留存率
					newDeviceRate = parseFloat((newDevices * 100 / thisDayNewDevices).toFixed(2))
				}

				//安卓平台留存需要增加sdk更新后device_id发生变更的设备数
				if(platformInfo.code === 'android') {
					const retentionNewOldDeviceRes = await this.aggregate(sessionLog.tableName, {
						project: {
							appid: 1,
							version: 1,
							platform: 1,
							channel: 1,
							old_device_id: 1,
							create_time: 1
						},
						match: {
							appid: resultLog.appid,
							version: versionInfo.version,
							platform: platformInfo.code,
							channel: channelInfo.channel_code,
							old_device_id: {
								$in: thisDayNewDeviceIds
							},
							create_time: {
								$gte: lastTimeInfo.startTime,
								$lte: lastTimeInfo.endTime
							}
						},
						group: [{
							_id: {
								device_id: '$old_device_id'
							}
						}, {
							_id: {},
							total_devices: {
								$sum: 1
							}
						}]
					})

					if (this.debug) {
						console.log('retentionNewOldDeviceRes', JSON.stringify(retentionNewOldDeviceRes))
					}

					if (retentionNewOldDeviceRes && retentionNewOldDeviceRes.data.length > 0) {
						// 新增设备留存数
						newDevices += retentionNewOldDeviceRes.data[0].total_devices
						// 新增设备留存率
						newDeviceRate = parseFloat((newDevices * 100 / thisDayNewDevices).toFixed(2))
					}
				}
			}

			// 数据更新
			const retentionData = resultLog.retention
			const dataKey = type.substr(0, 1) + '_' + day
			if (!retentionData.active_device) {
				retentionData.active_device = {}
			}
			retentionData.active_device[dataKey] = {
				device_count: activeDevices,
				device_rate: activeDeviceRate
			}
			if (!retentionData.new_device) {
				retentionData.new_device = {}
			}
			retentionData.new_device[dataKey] = {
				device_count: newDevices,
				device_rate: newDeviceRate
			}

			if (this.debug) {
				console.log('retentionData', JSON.stringify(retentionData))
			}

			res = await this.update(this.tableName, {
				retention: retentionData
			}, {
				_id: resultLog._id
			})
		}

		if (res && res.updated) {
			return {
				code: 0,
				msg: 'success'
			}
		} else {
			return {
				code: 500,
				msg: 'retention data update failed'
			}
		}
	}

	/**
	 * 设备周/月留存数据填充
	 * @param {String} type 统计类型 hour：实时统计 day：按天统计，week：按周统计 month：按月统计
	 * @param {Date|Time} date 指定日期或时间戳
	 * @param {Boolean} reset 是否重置，为ture时会重置该批次数据
	 */
	async deviceRetentionFillWeekOrMonth(type, day, date) {
		if (!['week', 'month'].includes(type)) {
			return {
				code: 301,
				msg: 'Type error:' + type
			}
		}
		const dateTime = new DateTime()
		const {
			startTime,
			endTime
		} = dateTime.getTimeDimensionByType(type, 0 - day, date)

		if (!startTime || !endTime) {
			return {
				code: 1001,
				msg: 'The statistic time get failed'
			}
		}

		// 截止时间范围
		const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)

		// 获取当时批次的统计日志
		const resultLogRes = await this.selectAll(this.tableName, {
			dimension: type,
			start_time: startTime,
			end_time: endTime
		})

		if (this.debug) {
			console.log('resultLogRes', JSON.stringify(resultLogRes))
		}

		if (!resultLogRes || resultLogRes.data.length === 0) {
			if (this.debug) {
				console.log('Not found this session log --' + type + ':' + day + ', start:' + startTime +
					',endTime:' + endTime)
			}
			return {
				code: 1000,
				msg: 'Not found this session log'
			}
		}

		const activeDevicesObj = new ActiveDevices()
		let res = null;
		let activeDeviceRes;
		let activeDeviceRate;
		let activeDevices;
		let newDeviceRate;
		let newDevices
		for (const resultIndex in resultLogRes.data) {
			const resultLog = resultLogRes.data[resultIndex]
			// 获取该批次的活跃设备数
			activeDeviceRes = await this.selectAll(activeDevicesObj.tableName, {
				appid: resultLog.appid,
				platform_id: resultLog.platform_id,
				channel_id: resultLog.channel_id,
				version_id: resultLog.version_id,
				dimension: type,
				create_time: {
					$gte: startTime,
					$lte: endTime
				}
			}, {
				device_id: 1
			})

			if (this.debug) {
				console.log('activeDeviceRes', JSON.stringify(activeDeviceRes))
			}

			activeDeviceRate = 0
			activeDevices = 0
			if (activeDeviceRes && activeDeviceRes.data.length > 0) {
				const thisDayActiveDevices = activeDeviceRes.data.length
				const thisDayActiveDeviceIds = []
				for (const tau in activeDeviceRes.data) {
					thisDayActiveDeviceIds.push(activeDeviceRes.data[tau].device_id)
				}
				if (this.debug) {
					console.log('thisDayActiveDeviceIds', JSON.stringify(thisDayActiveDeviceIds))
				}
				// 留存活跃设备数
				const retentionActiveDeviceRes = await this.getCollection(activeDevicesObj.tableName).where({
					appid: resultLog.appid,
					platform_id: resultLog.platform_id,
					channel_id: resultLog.channel_id,
					version_id: resultLog.version_id,
					device_id: {
						$in: thisDayActiveDeviceIds
					},
					dimension: type,
					create_time: {
						$gte: lastTimeInfo.startTime,
						$lte: lastTimeInfo.endTime
					}
				}).count()

				if (this.debug) {
					console.log('retentionActiveDeviceRes', JSON.stringify(retentionActiveDeviceRes))
				}
				if (retentionActiveDeviceRes && retentionActiveDeviceRes.total > 0) {
					// 活跃设备留存数
					activeDevices = retentionActiveDeviceRes.total
					// 活跃设备留存率
					activeDeviceRate = parseFloat((activeDevices * 100 / thisDayActiveDevices).toFixed(2))
				}
			}

			// 获取该批次的新增设备数
			const newDeviceRes = await this.selectAll(activeDevicesObj.tableName, {
				appid: resultLog.appid,
				platform_id: resultLog.platform_id,
				channel_id: resultLog.channel_id,
				version_id: resultLog.version_id,
				is_new: 1,
				dimension: type,
				create_time: {
					$gte: startTime,
					$lte: endTime
				}
			}, {
				device_id: 1
			})

			newDeviceRate = 0
			newDevices = 0
			if (newDeviceRes && newDeviceRes.data.length > 0) {
				const thisDayNewDevices = newDeviceRes.data.length
				const thisDayNewDeviceIds = []
				for (const tau in newDeviceRes.data) {
					thisDayNewDeviceIds.push(newDeviceRes.data[tau].device_id)
				}

				// 新增设备留存数
				const retentionNewDeviceRes = await this.getCollection(activeDevicesObj.tableName).where({
					appid: resultLog.appid,
					platform_id: resultLog.platform_id,
					channel_id: resultLog.channel_id,
					version_id: resultLog.version_id,
					device_id: {
						$in: thisDayNewDeviceIds
					},
					dimension: type,
					create_time: {
						$gte: lastTimeInfo.startTime,
						$lte: lastTimeInfo.endTime
					}
				}).count()

				if (retentionNewDeviceRes && retentionNewDeviceRes.total > 0) {
					// 新增设备留存数
					newDevices = retentionNewDeviceRes.total
					// 新增设备留存率
					newDeviceRate = parseFloat((newDevices * 100 / thisDayNewDevices).toFixed(2))
				}
			}

			// 数据更新
			const retentionData = resultLog.retention
			const dataKey = type.substr(0, 1) + '_' + day
			if (!retentionData.active_device) {
				retentionData.active_device = {}
			}
			retentionData.active_device[dataKey] = {
				device_count: activeDevices,
				device_rate: activeDeviceRate
			}
			if (!retentionData.new_device) {
				retentionData.new_device = {}
			}
			retentionData.new_device[dataKey] = {
				device_count: newDevices,
				device_rate: newDeviceRate
			}

			if (this.debug) {
				console.log('retentionData', JSON.stringify(retentionData))
			}

			res = await this.update(this.tableName, {
				retention: retentionData
			}, {
				_id: resultLog._id
			})
		}

		if (res && res.updated) {
			return {
				code: 0,
				msg: 'success'
			}
		} else {
			return {
				code: 500,
				msg: 'retention data update failed'
			}
		}
	}

	/**
	 * 用户日留存数据填充
	 * @param {String} type 统计类型 hour：实时统计 day：按天统计，week：按周统计 month：按月统计
	 * @param {Date|Time} date 指定日期或时间戳
	 * @param {Boolean} reset 是否重置，为ture时会重置该批次数据
	 */
	async userRetentionFillDayly(type, day, date) {
		if (type !== 'day') {
			return {
				code: 301,
				msg: 'Type error:' + type
			}
		}
		const dateTime = new DateTime()
		const {
			startTime,
			endTime
		} = dateTime.getTimeDimensionByType(type, 0 - day, date)

		if (!startTime || !endTime) {
			return {
				code: 1001,
				msg: 'The statistic time get failed'
			}
		}

		// 截止时间范围
		const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)

		// 获取当时批次的统计日志
		const resultLogRes = await this.selectAll(this.tableName, {
			dimension: type,
			start_time: startTime,
			end_time: endTime
		})

		if (this.debug) {
			console.log('resultLogRes', JSON.stringify(resultLogRes))
		}

		if (!resultLogRes || resultLogRes.data.length === 0) {
			if (this.debug) {
				console.log('Not found this log --' + type + ':' + day + ', start:' + startTime + ',endTime:' +
					endTime)
			}
			return {
				code: 1000,
				msg: 'Not found this log'
			}
		}

		const userSessionLog = new UserSessionLog()
		const uniIDUsers = new UniIDUsers()
		const platform = new Platform()
		const channel = new Channel()
		const version = new Version()
		let res = null
		for (const resultIndex in resultLogRes.data) {
			const resultLog = resultLogRes.data[resultIndex]
			// 平台信息
			let platformInfo = null
			if (this.platforms && this.platforms[resultLog.platform_id]) {
				platformInfo = this.platforms[resultLog.platform_id]
			} else {
				platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
				if (!platformInfo || platformInfo.length === 0) {
					platformInfo.code = ''
				}
				this.platforms[resultLog.platform_id] = platformInfo
				if (this.debug) {
					console.log('platformInfo', JSON.stringify(platformInfo))
				}
			}
			// 渠道信息
			let channelInfo = null
			if (this.channels && this.channels[resultLog.channel_id]) {
				channelInfo = this.channels[resultLog.channel_id]
			} else {
				channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
				if (!channelInfo || channelInfo.length === 0) {
					channelInfo.channel_code = ''
				}
				this.channels[resultLog.channel_id] = channelInfo
				if (this.debug) {
					console.log('channelInfo', JSON.stringify(channelInfo))
				}
			}
			// 版本信息
			let versionInfo = null
			if (this.versions && this.versions[resultLog.version_id]) {
				versionInfo = this.versions[resultLog.version_id]
			} else {
				versionInfo = await this.getById(version.tableName, resultLog.version_id, false)
				if (!versionInfo || versionInfo.length === 0) {
					versionInfo.version = ''
				}
				this.versions[resultLog.version_id] = versionInfo
				if (this.debug) {
					console.log('versionInfo', JSON.stringify(versionInfo))
				}
			}

			// 获取该时间段内的活跃用户
			const activeUserRes = await this.aggregate(userSessionLog.tableName, {
				project: {
					appid: 1,
					version: 1,
					platform: 1,
					channel: 1,
					uid: 1,
					create_time: 1
				},
				match: {
					appid: resultLog.appid,
					version: versionInfo.version,
					platform: platformInfo.code,
					channel: channelInfo.channel_code,
					create_time: {
						$gte: startTime,
						$lte: endTime
					}
				},
				group: {
					_id: {
						uid: '$uid'
					},
					create_time: {
						$min: '$create_time'
					},
					sessionCount: {
						$sum: 1
					}
				},
				sort: {
					create_time: 1,
					sessionCount: 1
				},
				getAll: true
			})

			if (this.debug) {
				console.log('activeUserRes', JSON.stringify(activeUserRes))
			}

			//活跃用户留存率
			let activeUserRate = 0
			//活跃用户留存数
			let activeUsers = 0
			if (activeUserRes && activeUserRes.data.length > 0) {
				const thisDayActiveUsers = activeUserRes.data.length
				//获取该时间段内的活跃用户编号，这里没用lookup联查是因为数据量较大时lookup效率很低
				const thisDayActiveUids = []
				for (let tau in activeUserRes.data) {
					thisDayActiveUids.push(activeUserRes.data[tau]._id.uid)
				}

				if (this.debug) {
					console.log('thisDayActiveUids', JSON.stringify(thisDayActiveUids))
				}

				// 留存活跃用户数
				const retentionActiveUserRes = await this.aggregate(userSessionLog.tableName, {
					project: {
						appid: 1,
						version: 1,
						platform: 1,
						channel: 1,
						uid: 1,
						create_time: 1
					},
					match: {
						appid: resultLog.appid,
						version: versionInfo.version,
						platform: platformInfo.code,
						channel: channelInfo.channel_code,
						uid: {
							$in: thisDayActiveUids
						},
						create_time: {
							$gte: lastTimeInfo.startTime,
							$lte: lastTimeInfo.endTime
						}
					},
					group: [{
						_id: {
							uid: '$uid'
						}
					}, {
						_id: {},
						total_users: {
							$sum: 1
						}
					}]
				})

				if (this.debug) {
					console.log('retentionActiveUserRes', JSON.stringify(retentionActiveUserRes))
				}
				if (retentionActiveUserRes && retentionActiveUserRes.data.length > 0) {
					// 活跃用户留存数
					activeUsers = retentionActiveUserRes.data[0].total_users
					// 活跃用户留存率
					activeUserRate = parseFloat((activeUsers * 100 / thisDayActiveUsers).toFixed(2))
				}
			}



			//新增用户编号
			const thisDayNewUids = await uniIDUsers.getUserIds(resultLog.appid, platformInfo.code, channelInfo.channel_code, versionInfo.version, {
				$gte: startTime,
				$lte: endTime
			})
			//新增用户留存率
			let newUserRate = 0
			//新增用户留存数
			let newUsers = 0
			if (thisDayNewUids.length > 0) {
				// 现在依然活跃的用户数
				const retentionNewUserRes = await this.aggregate(userSessionLog.tableName, {
					project: {
						appid: 1,
						version: 1,
						platform: 1,
						channel: 1,
						uid: 1,
						create_time: 1
					},
					match: {
						appid: resultLog.appid,
						version: versionInfo.version,
						platform: platformInfo.code,
						channel: channelInfo.channel_code,
						uid: {
							$in: thisDayNewUids
						},
						create_time: {
							$gte: lastTimeInfo.startTime,
							$lte: lastTimeInfo.endTime
						}
					},
					group: [{
						_id: {
							uid: '$uid'
						}
					}, {
						_id: {},
						total_users: {
							$sum: 1
						}
					}]
				})

				if (retentionNewUserRes && retentionNewUserRes.data.length > 0) {
					// 新增用户留存数
					newUsers = retentionNewUserRes.data[0].total_users
					// 新增用户留存率
					newUserRate = parseFloat((newUsers * 100 / thisDayNewUids.length).toFixed(2))
				}
			}

			// 数据更新
			const retentionData = resultLog.retention
			const dataKey = type.substr(0, 1) + '_' + day
			if (!retentionData.active_user) {
				retentionData.active_user = {}
			}
			retentionData.active_user[dataKey] = {
				user_count: activeUsers,
				user_rate: activeUserRate
			}
			if (!retentionData.new_user) {
				retentionData.new_user = {}
			}
			retentionData.new_user[dataKey] = {
				user_count: newUsers,
				user_rate: newUserRate
			}

			if (this.debug) {
				console.log('retentionData', JSON.stringify(retentionData))
			}

			res = await this.update(this.tableName, {
				retention: retentionData
			}, {
				_id: resultLog._id
			})
		}

		if (res && res.updated) {
			return {
				code: 0,
				msg: 'success'
			}
		} else {
			return {
				code: 500,
				msg: 'retention data update failed'
			}
		}
	}


	/**
	 * 用户周/月留存数据填充
	 * @param {String} type 统计类型 hour：实时统计 day：按天统计，week：按周统计 month：按月统计
	 * @param {Date|Time} date 指定日期或时间戳
	 * @param {Boolean} reset 是否重置，为ture时会重置该批次数据
	 */
	async userRetentionFillWeekOrMonth(type, day, date) {
		if (!['week', 'month'].includes(type)) {
			return {
				code: 301,
				msg: 'Type error:' + type
			}
		}
		const dateTime = new DateTime()
		const {
			startTime,
			endTime
		} = dateTime.getTimeDimensionByType(type, 0 - day, date)

		if (!startTime || !endTime) {
			return {
				code: 1001,
				msg: 'The statistic time get failed'
			}
		}

		// 截止时间范围
		const lastTimeInfo = dateTime.getTimeDimensionByType(type, 0, date)

		// 获取当时批次的统计日志
		const resultLogRes = await this.selectAll(this.tableName, {
			dimension: type,
			start_time: startTime,
			end_time: endTime
		})

		if (this.debug) {
			console.log('resultLogRes', JSON.stringify(resultLogRes))
		}

		if (!resultLogRes || resultLogRes.data.length === 0) {
			if (this.debug) {
				console.log('Not found this session log --' + type + ':' + day + ', start:' + startTime +
					',endTime:' + endTime)
			}
			return {
				code: 1000,
				msg: 'Not found this session log'
			}
		}

		const activeUserObj = new ActiveUsers()
		const uniIDUsers = new UniIDUsers()
		const platform = new Platform()
		const channel = new Channel()
		const version = new Version()
		let res = null
		//活跃用户留存率
		let activeUserRate
		//活跃用户留存数
		let activeUsers
		//新增用户留存率
		let newUserRate
		//新增用户留存数
		let newUsers
		for (const resultIndex in resultLogRes.data) {
			const resultLog = resultLogRes.data[resultIndex]

			// 平台信息
			let platformInfo = null
			if (this.platforms && this.platforms[resultLog.platform_id]) {
				platformInfo = this.platforms[resultLog.platform_id]
			} else {
				platformInfo = await this.getById(platform.tableName, resultLog.platform_id)
				if (!platformInfo || platformInfo.length === 0) {
					platformInfo.code = ''
				}
				this.platforms[resultLog.platform_id] = platformInfo
				if (this.debug) {
					console.log('platformInfo', JSON.stringify(platformInfo))
				}
			}
			// 渠道信息
			let channelInfo = null
			if (this.channels && this.channels[resultLog.channel_id]) {
				channelInfo = this.channels[resultLog.channel_id]
			} else {
				channelInfo = await this.getById(channel.tableName, resultLog.channel_id)
				if (!channelInfo || channelInfo.length === 0) {
					channelInfo.channel_code = ''
				}
				this.channels[resultLog.channel_id] = channelInfo
				if (this.debug) {
					console.log('channelInfo', JSON.stringify(channelInfo))
				}
			}
			// 版本信息
			let versionInfo = null
			if (this.versions && this.versions[resultLog.version_id]) {
				versionInfo = this.versions[resultLog.version_id]
			} else {
				versionInfo = await this.getById(version.tableName, resultLog.version_id, false)
				if (!versionInfo || versionInfo.length === 0) {
					versionInfo.version = ''
				}
				this.versions[resultLog.version_id] = versionInfo
				if (this.debug) {
					console.log('versionInfo', JSON.stringify(versionInfo))
				}
			}

			// 获取该批次的活跃用户数
			const activeUserRes = await this.selectAll(activeUserObj.tableName, {
				appid: resultLog.appid,
				platform_id: resultLog.platform_id,
				channel_id: resultLog.channel_id,
				version_id: resultLog.version_id,
				dimension: type,
				create_time: {
					$gte: startTime,
					$lte: endTime
				}
			}, {
				uid: 1
			})

			if (this.debug) {
				console.log('activeUserRes', JSON.stringify(activeUserRes))
			}

			activeUserRate = 0
			activeUsers = 0
			if (activeUserRes && activeUserRes.data.length > 0) {
				const thisDayactiveUsers = activeUserRes.data.length
				const thisDayActiveDeviceIds = []
				for (const tau in activeUserRes.data) {
					thisDayActiveDeviceIds.push(activeUserRes.data[tau].uid)
				}
				if (this.debug) {
					console.log('thisDayActiveDeviceIds', JSON.stringify(thisDayActiveDeviceIds))
				}
				// 留存活跃用户数
				const retentionactiveUserRes = await this.getCollection(activeUserObj.tableName).where({
					appid: resultLog.appid,
					platform_id: resultLog.platform_id,
					channel_id: resultLog.channel_id,
					version_id: resultLog.version_id,
					uid: {
						$in: thisDayActiveDeviceIds
					},
					dimension: type,
					create_time: {
						$gte: lastTimeInfo.startTime,
						$lte: lastTimeInfo.endTime
					}
				}).count()

				if (this.debug) {
					console.log('retentionactiveUserRes', JSON.stringify(retentionactiveUserRes))
				}
				if (retentionactiveUserRes && retentionactiveUserRes.total > 0) {
					// 活跃用户留存数
					activeUsers = retentionactiveUserRes.total
					// 活跃用户留存率
					activeUserRate = parseFloat((activeUsers * 100 / thisDayactiveUsers).toFixed(2))
				}
			}


			//新增用户编号
			const thisDayNewUids = await uniIDUsers.getUserIds(resultLog.appid, platformInfo.code, channelInfo.channel_code, versionInfo.version, {
				$gte: startTime,
				$lte: endTime
			})

			// 新增用户留存率
			newUserRate = 0
			// 新增用户留存数
			newUsers = 0
			if(thisDayNewUids && thisDayNewUids.length > 0) {
				// 新增用户留存数
				const retentionnewUserRes = await this.getCollection(activeUserObj.tableName).where({
					appid: resultLog.appid,
					platform_id: resultLog.platform_id,
					channel_id: resultLog.channel_id,
					version_id: resultLog.version_id,
					uid: {
						$in: thisDayNewUids
					},
					dimension: type,
					create_time: {
						$gte: lastTimeInfo.startTime,
						$lte: lastTimeInfo.endTime
					}
				}).count()

				if (retentionnewUserRes && retentionnewUserRes.total > 0) {
					// 新增用户留存数
					newUsers = retentionnewUserRes.total
					// 新增用户留存率
					newUserRate = parseFloat((newUsers * 100 / thisDayNewUids.length).toFixed(2))
				}
			}

			// 数据更新
			const retentionData = resultLog.retention
			const dataKey = type.substr(0, 1) + '_' + day
			if (!retentionData.active_user) {
				retentionData.active_user = {}
			}
			retentionData.active_user[dataKey] = {
				user_count: activeUsers,
				user_rate: activeUserRate
			}
			if (!retentionData.new_user) {
				retentionData.new_user = {}
			}
			retentionData.new_user[dataKey] = {
				user_count: newUsers,
				user_rate: newUserRate
			}

			if (this.debug) {
				console.log('retentionData', JSON.stringify(retentionData))
			}

			res = await this.update(this.tableName, {
				retention: retentionData
			}, {
				_id: resultLog._id
			})
		}

		if (res && res.updated) {
			return {
				code: 0,
				msg: 'success'
			}
		} else {
			return {
				code: 500,
				msg: 'retention data update failed'
			}
		}
	}


	/**
	 * 清理实时统计的日志
	 * @param {Number} days 实时统计日志保留天数
	 */
	async cleanHourLog(days = 7) {

		console.log('clean hour logs - day:', days)

		const dateTime = new DateTime()

		const res = await this.delete(this.tableName, {
			dimension: 'hour',
			start_time: {
				$lt: dateTime.getTimeBySetDays(0 - days)
			}
		})

		if (!res.code) {
			console.log('clean hour logs - res:', res)
		}
	}
}
