const fs = require('fs-extra');
const access = require('@core/helpers/access');
const file_helper = require('@core/helpers/file');
const enums_radios = require('@core/utils/enum_radio.js');
const models = require('@app/models');

const Route = require('@core/abstract_routes/route');

class CoreApp extends Route {

	/**
	 * @constructor
	 * @param {array} [additionalRoutes] - Additional routes implemented in CoreEntity child class.
	 */
	constructor(additionalRoutes = []) {
		const registeredRoutes = [
			'status',
			'widgets',
			'change_language',
			'get_file',
			'download',
			...additionalRoutes
		];
		super(registeredRoutes)
	}

	status() {
		this.router.get('/status', (req, res) => {
			res.sendStatus(200);
		});
	}

	/**
	 * POST - Route that handle ajax call from application widgets
	 * @namespace CoreApp#widgets
	 */
	widgets() {
		this.router.post('/widgets', ...this.middlewares.widgets, this.asyncRoute(async(data) => {
			/**
			 * Called at route start
			 * @function CoreApp#widgets#start
			 * @memberof CoreApp#widgets
			 * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 */
			if (await this.getHook('widgets', 'start', data) === false)
				return;

			const user = data.req.session.passport.user;
			const widgetsInfo = data.req.body.widgets;
			const widgetsPromises = [];

			for (let i = 0; i < widgetsInfo.length; i++) {
				const currentWidget = widgetsInfo[i];
				const modelName = 'E_' + currentWidget.entity.substring(2);

				// Check group and role access to widget's entity
				if (!access.entityAccess(user.r_group, currentWidget.entity.substring(2)) || !access.actionAccess(user.r_role, currentWidget.entity.substring(2), 'read'))
					continue;

				widgetsPromises.push(((widget, model) => new Promise(resolve => {
					const widgetRes = {type: widget.type};
					switch (widget.type) {
						case 'info':
						case 'stats':
							models[model].count().then(widgetData => {
								widgetRes.data = widgetData;
								data[widget.widgetID] = widgetRes;
								resolve();
							}).catch(resolve);
							break;

						case 'piechart':
							if (!widget.field) {
								console.error('No field defined for widget piechart')
								return resolve();
							}
							// RELATED TO PIECHART
							if (widget.field.indexOf('r_') == 0) {
								// Find option matching wdiget's targeted alias
								let targetOption;
								try {
									const options = JSON.parse(fs.readFileSync(__appPath+'/models/options/'+model.toLowerCase()+'.json', 'utf8'));
									for (const option of options) {
										if (option.relation == 'belongsTo' && option.as == widget.field) {
											targetOption = option;
											break;
										}
									}
									if (!targetOption)
										throw new Error();
								} catch(e) {
									console.error("Couldn't load piechart for "+model+" on field "+widget.field);
									return resolve();
								}

								if (targetOption.target == 'e_status') {
									const statusAlias = widget.field;
									models[model].findAll({
										attributes: [statusAlias + '.f_name', statusAlias + '.f_color', [models.sequelize.fn('COUNT', 'id'), 'count']],
										group: [statusAlias + '.f_name', statusAlias + '.f_color', statusAlias + '.id'],
										include: {model: models.E_status, as: statusAlias},
										raw: true
									}).then((piechartData) => {
										const dataSet = {labels: [], backgroundColor: [], data: []};
										for (let i = 0; i < piechartData.length; i++) {
											if (dataSet.labels.indexOf(piechartData[i].f_name) != -1) {
												dataSet.data[dataSet.labels.indexOf(piechartData[i].f_name)] += piechartData[i].count
											} else {
												dataSet.labels.push(piechartData[i].f_name);
												dataSet.backgroundColor.push(piechartData[i].f_color);
												dataSet.data.push(piechartData[i].count);
											}
										}
										widgetRes.data = dataSet;
										data[widget.widgetID] = widgetRes;
										console.log(data[widget.widgetID]);
										resolve();
									}).catch(resolve);
								}
								else {
									// Build all variables required to query piechart data
									const using = targetOption.usingField ? targetOption.usingField : [{value:'id'}];
									const selectAttributes = [];
									for (const attr of using)
										selectAttributes.push('target.'+attr.value);
									const foreignKey = targetOption.foreignKey;
									const target = models['E'+targetOption.target.substring(1)].getTableName();
									const source = models[model].getTableName();

									models.sequelize.query(`
										SELECT
											count(source.id) count, ${selectAttributes.join(', ')}
										FROM
											${source} source
										LEFT JOIN
											${target} target
										ON
											target.id = source.${foreignKey}
										GROUP BY ${foreignKey}
									`, {type: models.sequelize.QueryTypes.SELECT}).then(piechartData => {
										const dataSet = {labels: [], data: []};
										for (const pie of piechartData) {
											const labels = [];
											for (const attr of using)
												labels.push(pie[attr.value])
											dataSet.labels.push(labels.join(' - '));
											dataSet.data.push(pie.count);
										}
										widgetRes.data = dataSet;
										data[widget.widgetID] = widgetRes;
										resolve();
									}).catch(resolve);
								}
							}
							// FIELD PIECHART
							else {
								models[model].findAll({
									attributes: [widget.field, [models.sequelize.fn('COUNT', 'id'), 'count']],
									group: [widget.field],
									raw: true
								}).then((piechartData) => {
									const dataSet = {labels: [], data: []};
									for (let i = 0; i < piechartData.length; i++) {
										let label = piechartData[i][widget.field];
										if (widget.fieldType == 'enum')
											label = enums_radios.translateFieldValue(widget.entity, widget.field, label, data.req.session.lang_user);

										if(dataSet.labels.indexOf(label) != -1)
											dataSet.data[dataSet.labels.indexOf(label)] += piechartData[i].count
										else {
											dataSet.labels.push(label);
											dataSet.data.push(piechartData[i].count);
										}
									}
									widgetRes.data = dataSet;
									data[widget.widgetID] = widgetRes;
									resolve();
								}).catch(resolve);
							}
							break;

						default:
							console.error(`Widget type '${widget.type}' not found`);
							resolve();
							break;
					}
				}))(currentWidget, modelName));
			}

			await Promise.all(widgetsPromises);

			/**
			 * Called before route end
			 * @function CoreApp#widgets#beforeSend
			 * @memberof CoreApp#widgets
			 * @param {object} data
			 * @param {object} data.req - Request - See expressjs definition
			 * @param {object} data.res - Response - See expressjs definition
			 * @param {object} data.transaction - Database transaction. Use this transaction in your hooks. Commit and rollback are handled through res.success() / res.error()
			 */
			if (await this.getHook('widgets', 'beforeSend', data) === false)
				return;

			data.res.success(_ => data.res.json({
				...data,
				transaction: undefined,
				req: undefined,
				res: undefined
			}));
		}));
	}

	change_language() {
		this.router.post('/change_language', ...this.middlewares.change_language, (req, res) => {
			req.session.lang_user = req.body.lang;
			res.locals.lang_user = req.body.lang;
			res.json({
				success: true
			});
		});
	}

	get_file() {
		this.router.get('/get_file', ...this.middlewares.get_file, this.asyncRoute(async (data) => {
			const entity = data.req.query.entity;
			const id = data.req.query.id;
			const field = data.req.query.field;

			if (!access.entityAccess(data.req.session.passport.user.r_group, entity.substring(2)))
				return data.res.error(_ => data.res.status(403).end());

			const row = await models[entity.capitalizeFirstLetter()].findOne({where: {id}});
			if (!row)
				return data.res.error(_ => data.res.status(404).end());

			const buffer = await file_helper.readBuffer(row[field]);
			data.res.success(_ => data.res.json({
				data: buffer,
				file: file_helper.originalFilename(row[field])
			}));
		}));
	}

	download() {
		this.router.get('/download', ...this.middlewares.download, this.asyncRoute(async (data) => {
			const entity = data.req.query.entity;
			const id = data.req.query.id;
			const field = data.req.query.field;

			if (!access.entityAccess(data.req.session.passport.user.r_group, entity.substring(2)))
				return data.res.error(_ => data.res.status(403).end());

			const row = await models[entity.capitalizeFirstLetter()].findOne({where: {id}});
			if (!row)
				return data.res.error(_ => data.res.status(404).end());

			const path = file_helper.fullPath(row[field]);
			const filename = file_helper.originalFilename(row[field]);
			data.res.success(_ => data.res.download(path, filename, function (err) {
				if (err)
					console.error(err);
			}));
		}));
	}
}

module.exports = CoreApp;