const CONFIG = require('./config');
class MockettaroProgram {
    /**
     * @static
     * @readonly
     * @property {RegExp} RESOURCE_MATCHER
     * @memberof MockettaroProgram
     * @example MockettaroProgram.RESOURCE_MATCHER
     */
    static get RESOURCE_MATCHER() {
        return /^(?:\/?[a-z0-9_-]+)+$/i;
    }

    /**
     * @static
     * @readonly
     * @property {RegExp} FOLDER_MATCHER
     * @memberof MockettaroProgram
     * @example MockettaroProgram.FOLDER_MATCHER
     */
    static get FOLDER_MATCHER() {
        return /^(?:(?:[a-z]:|\.{1,2})?[\\/])?([^!#$%&+={}[\]\n]+[\\/])*[^!#$%&+={}[\]\n]+$/i;
    }

    /**
     * @static
     * @readonly
     * @property {number} DEFAULT_PORT
     * @memberof MockettaroProgram
     * @example MockettaroProgram.DEFAULT_PORT
     */
    static get DEFAULT_PORT() {
        return CONFIG.DEFAULT_PORT;
    }

    /**
     * @static
     * @readonly
     * @property {string} DEFAULT_RESOURCE
     * @memberof MockettaroProgram
     * @example MockettaroProgram.DEFAULT_RESOURCE
     */
    static get DEFAULT_RESOURCE() {
        return CONFIG.DEFAULT_RESOURCE;
    }

    /**
     * @static
     * @readonly
     * @property {string} DEFAULT_FOLDER
     * @memberof MockettaroProgram
     * @example MockettaroProgram.DEFAULT_RESOURCE
     */
    static get DEFAULT_FOLDER() {
        return CONFIG.DEFAULT_FOLDER;
    }

    /**
     * @static
     * @readonly
     * @property {number} DEFAULT_DELAY
     * @memberof MockettaroProgram
     * @example MockettaroProgram.DEFAULT_DELAY
     */
    static get DEFAULT_DELAY() {
        return CONFIG.DEFAULT_DELAY;
    }

    /**
     * @static
     * @readonly
     * @property {number} DEFAULT_CACHE_LIFETIME
     * @memberof MockettaroProgram
     * @example MockettaroProgram.DEFAULT_CACHE_LIFETIME
     */
    static get DEFAULT_CACHE_LIFETIME() {
        return CONFIG.DEFAULT_CACHE_LIFETIME;
    }

    /**
     * @static
     * @readonly
     * @property {number} MIN_PORT
     * @memberof MockettaroProgram
     * @example MockettaroProgram.MIN_PORT
     */
    static get MIN_PORT() {
        return CONFIG.MIN_PORT;
    }

    /**
     * @static
     * @readonly
     * @property {number} MAX_PORT
     * @memberof MockettaroProgram
     * @example MockettaroProgram.MAX_PORT
     */
    static get MAX_PORT() {
        return CONFIG.MAX_PORT;
    }

    /**
     * @static
     * @readonly
     * @property {number} MAX_DELAY
     * @memberof MockettaroProgram
     * @example MockettaroProgram.MAX_DELAY
     */
    static get MAX_DELAY() {
        return CONFIG.MAX_DELAY;
    }

    /**
     * @static
     * @readonly
     * @property {number} MAX_CACHE_LIFETIME
     * @memberof MockettaroProgram
     * @example MockettaroProgram.MAX_CACHE_LIFETIME
     */
    static get MAX_CACHE_LIFETIME() {
        return 720000;
    }

    /**
     * @static
     * @method numericArgParser
     * @param {RegExp} matcher Number regexp matcher
     * @param {number} min Lower limit
     * @param {number} max Upper limit
     * @returns {Function} Parser
     * @memberof MockettaroProgram
     * @example MockettaroProgram.numericArgParser(/\d+/, 0, 100)
     */
    static numericArgParser(matcher = /\d+/, min = 0, max = 99999999) {
        return v => {
            const parsedV = parseInt(((`${v || ''}`).match(matcher) || [])[0]);
            return isNaN(parsedV) ? null : Math.min(Math.max(min, parsedV), max);
        };
    }

    /**
     * @static
     * @method cmdParser
     * @param {Array<string>} argv command line arguments
     * @returns {Commander} Returns a commander instanced with parsed argv
     * @memberof MockettaroProgram
     * @example MockettaroProgram.cmdParser('abc', 'abc', '-p', '...')
     */
    static cmdParser(...argv) {
        const pkgjson = require('../package.json');
        const program = require('commander');

        return program
          .version(pkgjson.version, '-v, --version')
          .description(pkgjson.description)
          .option('-p, --port <number>', 'Serve on specified port', this.numericArgParser(/\d{2,5}/, this.MIN_PORT, this.MAX_PORT), this.DEFAULT_PORT)
          .option('-r, --resource <path>', 'Root resource to serve', this.RESOURCE_MATCHER, this.DEFAULT_RESOURCE)
          .option('-f, --folder <path>', 'Sub-folder to fetch for files', this.FOLDER_MATCHER, this.DEFAULT_FOLDER)
          .option('-d, --response-delay <milliseconds>', 'Response delay in ms', this.numericArgParser(/\d{1,6}/, 0, this.MAX_DELAY), this.DEFAULT_DELAY)
          .option('-t, --cache-lifetime <milliseconds>', 'JSON cache lifetime', this.numericArgParser(/\d{1,8}/, 0, this.MAX_CACHE_LIFETIME), this.DEFAULT_CACHE_LIFETIME)
          .option('-s, --silent', 'No logs')
          .option('--verbose', 'Verbose logs')
          .parse(argv);
    }

    /**
     * @constructor
     * @param {Array<string>} argv command line arguments
     * @param {string|Function} cwd Current Working Directory
     * @returns {Promise<number>} Returns the copy process final status
     */
    constructor({
        argv = process.argv,
        cwd = process.cwd()
    } = {}) {
        const program = this.constructor.cmdParser(...argv)

        if (program.verbose && program.silent) {
            return Promise.reject(new Error('Can\'t run in both silent and verbose mode'));
        }

        const mockettaro = require('./mockettaro.class');
        const logger = require("@marketto/js-logger").global();
        const server = require('express')();

        const {port, resource, folder, responseDelay, cacheLifetime, silent, verbose} = program;
        server.use(`/${resource}`, mockettaro({
            directory: folder,
            responseDelay,
            cacheLifetime,
            cwd,
            verbose,
            errors: true,
            info: !silent
        }));


        return new Promise((resolve, reject) => {
            const serverErrorHandler = err => {
                logger.error(err);
                reject(err);
            };
            if (!process.hasUncaughtExceptionCaptureCallback()) {
                process.setUncaughtExceptionCaptureCallback(err => serverErrorHandler(err));
            }

            const serverInstance = server.listen(port, () => {
                logger.info(`Mockettaro serving ${folder} content @ localhost:${port}/${resource}`);
                resolve(serverInstance);
            });
        });
    }
}

module.exports.mockettaroProgram = async (...args) => new MockettaroProgram(...args);
module.exports.MockettaroProgram = MockettaroProgram;