import dotenv from 'dotenv';
import pg from 'pg';
const { Pool, DatabaseError } = pg;
dotenv.config();

const config = {
  user: process.env.DATABASE_USER,
  host: process.env.DATABASE_HOST,
  database: process.env.DATABASE_NAME,
  password: process.env.DATABASE_PASSWORD,
  port: Number(process.env.DATABASE_PORT),
  max: Number(process.env.DATABASE_MAX_CONNECTIONS || 3),
} as any;

if (process.env.PGSSLMODE === 'require') {
  config.ssl = { rejectUnauthorized: false };
}

const pool = new Pool(config);

export const query = async <T>(
  queryText: string,
  variables: (string[] | string | number | null | Date | boolean)[]
) => {
  const result = await pool.query<T>(queryText, variables);
  return result;
};

const SERIALIZATION_FAILURE = '40001';

export const transaction = async <T, V>(
  callback: (q: typeof query<T>) => Promise<V>,
  retries: number = 0 // retry serialization failures
): Promise<V> => {
  const client = await pool.connect();
  let doRetry = false;
  try {
    await client.query('BEGIN');
    const result = await callback(async (queryText, variables) => {
      const result = await client.query<T>(queryText, variables);
      return result;
    });
    await client.query('COMMIT');
    return result;
  } catch (e) {
    await client.query('ROLLBACK');
    if (e instanceof DatabaseError && e.code === SERIALIZATION_FAILURE && retries > 0) {
      doRetry = true;
    } else {
      throw e;
    }
  } finally {
    client.release();
  }
  if (doRetry) {
    return await transaction(callback, retries - 1);
  }
};

export default query;
