const { Pool, Client } = require('pg')
const fs = require('fs')
const path = require('path')

const dotenvPath = path.resolve(__dirname, '..', '.env')
if (fs.existsSync(dotenvPath)) {
  require('dotenv').config({ path: dotenvPath })
} else {
  require('dotenv').config()
}

const hasConnectionString =
  typeof process.env.DATABASE_URL === 'string' && process.env.DATABASE_URL.trim().length > 0

if (!hasConnectionString) {
  console.warn('[db] DATABASE_URL is not set. Falling back to discrete Postgres environment variables.')
}

const sslConfig =
  process.env.DATABASE_SSL === 'true' || process.env.PGSSLMODE === 'require'
    ? { rejectUnauthorized: false }
    : undefined

const defaultConfig = {
  host: process.env.PGHOST || 'localhost',
  port: Number(process.env.PGPORT || 5432),
  user: process.env.PGUSER || 'postgres',
  password: process.env.PGPASSWORD || 'postgres',
  database: process.env.PGDATABASE || 'claudia_blog'
}

const poolConfig = hasConnectionString
  ? { connectionString: process.env.DATABASE_URL.trim() }
  : { ...defaultConfig }

if (sslConfig) {
  poolConfig.ssl = sslConfig
}

let pool
let ensuringDatabasePromise

function quoteIdentifier(identifier) {
  return `"${identifier.replace(/"/g, '""')}"`
}

function resolveTargetDatabase() {
  if (hasConnectionString) {
    try {
      const url = new URL(process.env.DATABASE_URL.trim())
      const dbName = url.pathname ? url.pathname.replace(/^\//, '') : null
      return dbName || null
    } catch (error) {
      console.warn(`[db] Unable to parse DATABASE_URL: ${error.message}`)
      return null
    }
  }

  return poolConfig.database || null
}

async function ensureDatabaseExists(force = false) {
  if (ensuringDatabasePromise && !force) {
    return ensuringDatabasePromise
  }

  ensuringDatabasePromise = (async () => {
    const targetDb = resolveTargetDatabase()
    if (!targetDb) {
      return
    }

    let adminConfig

    if (hasConnectionString) {
      try {
        const adminUrl = new URL(process.env.DATABASE_URL.trim())
        adminUrl.pathname = '/postgres'
        adminConfig = { connectionString: adminUrl.toString() }
        if (sslConfig) {
          adminConfig.ssl = sslConfig
        }
      } catch (error) {
        console.warn(`[db] Unable to prepare admin connection to create database: ${error.message}`)
        return
      }
    } else {
      const adminDatabase = process.env.PGDEFAULTDB || 'postgres'
      adminConfig = {
        ...poolConfig,
        database: adminDatabase
      }
    }

    const client = new Client(adminConfig)

    try {
      await client.connect()
      const exists = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [targetDb])
      if (exists.rowCount === 0) {
        await client.query(`CREATE DATABASE ${quoteIdentifier(targetDb)}`)
        console.log(`[db] Created database ${targetDb}`)
      }
    } catch (error) {
      if (error.code === '42P04') {
        return
      }
      console.warn(`[db] Could not ensure database ${targetDb}: ${error.message}`)
    } finally {
      await client.end().catch(() => {})
    }
  })()

  return ensuringDatabasePromise
}

async function initialisePool() {
  const activePool = new Pool(poolConfig)
  activePool.on('error', (err) => {
    console.error('[db] Unexpected error on idle client', err)
  })
  // Force a connection so we surface errors immediately
  await activePool.query('SELECT 1')
  return activePool
}

async function getPool() {
  if (pool) {
    return pool
  }

  try {
    await ensureDatabaseExists()
    pool = await initialisePool()
    return pool
  } catch (error) {
    if (error.code === '3D000') {
      // Database missing, retry once after forcing ensure
      await ensureDatabaseExists(true)
      pool = await initialisePool()
      return pool
    }
    throw error
  }
}

async function query(text, params) {
  const activePool = await getPool()
  return activePool.query(text, params)
}

async function closePool() {
  if (pool) {
    await pool.end()
    pool = null
  }
}

module.exports = {
  query,
  getPool,
  closePool,
  ensureDatabaseExists
}
