import { Pool, PoolConfig } from "pg";
import { drizzle } from "drizzle-orm/node-postgres";
import * as schema from "../schema/db";

// Global cache for connection pool in Next.js development mode
declare global {
  // eslint-disable-next-line no-var
  var __pgPool: Pool | undefined;
}

function getPoolConfig(): PoolConfig {
  const connectionString = process.env.DATABASE_URL;
  if (!connectionString) {
    throw new Error(
      "DATABASE_URL is not set. Provide it via the environment or .env.local before starting the app."
    );
  }

  const isLocal =
    connectionString.includes("localhost") ||
    connectionString.includes("127.0.0.1") ||
    connectionString.includes("@postgres:") ||
    connectionString.includes("sslmode=disable");

  const requiresSsl =
    connectionString.includes("sslmode=require") ||
    connectionString.includes("ssl=true") ||
    (!isLocal && process.env.NODE_ENV === "production");

  // Verify the certificate chain in production; development boxes often run
  // Postgres with self-signed or no TLS, so relax the check there.
  const rejectUnauthorized = process.env.NODE_ENV === "production";

  return {
    connectionString,
    max: 10,
    idleTimeoutMillis: 30000,
    connectionTimeoutMillis: 5000,
    ssl: requiresSsl ? { rejectUnauthorized } : undefined,
  };
}

/**
 * A pool that heals itself when connections die silently (Docker network
 * blips, host sleep, transient packet loss). node-postgres pools can wedge
 * with all slots stuck in a "connecting"/checked-out state while the database
 * itself is fine — every query then fails after `connectionTimeoutMillis`
 * with "timeout exceeded when trying to connect" and nothing recovers until
 * the process restarts. This variant destroys and rebuilds the pool on the
 * first tell-tale error, so the app survives such blips on its own.
 */
function createPoolWithHealing(): Pool {
  const pool = new Pool(getPoolConfig());

  // Without an 'error' listener a pool that encounters client errors crashes
  // the process; with one, the pool simply discards the broken client.
  pool.on("error", (err) => {
    console.error("[db] pool client error", err.message);
    if (isWedgeError(err)) healPool();
  });

  const originalQuery = pool.query.bind(pool) as (...args: any[]) => Promise<any>;
  pool.query = ((...args: any[]) => {
    const promise = originalQuery(...args);
    promise.catch((err: unknown) => {
      if (isWedgeError(err)) healPool();
    });
    return promise;
  }) as typeof pool.query;

  return pool;
}

function isWedgeError(err: unknown): boolean {
  if (!(err instanceof Error)) return false;
  const msg = err.message.toLowerCase();
  return (
    msg.includes("timeout exceeded when trying to connect") ||
    msg.includes("connection terminated") ||
    msg.includes("connection refused") ||
    msg.includes("socket hang up")
  );
}

let healing = false;
function healPool(): void {
  if (healing) return;
  healing = true;
  console.error("[db] connection pool wedged - rebuilding");
  const old = currentPool;
  currentPool = createPoolWithHealing();
  if (old) {
    old.end().catch(() => {});
  }
  setTimeout(() => {
    healing = false;
  }, 10_000);
}

let currentPool: Pool | null = null;

function getPool(): Pool {
  if (!currentPool) {
    currentPool = createPoolWithHealing();
    if (process.env.NODE_ENV !== "production") global.__pgPool = currentPool;
  }
  return currentPool;
}

function createPoolLazy(): Pool {
  const connectionString = process.env.DATABASE_URL;
  if (!connectionString) return undefined as unknown as Pool;

  return new Proxy({} as Pool, {
    get(_, prop) {
      return Reflect.get(getPool(), prop);
    },
  });
}

export const pool: Pool = createPoolLazy();
export const db = drizzle(pool, { schema });

/**
 * Checks whether the PostgreSQL database is reachable and responding.
 * Returns false cleanly without throwing or crashing the application.
 */
export async function isDatabaseAvailable(timeoutMs: number = 2500): Promise<boolean> {
  try {
    const checkPromise = pool.query("SELECT 1 AS health");
    const timeoutPromise = new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error("DB connection timeout")), timeoutMs)
    );

    const result = await Promise.race([checkPromise, timeoutPromise]);
    return Boolean(result && result.rows && result.rows.length > 0);
  } catch (error) {
    // Database is offline or unreachable - log trace at debug level
    return false;
  }
}

export { schema };