import { Pool, type QueryResultRow } from "pg";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 10,
  idleTimeoutMillis: 30_000,
  connectionTimeoutMillis: 10_000,
});

export type SqlValue = string | number | boolean | null | Date;

export async function sql<R extends QueryResultRow = QueryResultRow>(
  strings: TemplateStringsArray,
  ...values: SqlValue[]
): Promise<R[]> {
  const text = strings.reduce(
    (acc, chunk, i) =>
      acc + chunk + (i < values.length ? `$${i + 1}` : ""),
    "",
  );

  const result = await pool.query<R>(text, values);

  return result.rows;
}

export async function query<R extends QueryResultRow = QueryResultRow>(
  text: string,
  values: SqlValue[] = [],
): Promise<R[]> {
  const result = await pool.query<R>(text, values);
  return result.rows;
}

export async function transaction<T>(
  callback: (
    tx: <R extends QueryResultRow = QueryResultRow>(
      strings: TemplateStringsArray,
      ...values: SqlValue[]
    ) => Promise<R[]>,
  ) => Promise<T>,
): Promise<T> {
  const client = await pool.connect();

  try {
    await client.query("BEGIN");

    const tx = async <R extends QueryResultRow = QueryResultRow>(
      strings: TemplateStringsArray,
      ...values: SqlValue[]
    ) => {
      const text = strings.reduce(
        (acc, chunk, i) =>
          acc + chunk + (i < values.length ? `$${i + 1}` : ""),
        "",
      );

      const result = await client.query<R>(text, values);

      return result.rows;
    };

    const out = await callback(tx);

    await client.query("COMMIT");

    return out;
  } catch (error) {
    await client.query("ROLLBACK");

    throw error;
  } finally {
    client.release();
  }
}
