import { randomUUID } from "node:crypto"
import { mkdir, readFile, rm, writeFile } from "node:fs/promises"
import os from "node:os"
import path from "node:path"
import Database from "better-sqlite3"
import { env } from "../config.js"
import { beginSqliteExclusiveOp, endSqliteExclusiveOp, reopenSqlite, sqliteDatabase } from "../db/index.js"
import { refreshScheduler, stopScheduler } from "./scheduler.js"
import {
  reattachFlowSqlite,
  startTrafficFlowListener,
  stopTrafficFlowListener,
} from "./traffic-flow-ingest.js"

const SQLITE_MAGIC = Buffer.from("SQLite format 3\0")
const MAX_RESTORE_BYTES = 512 * 1024 * 1024

type SqliteHandle = InstanceType<typeof Database>

function fmtTimestamp(date = new Date()): string {
  const pad = (n: number) => String(n).padStart(2, "0")
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`
}

function resolveDatabasePath(): string {
  return path.resolve(process.cwd(), env.DATABASE_PATH)
}

function assertSqliteFile(buffer: Buffer): void {
  if (buffer.length < SQLITE_MAGIC.length) {
    throw new Error("Файл слишком маленький для SQLite")
  }
  if (!buffer.subarray(0, SQLITE_MAGIC.length).equals(SQLITE_MAGIC)) {
    throw new Error("Файл не похож на резервную копию SQLite")
  }
}

async function withDatabaseOperation<T>(fn: () => Promise<T> | T): Promise<T> {
  beginSqliteExclusiveOp()
  stopTrafficFlowListener()
  stopScheduler()
  try {
    return await fn()
  } finally {
    startTrafficFlowListener()
    refreshScheduler()
    endSqliteExclusiveOp()
  }
}

export async function exportSystemDatabaseBackup(): Promise<{ filename: string; buffer: Buffer }> {
  return withDatabaseOperation(async () => {
    sqliteDatabase.pragma("wal_checkpoint(TRUNCATE)")
    const tempDir = path.join(os.tmpdir(), "mmapp-db-backup")
    await mkdir(tempDir, { recursive: true })
    const tempPath = path.join(tempDir, `manager-${randomUUID()}.db`)
    try {
      await sqliteDatabase.backup(tempPath)
      const buffer = await readFile(tempPath)
      return {
        filename: `mikrotik-manager_${fmtTimestamp()}.db`,
        buffer,
      }
    } finally {
      await rm(tempPath, { force: true })
    }
  })
}

export async function restoreSystemDatabaseBackup(buffer: Buffer): Promise<void> {
  if (buffer.length > MAX_RESTORE_BYTES) {
    throw new Error("Файл бэкапа слишком большой")
  }
  assertSqliteFile(buffer)

  await withDatabaseOperation(async () => {
    const tempDir = path.join(os.tmpdir(), "mmapp-db-restore")
    await mkdir(tempDir, { recursive: true })
    const tempPath = path.join(tempDir, `restore-${randomUUID()}.db`)
    let source: SqliteHandle | null = null
    try {
      await writeFile(tempPath, buffer)
      source = new Database(tempPath, { readonly: true, fileMustExist: true })
      await source.backup(resolveDatabasePath())
      reopenSqlite()
      reattachFlowSqlite()
      sqliteDatabase.pragma("wal_checkpoint(TRUNCATE)")
    } finally {
      source?.close()
      await rm(tempPath, { force: true })
    }
  })
}

export function getSystemDatabasePath(): string {
  return resolveDatabasePath()
}
