module.exports = SessionFromToken;

/**
 * @typedef {object} User
 * @prop {string} id
 */

/**
 * @typedef {import('express').Request} Req
 * @typedef {import('express').Response} Res
 * @typedef {import('express').NextFunction} Next
 * @typedef {import('express').RequestHandler} RequestHandler
 */

/**
 * Returns a connect middleware function which exchanges a token for a session
 *
 * @template Token
 * @template Lookup
 *
 * @param { object } deps
 * @param { (req: Req) => Promise<Token> } deps.getTokenFromRequest
 * @param { (token: Token) => Promise<Lookup> } deps.getLookupFromToken
 * @param { (lookup: Lookup) => Promise<User> } deps.findUserByLookup
 * @param { (req: Req, res: Res, user: User) => Promise<void> } deps.createSession
 * @param { boolean } deps.callNextWithError - Whether next should be call with an error or just pass through
 *
 * @returns {RequestHandler}
 */
function SessionFromToken({
    getTokenFromRequest,
    getLookupFromToken,
    findUserByLookup,
    createSession,
    callNextWithError
}) {
    /**
     * @param {Req} req
     * @param {Res} res
     * @param {Next} next
     * @returns {Promise<void>}
     */
    async function handler(req, res, next) {
        try {
            const token = await getTokenFromRequest(req);
            if (!token) {
                return next();
            }
            const email = await getLookupFromToken(token);
            if (!email) {
                return next();
            }
            const user = await findUserByLookup(email);
            if (!user) {
                return next();
            }
            await createSession(req, res, user);
            next();
        } catch (err) {
            if (callNextWithError) {
                next(err);
            } else {
                next();
            }
        }
    }

    return handler;
}