/*
 * Copyright (C) Sapphirecode - All Rights Reserved
 * This file is part of Auth-Server-Helper which is released under MIT.
 * See file 'LICENSE' for full license details.
 * Created by Timo Hocker <timo@scode.ovh>, December 2020
 */

import { IncomingMessage, ServerResponse } from 'http';
import { run_regex } from '@sapphirecode/utilities';
import authority from './Authority';

type AnyFunc = (...args: unknown[]) => unknown;
type Gateway = (
  req: IncomingMessage,
  res: ServerResponse, next: AnyFunc
) => unknown;

interface GatewayOptions {
  redirect_url: string;
  cookie_name?: string;
}

class GatewayClass {
  private _options: GatewayOptions;

  public constructor (options: GatewayOptions) {
    this._options = options;
  }

  private redirect (res: ServerResponse): void {
    res.statusCode = 302;
    res.setHeader ('Location', this._options.redirect_url);
    res.end ();
  }

  private get_header_auth (req: IncomingMessage): string | null {
    const auth_header = req.headers.authorization;
    const auth = (/(?<type>\w+) (?<data>.*)/u).exec (auth_header || '');
    if (auth === null)
      return null;
    if (auth.groups?.type !== 'Bearer')
      return null;
    return auth.groups?.data;
  }

  private get_cookie_auth (req: IncomingMessage): string | null {
    if (typeof this._options.cookie_name === 'undefined')
      return null;
    let auth = null;
    run_regex (
      /(?:^|;)(?<name>[^;=]+)=(?<value>[^;]+)/gu,
      req.headers.cookie,
      (res: RegExpMatchArray) => {
        if (res.groups?.name === this._options.cookie_name)
          auth = res.groups?.value;
      }
    );
    return auth;
  }

  private authenticate (req: IncomingMessage): boolean {
    let auth = this.get_header_auth (req);
    if (auth === null)
      auth = this.get_cookie_auth (req);
    if (auth === null)
      return false;

    const ver = authority.verify (auth);

    const con = req.connection as unknown as Record<string, unknown>;
    con.auth = { token_id: ver.id, token_data: ver.data };

    return ver.authorized;
  }

  public process_request (
    req: IncomingMessage,
    res: ServerResponse,
    next: AnyFunc
  ): unknown {
    if (this.authenticate (req))
      return next ();
    return this.redirect (res);
  }
}

export default function create_gateway (options: GatewayOptions): Gateway {
  const g = new GatewayClass (options);
  return g.process_request.bind (g);
}

export { Gateway, AnyFunc };