"use strict";

const fs = require("fs");
const path = require("path");
const { spawnSync } = require("child_process");

const migrationsDir = path.resolve(
  __dirname,
  "..",
  "packages",
  "db",
  "migrations"
);
const staleConfigPath = path.join(migrationsDir, "stale-files.json");

function readStaleConfigFile() {
  if (!fs.existsSync(staleConfigPath)) {
    return new Set();
  }

  const raw = fs.readFileSync(staleConfigPath, "utf8");
  let parsed;
  try {
    parsed = JSON.parse(raw);
  } catch (error) {
    throw new Error(`Invalid JSON in ${staleConfigPath}`);
  }

  const values = Array.isArray(parsed?.stale_files) ? parsed.stale_files : [];
  return new Set(
    values
      .map((value) => String(value || "").trim())
      .filter(Boolean)
  );
}

function getSkippedMigrations() {
  const includeStale = String(process.env.DB_MIGRATE_INCLUDE_STALE || "")
    .trim()
    .toLowerCase();
  const skipFromConfig =
    includeStale === "1" || includeStale === "true" || includeStale === "yes"
      ? new Set()
      : readStaleConfigFile();

  const raw = process.env.DB_MIGRATE_SKIP_FILES || "";
  const skipFromEnv = new Set(
    raw
      .split(",")
      .map((value) => value.trim())
      .filter(Boolean)
  );

  return new Set([...skipFromConfig, ...skipFromEnv]);
}

function ensureDatabaseUrl() {
  const databaseUrl = process.env.DATABASE_URL;
  if (!databaseUrl) {
    throw new Error("DATABASE_URL is required.");
  }
  return databaseUrl;
}

function ensurePsql() {
  const result = spawnSync("psql", ["--version"], { stdio: "pipe" });
  if (result.error || result.status !== 0) {
    throw new Error("psql executable was not found in PATH.");
  }
}

function ensureMigrationsDir() {
  if (!fs.existsSync(migrationsDir)) {
    throw new Error(`Migrations directory not found: ${migrationsDir}`);
  }
}

function getMigrationFiles() {
  ensureMigrationsDir();
  const skipped = getSkippedMigrations();
  return fs
    .readdirSync(migrationsDir)
    .filter((file) => file.endsWith(".sql"))
    .filter((file) => !skipped.has(file))
    .sort((a, b) => a.localeCompare(b));
}

function runPsql(databaseUrl, args) {
  const result = spawnSync("psql", [databaseUrl, ...args], {
    stdio: "pipe",
    encoding: "utf8",
  });
  if (result.status !== 0) {
    const stderr = (result.stderr || "").trim();
    const stdout = (result.stdout || "").trim();
    const details = [stderr, stdout].filter(Boolean).join("\n");
    throw new Error(details || "psql command failed");
  }
  return result.stdout || "";
}

function escapeSqlLiteral(value) {
  return value.replace(/'/g, "''");
}

function ensureSchemaMigrationsTable(databaseUrl) {
  runPsql(databaseUrl, [
    "-v",
    "ON_ERROR_STOP=1",
    "-c",
    "CREATE TABLE IF NOT EXISTS schema_migrations (filename TEXT PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW());",
  ]);
}

function getAppliedMigrations(databaseUrl) {
  const output = runPsql(databaseUrl, [
    "-At",
    "-v",
    "ON_ERROR_STOP=1",
    "-c",
    "SELECT filename FROM schema_migrations ORDER BY filename ASC;",
  ]);
  return new Set(
    output
      .split(/\r?\n/)
      .map((line) => line.trim())
      .filter(Boolean)
  );
}

function applyMigration(databaseUrl, filename) {
  const fullPath = path.join(migrationsDir, filename);
  runPsql(databaseUrl, ["-v", "ON_ERROR_STOP=1", "-f", fullPath]);
  runPsql(databaseUrl, [
    "-v",
    "ON_ERROR_STOP=1",
    "-c",
    `INSERT INTO schema_migrations (filename) VALUES ('${escapeSqlLiteral(
      filename
    )}') ON CONFLICT DO NOTHING;`,
  ]);
}

module.exports = {
  applyMigration,
  ensureDatabaseUrl,
  ensurePsql,
  ensureSchemaMigrationsTable,
  getAppliedMigrations,
  getMigrationFiles,
  getSkippedMigrations,
  migrationsDir,
};
