'use strict';

const _ = require('lodash');
const shortid = require('shortid');
const formidable = require('formidable');
const streamToPrimise = require('stream-to-promise'); // todo: cambiar el nombre....
const ipaddr = require('ipaddr.js');

class ControllerRequest {

    constructor(controller, action, query, body, params) {
        this.session = shortid.generate();
        this.controller = controller;
        this.action = action;
        this.query = _.cloneDeep(query);
        this.params = _.cloneDeep(params);
        this.queryRaw = _.cloneDeep(query);
        this.files = [];
        this.body = _.cloneDeep(body);
        this.bodyRaw = _.cloneDeep(body);
        this.created = new Date();
        this.cancel = null;
        this.user = null;
        this.authenticated = null;

        this.info = {
            url: null,
            remote_addr: null, // deprecated
            request_uri: null,
            useragent: null,
            ip: null,
            ips: null,
        };
    }

    elapsed() {
        return +Date.now() - +this.created;
    }

    getQuery(key, raw) {
        if(raw) {
            return this.queryRaw[key] || null;
        }

        return this.query[key] || null;
    }

    getUser() {
        return this.user;
    }

    setRemoteAddr(addr) {
        this.info.remote_addr = addr;
        return this;
    }

    setSessionId(session) {
        this.session = session;
        return this;
    }

    setUser(user) {
        this.user = user;
        return this;
    }

    setRequestUri(uri) {
        this.info.request_uri = uri;
        return this;
    }

    setUserAgent(agent) {
        this.info.useragent = agent;
        return this;
    }

    setAuthenticated(status) {
        this.authenticated = status;
        return this;
    }

    isAuthenticated() {
        return this.authenticated;
    }

    /* -------------------------------------------------------------------------
     * Clone
     * ---------------------------------------------------------------------- */

    clone() {

    }

    /* -------------------------------------------------------------------------
     * Utils
     * ---------------------------------------------------------------------- */

    toObject() {
        let obj = {};

        for(let key in this) {
            if (this.hasOwnProperty(key) && !key.match(/^_/)) {
                obj[key] = this[key];
            }
        }

        return obj;
    }

    /**
     * @param obj
     * @returns {ControllerRequest}
     */
    static factoryObject(obj) {
        if(obj instanceof ControllerRequest) {
            return obj;
        }

        let req = new ControllerRequest(obj.controller, obj.action, obj.query || {}, obj.body || {});

        for(let key in obj) {
            if(obj.hasOwnProperty(key) && !key.match(/^_/)) {
                req[key] = _.cloneDeep(obj[key]);
            }
        }

        return req;
    }

    static factory(req) {
        let namespace = req.params.namespace ? req.params.namespace + '.' : '';

        let request = new ControllerRequest(namespace + req.params.controller, req.params.action, req.query, req.body, req.params);

        request.setAuthenticated(req.isAuthenticated());
        request.setUser(req.user);
        request.setUserAgent(req.headers['user-agent']);
        request.setRequestUri(req.originalUrl);

        let ips = req.header('x-forwarded-for') || null;

        if(ips) {
            ips = ips.split(',').map(ip => {
                return ipaddr.process(ip).toString();
            });
        }

        request.info.ip = ipaddr.process(req.clientIp).toString();
        request.info.ips = ips || [];
        request.info.remote_addr = request.info.ip;
        request.info.url = req.url;
        request.info.method = req.method;

        if(req.query.session) {
            request.setSessionId(req.query.session);
        }

        return request;
    }

    static factoryMultipart(req) {
        return new Promise((resolve, reject) => {
            let form = new formidable.IncomingForm();

            form.multiples = true;
            form.keepExtensions = true;

            let partPromises = [];

            form.onPart = function(part) {
                let promise = null;

                if(part.filename) {
                    promise = streamToPrimise(part)
                        .then(buffer => {
                            return {
                                type: 'files',
                                data: {
                                    name: part.name,
                                    filename: part.filename,
                                    mime: part.mime,
                                    length: buffer.length,
                                    buffer,
                                }
                            };
                        });

                } else {
                    promise = streamToPrimise(part)
                        .then(buffer => {
                            let value = buffer.toString('ascii');

                            try {
                                value = JSON.parse(value);
                            } catch(e) {
                            }

                            return {
                                type: 'fields',
                                data: {
                                    name: part.name,
                                    value: value,
                                }
                            };
                        });
                }

                partPromises.push(promise);
            };

            form.parse(req, (err) => {
                if(err) return reject(err);

                Promise
                    .all(partPromises)
                    .then(results => {
                        let request = ControllerRequest.factory(req);

                        let groups = _.groupBy(results, 'type');

                        request.files = groups.files.map(v => v.data) || [];

                        (groups.fields || []).forEach(field => {
                            request.query[field.data.name] = field.data.value;
                        });

                        resolve(request);
                    })
                    .catch(reject);
            });
        });
    }
}

module.exports = ControllerRequest;