/**
 * # Parent Router
 *
 * A wrapper around express.Router, which is controlled in Ghost.
 *
 * Intended to be extended anywhere that routes need to be registered in Ghost.
 * Only allows for .use and .get at the moment - we don't have clear use-cases for anything else yet.
 */

const debug = require('@tryghost/debug')('routing:parent-router');

const express = require('../../../shared/express');
const _ = require('lodash');
const url = require('url');
const security = require('@tryghost/security');
const urlUtils = require('../../../shared/url-utils').default;
const registry = require('./registry');

/**
 * @description Inherited express router, which gives control to us.
 *
 * Purposes:
 *   - give the router a correct name
 *   - give the router a correct parent
 *
 * @param {Object} options
 * @returns {Express-Router}
 * @constructor
 */
function GhostRouter(options) {
    const router = express.Router('Parent', options);

    function innerRouter(req, res, next) {
        return innerRouter.handle(req, res, next);
    }

    Object.setPrototypeOf(innerRouter, router);

    Object.defineProperty(innerRouter, 'name', {
        value: options.parent.name,
        writable: false
    });

    innerRouter.parent = options.parent;
    return innerRouter;
}

class ParentRouter {
    constructor(name) {
        this.identifier = security.identifier.uid(10);

        this.name = name;
        this._router = GhostRouter({mergeParams: true, parent: this});
    }

    /**
     * @description Helper function to find the site router in the express router stack.
     * @param {Object} req
     * @returns {import('express').Router}
     * @private
     */
    _getSiteRouter(req) {
        let siteRouter = null;

        req.app._router.stack.every((router) => {
            if (router.name === 'SiteRouter') {
                siteRouter = router;
                return false;
            }

            return true;
        });

        return siteRouter;
    }

    /**
     * @description Helper function to handle redirects across routers.
     * @param {Object} req
     * @param {Object} res
     * @param {Function} next
     * @param {string} slug
     * @private
     */
    _respectDominantRouter(req, res, next, slug) {
        let siteRouter = this._getSiteRouter(req);
        let targetRoute = null;

        // CASE: iterate over routers and check whether a router has a redirect for the target slug enabled.
        siteRouter.handle.stack.every((router) => {
            if (router.handle.parent && router.handle.parent.isRedirectEnabled && router.handle.parent.isRedirectEnabled(this.getResourceType(), slug)) {
                targetRoute = router.handle.parent.getRoute();
                return false;
            }

            return true;
        });

        if (targetRoute) {
            debug('_respectDominantRouter');

            // CASE: transform /tag/:slug/ -> /tag/[a-zA-Z0-9-_]+/ to able to find url pieces to append
            // e.g. /tag/bacon/page/2/  -> 'page/2' (to append)
            // e.g. /bacon/welcome/     -> '' (nothing to append)
            const matchPath = this.permalinks.getValue().replace(/:\w+/g, '[a-zA-Z0-9-_]+');
            const toAppend = req.url.replace(new RegExp(matchPath), '');

            return urlUtils.redirect301(res, url.format({
                pathname: urlUtils.createUrl(urlUtils.urlJoin(targetRoute, toAppend), false, false, true),
                search: url.parse(req.originalUrl).search
            }));
        }

        next();
    }

    /**
     * @description Mount a router on a router (sub-routing)
     * @param {String | import('express').Router} path
     * @param {import('express').Router} [router]
     */
    mountRouter(path, router) {
        if (arguments.length === 1) {
            router = path;
            debug(this.name + ': mountRouter: ' + router.name);
            this._router.use(router);
        } else {
            registry.setRoute(this.name, path);
            debug(this.name + ': mountRouter: ' + router.name + ' at ' + path);
            this._router.use(path, router);
        }
    }

    /**
     * @description Mount a route on this router.
     * @param {string} path
     * @param {Function} controller
     */
    mountRoute(path, controller) {
        debug(this.name + ': mountRoute for', path, controller.name);
        registry.setRoute(this.name, path);
        this._router.post(path, controller);
        this._router.get(path, controller);
    }

    /**
     * @description Unmount route.
     *
     * Not used at the moment, but useful to keep for e.g. deregister routes on runtime.
     *
     * @param {string} path
     */
    unmountRoute(path) {
        let indexToRemove = null;

        _.each(this._router.stack, (item, index) => {
            if (item.path === path) {
                indexToRemove = index;
            }
        });

        if (indexToRemove !== null) {
            this._router.stack.splice(indexToRemove, 1);
        }
    }

    /**
     * @description Very important function to get the actual express router, which satisfies express.
     * @returns {import('express').Router}
     */
    router() {
        return this._router;
    }

    /**
     * @description Get configured permalinks of this router.
     * @returns {Object}
     */
    getPermalinks() {
        return this.permalinks;
    }

    /**
     * @description Get main route of this router.
     *
     * Will return the full route including subdirectory. Do not use this function to mount routes for now,
     * because the subdirectory is already mounted as exclusive feature (independent of dynamic routing).
     *
     * @param {Object} options
     * @returns {string}
     */
    getRoute(options) {
        options = options || {};

        return urlUtils.createUrl(this.route.value, options.absolute);
    }

    /**
     * @description Figure out if the router has a redirect enabled.
     * @param {string} routerType
     * @param {string} slug
     * @returns {boolean}
     */
    isRedirectEnabled(routerType, slug) {
        debug('isRedirectEnabled', this.name, this.route && this.route.value, routerType, slug);

        if (!this.data || !Object.keys(this.data.router)) {
            return false;
        }

        return _.find(this.data.router, function (entries, type) {
            if (routerType === type) {
                return _.find(entries, {redirect: true, slug: slug});
            }
        });
    }

    reset() {}
}

module.exports = ParentRouter;
