import { Pool } from 'pg';

// import os from 'os';
import { Migrator } from './Migrator';

import { PoolClient } from 'pg';
import { Repositories } from './Repositories';

export interface DbConnection {
  user: string;
  password: string;
  host: string;
  port: number;
  database: string;
}

export interface ConnectionStringObj {
  connectionString: string;
}

export type TransactionClient = PoolClient;

export class ConnectionManager<T extends Repositories> {
  public readonly pool: Pool;
  public readonly repositories: T;

  constructor(
    protected applicationName: string,
    connection: string | DbConnection,
    protected migrationDirectory: string,
    protected migrationList: string[],
    repositoryCreator: (dbManager: ConnectionManager<T>) => T,
  ) {
    let connectionInfo: ConnectionStringObj | DbConnection;

    if (typeof connection === 'string') {
      connectionInfo = { connectionString: connection };
    } else {
      connectionInfo = connection;
    }

    this.pool = new Pool({
      ...connectionInfo,

      application_name: applicationName,

      query_timeout: 5000, // TODO consider
      // idleTimeoutMillis: 0, // TODO consider
      // connectionTimeoutMillis: 2000,

      // max: os.cpus().length * 2,
    });

    this.repositories = repositoryCreator(this);
  }

  public async applyMigrations() {
    const connection = await this.pool.connect();

    const migrator = new Migrator(this.migrationDirectory, this.migrationList, connection);
    await migrator.migrate();
  }

  public async close() {
    await this.pool.end();
    this.pool.removeAllListeners();
  }

  public async executeInTransaction<T>(func: (client: TransactionClient) => Promise<T>): Promise<T> {
    const client = await this.pool.connect();

    try {
      await client.query('BEGIN');
      const value = await func(client);
      await client.query('COMMIT');
      return value;
    } catch (e) {
      await client.query('ROLLBACK');
      throw e;
    } finally {
      if (client) {
        client.release();
      }
    }
  }
}
