/**
 * Database initialization and access
 */

import initSqlJs from 'sql.js'
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'

import { SCHEMA } from './schema.js'
import { MIGRATIONS } from './migrations.js'
import { seed, isDbEmpty } from './seed.js'

const __dirname = dirname(fileURLToPath(import.meta.url))
// db/ is in apps/api/, so .. = apps/api, ../.. = apps, ../../.. = project root
export const DB_PATH = join(__dirname, '..', '..', '..', 'data', 'vps-tracker.db')
const SEED_DIR = join(__dirname, '..', '..', '..', 'apps', 'web', 'public', 'data')

let dbInstance = null
/** @type {import('sql.js').SqlJsStatic | null} */
let sqlJsFactory = null

export async function initDb() {
  const dataDir = join(__dirname, '..', '..', '..', 'data')
  if (!existsSync(dataDir)) {
    mkdirSync(dataDir, { recursive: true })
  }

  const SQL = await initSqlJs()
  sqlJsFactory = SQL
  let db

  if (existsSync(DB_PATH)) {
    const fileBuffer = readFileSync(DB_PATH)
    db = new SQL.Database(fileBuffer)
  } else {
    db = new SQL.Database()
  }

  db.exec(SCHEMA)

  for (const m of MIGRATIONS) {
    try {
      m.run(db)
    } catch (err) {
      console.warn(`Migration ${m.name} failed:`, err.message)
    }
  }

  if (isDbEmpty(db)) {
    seed(db, SEED_DIR)
    const data = db.export()
    writeFileSync(DB_PATH, Buffer.from(data))
  }

  dbInstance = db
  if (existsSync(DB_PATH)) {
    saveDb()
  }
  return db
}

export function getDb() {
  if (!dbInstance) throw new Error('Database not initialized')
  return createDbWrapper(dbInstance)
}

function createDbWrapper(db) {
  return {
    /** One-off run: prepare, bind, step, free. Use for statements that run once. */
    run(sql, ...params) {
      const stmt = db.prepare(sql)
      stmt.bind(params)
      stmt.step()
      const changes = db.getRowsModified()
      stmt.free()
      saveDb()
      return { changes }
    },
    prepare(sql) {
      const stmt = db.prepare(sql)
      return {
        all(...params) {
          stmt.bind(params)
          const rows = []
          while (stmt.step()) {
            rows.push(stmt.getAsObject())
          }
          stmt.free()
          return rows
        },
        get(...params) {
          stmt.bind(params)
          const row = stmt.step() ? stmt.getAsObject() : null
          stmt.free()
          return row
        },
        run(...params) {
          stmt.bind(params)
          stmt.step()
          const changes = db.getRowsModified()
          stmt.free()
          saveDb()
          return { changes }
        },
      }
    },
  }
}

export function saveDb() {
  if (!dbInstance) return
  const data = dbInstance.export()
  writeFileSync(DB_PATH, Buffer.from(data))
}

/**
 * Заменить in-memory БД из буфера SQLite и сохранить на диск.
 * @param {Buffer|Uint8Array} buffer
 */
export async function reloadDatabaseFromBuffer(buffer) {
  const SQL = sqlJsFactory || (await initSqlJs())
  sqlJsFactory = SQL
  if (dbInstance) {
    dbInstance.close()
    dbInstance = null
  }
  const u8 = buffer instanceof Buffer ? new Uint8Array(buffer) : buffer
  dbInstance = new SQL.Database(u8)
  dbInstance.exec(SCHEMA)
  for (const m of MIGRATIONS) {
    try {
      m.run(dbInstance)
    } catch (err) {
      console.warn(`Migration ${m.name} after reload failed:`, err.message)
    }
  }
  saveDb()
}
