import { Context, Env, MiddlewareHandler } from "hono";
import AuthorizationError from "../../Errors/AuthorizationError";
import { sessions } from "../../managers/sessions";
import { Variables } from "../../types/HonoTypes";
import GenericError from "../../Errors/GenericError";
import { events } from "../../modules/globalEvents";

/**
 * Authorization Middleware
 *
 * This middleware will do all of the authorization for all the routes that may need authorization.
 * It will check which auth type is being used and pull the correct credentials from said auth type and
 * supply them as a variable to the route.  If there is an error thrown at any point in this middleware, it
 * will hault and will not proceed to the route handler.
 *
 * Eventually this method will analyze roles and permissions and supply those as objects to the route handler.
 *
 * ## Auth Types
 * - Bearer: Access Token for user authentication
 * - Key: API Key
 *
 * @param ctx - Hono Context Object
 * @param next - Move onto the handler
 */
export const authMiddleware = (permParams?: {
  permissions?: string[];
  scopes?: string[];
  forbiddenAuthTypes?: string[];
}): MiddlewareHandler<{
  Variables: Variables;
}> => {
  return async (ctx, next) => {
    const authorization = ctx.req.header()["authorization"];
    if (!authorization)
      throw new AuthorizationError("Missing 'authorization' header.");

    const components = authorization.match(
      /^(Bearer|Key)\s([a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+)$/,
    );
    if (!components)
      throw new AuthorizationError(
        "Invalid or malformed authorization header...",
      );

    const authType: string = components[1] ?? "";
    const authValue: string = components[2] ?? "";

    if (permParams?.forbiddenAuthTypes?.includes(authType))
      throw new GenericError({
        name: "NonpermittedAuthType",
        message:
          "The authorization method you are using is not permitted for this API request.",
        cause: `Type '${authType}' is not permitted.`,
        status: 403,
      });

    if (authType) {
      const session = await sessions.fetch({ accessToken: authValue });
      const user = await session.fetchUser();
      ctx.set("user", user);

      if (
        permParams?.permissions &&
        permParams?.permissions.length > 0 &&
        (
          await Promise.all(
            permParams?.permissions.map((p) => user.hasPermission(p)),
          )
        ).includes(false)
      )
        throw new GenericError({
          name: "InsufficentPermission",
          message: "You do not have sufficent permissions to do this.",
          status: 403,
        });
    }

    await next();
  };
};
