#!/usr/bin/env node
"use strict";

const fs = require("fs");
const path = require("path");
const os = require("os");
const net = require("net");

const pkgRoot = path.resolve(__dirname, "..");
const dist = path.join(pkgRoot, "dist");
const serverPath = path.join(dist, "server.js");

if (!fs.existsSync(serverPath)) {
  console.error(`uniro: missing ${serverPath}`);
  console.error("This package was built without a Next.js standalone bundle.");
  console.error("If you are developing locally, run:");
  console.error("  npm run build && npm run pack:assemble");
  process.exit(1);
}

const dataDir = process.env.DATA_DIR || path.join(os.homedir(), ".uniro", "data");
fs.mkdirSync(dataDir, { recursive: true });

// This published build was compiled for connected mode (auth via the hosted
// Uniro Cloud Supabase project). Default the runtime flag to match so the
// server middleware agrees with the inlined-into-client value.
const env = {
  UNIRO_CONNECTED_MODE: "true",
  NEXT_PUBLIC_UNIRO_CONNECTED_MODE: "true",
  ...process.env,
  NODE_ENV: process.env.NODE_ENV || "production",
  PORT: process.env.PORT || "20128",
  HOSTNAME: process.env.HOSTNAME || "127.0.0.1",
  DATA_DIR: dataDir,
};

const isConnected =
  env.UNIRO_CONNECTED_MODE === "true" ||
  env.NEXT_PUBLIC_UNIRO_CONNECTED_MODE === "true";

const usingDefault = (v, def) => !v || v.startsWith(def);

if (env.NODE_ENV === "production" && !isConnected) {
  const missing = [];
  if (usingDefault(env.JWT_SECRET, "uniro-default")) missing.push("JWT_SECRET");
  if (usingDefault(env.INITIAL_PASSWORD, "change-me")) missing.push("INITIAL_PASSWORD");
  if (missing.length) {
    console.error(`uniro: self-hosted mode requires ${missing.join(" + ")} to be set to real values.`);
    console.error("Example:");
    console.error("  JWT_SECRET=$(openssl rand -hex 32) INITIAL_PASSWORD=<choose-one> uniro");
    process.exit(2);
  }
}

if (env.NODE_ENV === "production" && isConnected) {
  // Connected mode just needs Supabase, which is already baked in. Soft-warn
  // on the keys whose default values are a hardening concern but not a
  // login-flow blocker.
  if (usingDefault(env.JWT_SECRET, "uniro-default")) {
    console.warn(
      "uniro: JWT_SECRET is unset; the local password-login fallback would accept forged cookies. " +
        "Set it to a random value if /api/auth/login is reachable on your host.",
    );
  }
  if (usingDefault(env.API_KEY_SECRET, "endpoint-proxy")) {
    console.warn(
      "uniro: API_KEY_SECRET is unset; set it BEFORE generating any sk-* keys for the /v1/* API. " +
        "Changing it later invalidates existing keys.",
    );
  }
}

// Probe a single port: resolves true if we can bind it, false otherwise.
function isPortFree(port, host) {
  return new Promise((resolve) => {
    const tester = net.createServer();
    tester.once("error", () => resolve(false));
    tester.once("listening", () => {
      tester.close(() => resolve(true));
    });
    tester.listen(port, host);
  });
}

// Walk upward from the desired port until a free one is found.
async function findAvailablePort(startPort, host, maxAttempts = 100) {
  for (let port = startPort; port < startPort + maxAttempts; port++) {
    if (port > 65535) break;
    if (await isPortFree(port, host)) return port;
  }
  return null;
}

const desiredPort = parseInt(env.PORT, 10);

findAvailablePort(desiredPort, env.HOSTNAME)
  .then((port) => {
    if (port === null) {
      console.error(
        `uniro: no available port found starting from ${desiredPort}.`,
      );
      process.exit(3);
    }
    if (port !== desiredPort) {
      console.warn(
        `uniro: port ${desiredPort} is in use, starting on ${port} instead.`,
      );
    }
    env.PORT = String(port);

    process.chdir(dist);
    process.env = env;

    require(serverPath);
  })
  .catch((err) => {
    console.error(`uniro: failed to select a port — ${err && err.message}`);
    process.exit(3);
  });
