import { AuthenticationInfo } from '../authentication-info.js';
import { AuthenticatedRequest } from '../authenticated-request.js';
import * as express from 'express';
import { RequestHandler } from 'express';
import { UnauthorizedException } from '../../../exceptions/unauthorized-exception.js';
import { ForbiddenException } from '../../../exceptions/forbidden-exception.js';
import { envVars, getEnvVar } from '../../../util/envVars.js';
import {AccountType} from "@dwengo-1/common/util/account-types";

/**
 * Middleware which rejects unauthenticated users (with HTTP 401) and authenticated users which do not fulfill
 * the given access condition.
 * @param accessCondition Predicate over the current AuthenticationInfo. Access is only granted when this evaluates
 *                        to true.
 */
export function authorize<P, ResBody, ReqBody, ReqQuery, Locals extends Record<string, unknown>>(
    accessCondition: (auth: AuthenticationInfo, req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>) => boolean | Promise<boolean>
): RequestHandler<P, ResBody, ReqBody, ReqQuery, Locals> {
    // Bypass authentication during testing
    if (getEnvVar(envVars.RunMode) === 'test') {
        return async (
            _req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>,
            _res: express.Response,
            next: express.NextFunction
        ): Promise<void> => {
            next();
        };
    }

    return async (
        req: AuthenticatedRequest<P, ResBody, ReqBody, ReqQuery, Locals>,
        _res: express.Response,
        next: express.NextFunction
    ): Promise<void> => {
        if (!req.auth) {
            throw new UnauthorizedException();
        } else if (!(await accessCondition(req.auth, req))) {
            throw new ForbiddenException();
        } else {
            next();
        }
    };
}

/**
 * Middleware which rejects all unauthenticated users, but accepts all authenticated users.
 */
export const authenticatedOnly = authorize((_) => true);
/**
 * Middleware which rejects requests from unauthenticated users or users that aren't students.
 */
export const studentsOnly = authorize((auth) => auth.accountType === AccountType.Student);
/**
 * Middleware which rejects requests from unauthenticated users or users that aren't teachers.
 */
export const teachersOnly = authorize((auth) => auth.accountType === AccountType.Teacher);
/**
 * Middleware which is to be used on requests no normal user should be able to execute.
 * Since there is no concept of administrator accounts yet, currently, those requests will always be blocked.
 */
export const adminOnly = authorize(() => false);
