// ── Automated Database Migration Runner ──────────────────────────────
// Reads .env.local for DATABASE_URL, tracks applied migrations in
// a _migrations table, and runs unapplied .sql files via psql.
// Runs automatically on npm run dev and npm run setup.
// ============================================================================

import { readFileSync, existsSync } from "node:fs"
import { execSync } from "node:child_process"
import { resolve, dirname } from "node:path"
import { fileURLToPath } from "node:url"
import { createRequire } from "node:module"

const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(__dirname, "..")

// ── Load .env.local into process.env ─────────────────────────────────

const envPath = resolve(ROOT, ".env.local")
if (existsSync(envPath)) {
  for (const line of readFileSync(envPath, "utf-8").split("\n")) {
    const trimmed = line.trim()
    if (!trimmed || trimmed.startsWith("#")) continue
    const eq = trimmed.indexOf("=")
    if (eq === -1) continue
    const key = trimmed.slice(0, eq).trim()
    let value = trimmed.slice(eq + 1).trim()
    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
      value = value.slice(1, -1)
    }
    if (!process.env[key]) process.env[key] = value
  }
}

const DATABASE_URL = process.env.DATABASE_URL
if (!DATABASE_URL) {
  console.log("  ~ DATABASE_URL not set — skipping migrations")
  process.exit(0)
}

// ── Find psql ────────────────────────────────────────────────────────

function findPsql() {
  try {
    execSync("psql --version", { stdio: "pipe", timeout: 5000 })
    return "psql"
  } catch {}

  const candidates = [
    "C:\\Program Files\\PostgreSQL\\16\\bin\\psql.exe",
    "C:\\Program Files\\PostgreSQL\\17\\bin\\psql.exe",
    "C:\\Program Files\\PostgreSQL\\15\\bin\\psql.exe",
    "C:\\Program Files\\PostgreSQL\\14\\bin\\psql.exe",
  ]
  for (const p of candidates) {
    if (existsSync(p)) return `"${p}"`
  }
  return null
}

const PSQL = findPsql()
if (!PSQL) {
  console.log("  ~ psql not found — skipping migrations (install PostgreSQL client tools)")
  process.exit(0)
}

// ── Database connection (for tracking table) ─────────────────────────

const require = createRequire(import.meta.url)
const { Pool } = require("pg")

let pool
try {
  pool = new Pool({
    connectionString: DATABASE_URL,
    max: 1,
    connectionTimeoutMillis: 5000,
    idleTimeoutMillis: 10000,
  })
  await pool.query("SELECT 1")
} catch {
  console.log("  ~ Database not available — skipping migrations")
  process.exit(0)
}

// ── Migration logic ──────────────────────────────────────────────────

async function runMigrations() {
  // 1. Create tracking table if not exists
  await pool.query(`
    CREATE TABLE IF NOT EXISTS _migrations (
      id SERIAL PRIMARY KEY,
      filename VARCHAR(255) NOT NULL UNIQUE,
      applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    )
  `)

  // 2. Determine file order from run_all.sql (authoritative)
  const runAllPath = resolve(ROOT, "database", "migrations", "run_all.sql")
  const runAllContent = readFileSync(runAllPath, "utf-8")
  const fileOrder = []
  for (const line of runAllContent.split("\n")) {
    const match = line.match(/\\i\s+(\S+\.sql)/)
    if (match) fileOrder.push(match[1])
  }

  if (fileOrder.length === 0) {
    console.log("  ~ No migration files found in run_all.sql")
    return
  }

  // 3. Get already-applied files
  let appliedSet
  try {
    const { rows: applied } = await pool.query("SELECT filename FROM _migrations ORDER BY filename")
    appliedSet = new Set(applied.map((r) => r.filename))
  } catch {
    appliedSet = new Set()
  }

  // 4. Build psql command base
  const psqlCmd = `${PSQL} -d "${DATABASE_URL}" -v ON_ERROR_STOP=1`

  // 5. Run unapplied files in order.
  //    psql -v ON_ERROR_STOP=1 aborts on the first error with exit code 3.
  //    If the error is "already exists", the file was already applied before
  //    tracking existed — mark it as applied and continue.
  //    Other errors are genuine failures — abort.
  const ALREADY_APPLIED_PATTERNS = [
    "already exists",
    "duplicate key",
    "cannot insert multiple commands into a prepared statement",
  ]

  let count = 0
  for (const file of fileOrder) {
    if (appliedSet.has(file)) continue

    const filePath = resolve(ROOT, "database", "migrations", file)
    if (!existsSync(filePath)) {
      console.error(`  ✗ Migration file not found: ${file}`)
      process.exit(1)
    }

    let stderr = ""
    try {
      const result = execSync(`${psqlCmd} -f "${filePath}"`, {
        timeout: 30000,
        encoding: "utf-8",
        maxBuffer: 10 * 1024 * 1024,
        stdio: ["ignore", "pipe", "pipe"],
      })
      // psql outputs notices/warnings to stderr even on success
      stderr = result.stderr || ""
    } catch (err) {
      const msg = err.stderr || err.stdout || err.message
      const isAlreadyApplied = ALREADY_APPLIED_PATTERNS.some((p) => msg.includes(p))
      if (isAlreadyApplied) {
        await pool.query("INSERT INTO _migrations (filename) VALUES ($1) ON CONFLICT DO NOTHING", [file])
        console.log(`  ~ Already applied: ${file}`)
        count++
        continue
      }
      console.error(`  ✗ Migration failed: ${file}`)
      console.error(`    ${msg.split("\n").slice(0, 5).join("\n    ")}`)
      process.exit(1)
    }

    await pool.query("INSERT INTO _migrations (filename) VALUES ($1)", [file])
    console.log(`  ✓ Applied: ${file}`)
    count++
  }

  if (count === 0) {
    console.log("  ~ All migrations up to date")
  }
}

// ── Run ──────────────────────────────────────────────────────────────

try {
  await runMigrations()
} finally {
  await pool.end()
}
