import fs from "node:fs";
import path from "node:path";
import express from "express";
import BetterSqlite3 from "better-sqlite3";
import {Kysely, SqliteDialect} from "kysely";
import {createHotUpdater} from "@hot-updater/server";
import {kyselyAdapter} from "@hot-updater/server/adapters/kysely";
import {createMigrator} from "@hot-updater/server/db";
import {toNodeHandler} from "@hot-updater/server/node";

const HOST = process.env.DANOGO_MOBILE_OTA_HOST || "127.0.0.1";
const PORT = Number(process.env.DANOGO_MOBILE_OTA_PORT || "18081");
const DATA_DIR = path.resolve(process.env.DANOGO_MOBILE_OTA_DATA_DIR || "./data");
const DB_PATH = path.resolve(process.env.DANOGO_MOBILE_OTA_DB || path.join(DATA_DIR, "metadata.sqlite"));
const BUNDLE_DIR = path.resolve(process.env.DANOGO_MOBILE_BUNDLE_DIR || path.join(DATA_DIR, "bundles"));
const ADMIN_TOKEN = String(process.env.DANOGO_MOBILE_OTA_ADMIN_TOKEN || "").trim();
const BASE_PATH = "/mobile-updates";

fs.mkdirSync(DATA_DIR, {recursive: true});
fs.mkdirSync(BUNDLE_DIR, {recursive: true});

const sqlite = new BetterSqlite3(DB_PATH);
sqlite.pragma("journal_mode = WAL");
sqlite.pragma("foreign_keys = ON");
sqlite.pragma("busy_timeout = 5000");

const db = new Kysely({
  dialect: new SqliteDialect({database: sqlite}),
});

const database = kyselyAdapter({
  db,
  provider: "sqlite",
});

const hotUpdater = createHotUpdater({
  database,
  basePath: BASE_PATH,
  routes: {
    updateCheck: true,
    bundles: true,
  },
});

const migrator = createMigrator(hotUpdater);
const migration = await migrator.migrateToLatest({
  mode: "from-schema",
  updateSettings: true,
});
await migration.execute();

const app = express();
app.disable("x-powered-by");
app.use(express.json({limit: "2mb"}));

app.get("/health", (_req, res) => {
  res.json({
    ok: true,
    service: "danogo-mobile-ota",
    storage: "local-https",
    database: "sqlite",
  });
});

app.use(
  "/mobile-bundles",
  express.static(BUNDLE_DIR, {
    fallthrough: false,
    immutable: true,
    maxAge: "365d",
    dotfiles: "deny",
    index: false,
  }),
);

function isBundleManagementPath(req) {
  return req.path.startsWith("/api/bundles") ||
    String(req.originalUrl || "").startsWith(`${BASE_PATH}/api/bundles`);
}

function adminAuthorized(req) {
  if (!ADMIN_TOKEN) return false;
  const auth = String(req.headers.authorization || "");
  return auth === `Bearer ${ADMIN_TOKEN}`;
}

app.use(BASE_PATH, (req, res, next) => {
  if (!isBundleManagementPath(req)) return next();
  if (!adminAuthorized(req)) {
    return res.status(401).json({error: "ota_admin_authorization_required"});
  }
  next();
});

app.all(`${BASE_PATH}/*splat`, toNodeHandler(hotUpdater));

const server = app.listen(PORT, HOST, () => {
  process.stdout.write(
    `DanoGo Mobile OTA listening on http://${HOST}:${PORT}\n` +
    `Bundles: ${BUNDLE_DIR}\n` +
    `Metadata: ${DB_PATH}\n`,
  );
});

async function shutdown(signal) {
  process.stdout.write(`\n${signal}: shutting down\n`);
  server.close(async () => {
    await db.destroy();
    sqlite.close();
    process.exit(0);
  });
}

process.on("SIGINT", () => void shutdown("SIGINT"));
process.on("SIGTERM", () => void shutdown("SIGTERM"));
