#!/usr/bin/env node
/**
 * app.db şemasını drizzle/ altındaki migration'larla eşitler — konteyner
 * açılışında (docker-entrypoint.sh) node server.js'den ÖNCE koşar, lokalde
 * `pnpm db:migrate` ile çalıştırılır. Idempotenttir.
 *
 * Neden drizzle-orm'un kendi migrate()'i değil: standalone çıktıda drizzle-orm
 * Turbopack'çe bundle'landığı için /app/node_modules'ta paket olarak yok;
 * @libsql/client ise serverExternalPackages'ta olduğundan her zaman çözülür.
 * Bu script drizzle migrator protokolünü birebir taklit eder (__drizzle_migrations
 * tablosu, sha256 hash, created_at = journal.when) — ileride resmi migrate()'e
 * geçiş kayıtları bozmaz.
 *
 * Benimseme (adoption): migration akışından önce doldurulmuş prod volume'lerinde
 * __drizzle_migrations tablosu yoktur ama şema (kısmen) vardır. Bu durumda
 * baseline (0000) bir kez additive uygulanır — CREATE'ler IF NOT EXISTS'e
 * çevrilir, eksik kolonlar snapshot'tan ALTER TABLE ile eklenir — ve baseline
 * uygulanmış olarak damgalanır. Sonraki migration'lar (0001+) normal akışla gelir.
 */
import { createClient } from "@libsql/client";
import { createHash } from "node:crypto";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import process from "node:process";

const GOC_KLASORU = process.env.DRIZZLE_DIR ?? join(process.cwd(), "drizzle");
const DB_URL = process.env.APP_DB_URL ?? "file:./data/app.db";

const GOC_TABLOSU_DDL = `CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
  id SERIAL PRIMARY KEY,
  hash text NOT NULL,
  created_at numeric
)`;

/** Journal'daki her girdiyi { when, tag, hash, ifadeler[] } olarak yükler. */
function gocleriOku() {
  const journal = JSON.parse(
    readFileSync(join(GOC_KLASORU, "meta", "_journal.json"), "utf8"),
  );
  return journal.entries.map((girdi) => {
    const sql = readFileSync(join(GOC_KLASORU, `${girdi.tag}.sql`), "utf8");
    return {
      when: girdi.when,
      tag: girdi.tag,
      hash: createHash("sha256").update(sql).digest("hex"),
      ifadeler: sql
        .split("--> statement-breakpoint")
        .map((s) => s.trim())
        .filter(Boolean),
    };
  });
}

async function tabloVar(db, ad) {
  const r = await db.execute({
    sql: "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?",
    args: [ad],
  });
  return r.rows.length > 0;
}

/** Snapshot'taki default değerini SQL literaline çevirir (string zaten tırnaklı gelir). */
function sqlVarsayilan(deger) {
  return typeof deger === "string" ? deger : String(deger);
}

/**
 * Benimseme adımı: baseline'ı additive uygular.
 * 1) CREATE TABLE/INDEX ifadeleri IF NOT EXISTS ile koşulur (eksik tablolar gelir).
 * 2) Mevcut tablolarda eksik kolonlar baseline snapshot'ından ALTER TABLE ile eklenir.
 */
async function baselineIleUzlastir(db, baseline) {
  for (const ifade of baseline.ifadeler) {
    const korunmus = ifade
      .replace(/^CREATE TABLE `/, "CREATE TABLE IF NOT EXISTS `")
      .replace(/^CREATE INDEX `/, "CREATE INDEX IF NOT EXISTS `")
      .replace(/^CREATE UNIQUE INDEX `/, "CREATE UNIQUE INDEX IF NOT EXISTS `");
    await db.execute(korunmus);
  }

  const snapshot = JSON.parse(
    readFileSync(join(GOC_KLASORU, "meta", "0000_snapshot.json"), "utf8"),
  );
  for (const tablo of Object.values(snapshot.tables)) {
    const mevcut = await db.execute(`PRAGMA table_info(\`${tablo.name}\`)`);
    const mevcutKolonlar = new Set(mevcut.rows.map((r) => r.name));
    for (const kolon of Object.values(tablo.columns)) {
      if (mevcutKolonlar.has(kolon.name)) continue;
      let ddl = `ALTER TABLE \`${tablo.name}\` ADD COLUMN \`${kolon.name}\` ${kolon.type}`;
      if (kolon.default !== undefined) {
        ddl += ` DEFAULT ${sqlVarsayilan(kolon.default)}`;
        if (kolon.notNull) ddl += " NOT NULL";
      } else if (kolon.notNull) {
        // Dolu tabloya default'suz NOT NULL kolon eklenemez (SQLite kısıtı);
        // nullable ekleyip uyarıyoruz — uygulama zaten değeri hep yazıyor.
        console.warn(
          `[db-goc] uyarı: ${tablo.name}.${kolon.name} NOT NULL ama default'suz — nullable eklendi`,
        );
      }
      await db.execute(ddl);
      console.log(`[db-goc] kolon eklendi: ${tablo.name}.${kolon.name}`);
    }
  }
}

async function main() {
  const gocler = gocleriOku();
  const db = createClient({
    url: DB_URL,
    authToken: process.env.APP_DB_AUTH_TOKEN,
  });

  try {
    // Benimseme: şema var ama migration kaydı yok → baseline'ı uzlaştır + damgala.
    const kayitVar = await tabloVar(db, "__drizzle_migrations");
    if (!kayitVar && (await tabloVar(db, "user"))) {
      console.log("[db-goc] migration kaydı yok, mevcut şema benimseniyor...");
      await baselineIleUzlastir(db, gocler[0]);
      await db.execute(GOC_TABLOSU_DDL);
      await db.execute({
        sql: 'INSERT INTO "__drizzle_migrations" (hash, created_at) VALUES (?, ?)',
        args: [gocler[0].hash, gocler[0].when],
      });
      console.log(`[db-goc] baseline damgalandı: ${gocler[0].tag}`);
    }

    // Normal akış: son uygulanan migration'dan sonrakileri sırayla uygula.
    await db.execute(GOC_TABLOSU_DDL);
    const son = await db.execute(
      'SELECT created_at FROM "__drizzle_migrations" ORDER BY created_at DESC LIMIT 1',
    );
    const sonZaman = son.rows.length ? Number(son.rows[0].created_at) : 0;

    for (const goc of gocler) {
      if (goc.when <= sonZaman) continue;
      await db.batch(
        [
          ...goc.ifadeler,
          {
            sql: 'INSERT INTO "__drizzle_migrations" (hash, created_at) VALUES (?, ?)',
            args: [goc.hash, goc.when],
          },
        ],
        "write",
      );
      console.log(`[db-goc] uygulandı: ${goc.tag}`);
    }
    console.log("[db-goc] şema güncel");
  } finally {
    db.close();
  }
}

main().catch((hata) => {
  console.error("[db-goc] migration başarısız:", hata);
  process.exit(1);
});
