import * as Hapi from "@hapi/hapi";
import pgPromise from "pg-promise";
import pgMonitor from "pg-monitor";
import type { IConnectionParameters } from "pg-promise/typescript/pg-subset";
import type { IMain, IInitOptions } from "pg-promise";
import { IPGPPluginOptions, ExtendedProtocol } from "./types.js";
import { Plugin } from "@hapi/hapi/lib/types/plugin";

export * from "./types.js";

export const startDiagnostics = <T>(
  logSql: boolean,
  initOpts: IInitOptions<T>
): void => {
  if (logSql) {
    pgMonitor.attach(initOpts);
  } else {
    pgMonitor.attach(initOpts, ["error"]);
  }
};

export const stopDiagnostics = (): void => pgMonitor.detach();

const startPgp = async <T>(initOptions: IInitOptions<T>): Promise<IMain> => {
  const pgp: IMain = pgPromise(initOptions);
  return pgp;
};

const startDb = async <T>(
  pgp,
  connection: string | IConnectionParameters
): Promise<ExtendedProtocol<T>> => {
  const db: ExtendedProtocol<T> = pgp(connection);

  return db;
};

export function makePlugin<T>(): Plugin<IPGPPluginOptions<T>, void> {
  return {
    version: "1.0.0",
    name: "pg-promise",
    async register(
      server: Hapi.Server,
      userOpts?: IPGPPluginOptions<T>
    ): Promise<void> {
      if (userOpts.logSql === undefined) userOpts.logSql = false;
      if (!userOpts.decorateAs) userOpts.decorateAs = { pgp: "pgp", db: "db" };

      const options = userOpts;

      if (!options.connection) {
        throw new Error(
          "hapi-pg-promise: connection details are not defined. You must specify a valid connection for the plugin to boot."
        );
      }

      if ("pgp" in options && "pgpInit" in options) {
        throw new Error(
          "hapi-pg-promise: options pgp and pgpInit are mutually exclusive"
        );
      }

      let pgp: IMain;
      if ("pgp" in options) {
        pgp = options.pgp;
      } else {
        pgp = await startPgp(options.pgpInit || {});
        startDiagnostics(options.logSql, options.pgpInit || {});
      }

      const db = await startDb(pgp, options.connection);

      if (options.decorateAs) {
        if (options.decorateAs) {
          server.decorate("request", options.decorateAs.pgp, pgp);
          server.decorate("server", options.decorateAs.pgp, pgp);
        }

        if (options.decorateAs.db) {
          server.decorate("server", options.decorateAs.db, () => db);
          server.decorate("request", options.decorateAs.db, () => db);
        }
      }

      server.ext("onPostStop", async () => {
        stopDiagnostics();
        await db.$pool.end();
      });
    },
  };
}
