import { spawn } from "child_process";
import net from "net";
import os from "os";
import path from "path";
import { fileURLToPath } from "url";
import fs from "fs";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const appRoot = path.resolve(__dirname, "..", "..");

function hydrateEnvFile(filePath) {
  if (!fs.existsSync(filePath)) return;
  const raw = fs.readFileSync(filePath, "utf8");
  for (const line of raw.split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith("#")) continue;
    const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
    if (!match) continue;
    const key = match[1];
    if (process.env[key]) continue;
    let value = match[2].trim();
    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'"))
    ) {
      value = value.slice(1, -1);
    }
    process.env[key] = value.replace(/\\n/g, "\n");
  }
}

hydrateEnvFile(path.join(appRoot, ".env.example"));
hydrateEnvFile(path.join(appRoot, ".env"));

if (!process.env.APP_VERSION) {
  const packageJsonPath = path.join(appRoot, "package.json");
  if (fs.existsSync(packageJsonPath)) {
    try {
      const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
      if (packageJson.version) process.env.APP_VERSION = String(packageJson.version);
    } catch {
      // Ignore version hydration failures and keep runtime startup resilient.
    }
  }
}

const portStart = Number(process.env.PORT_START || process.argv[2] || "3010");
const portEnd = Number(process.env.PORT_END || "3099");
const dataDir = process.env.DATA_DIR || path.join(process.env.ProgramData || appRoot, "FashionAIDesignStudio", "data");
const runtimeStatusPath = path.join(path.dirname(dataDir), "runtime-status.json");

function isPortFree(port) {
  return new Promise((resolve) => {
    const server = net.createServer();
    server.once("error", () => resolve(false));
    server.once("listening", () => {
      server.close(() => resolve(true));
    });
    server.listen(port, "0.0.0.0");
  });
}

async function findPort() {
  for (let port = portStart; port <= portEnd; port += 1) {
    if (await isPortFree(port)) return port;
  }
  throw new Error(`No free port found between ${portStart} and ${portEnd}`);
}

function lanUrls(port) {
  return Object.values(os.networkInterfaces())
    .flat()
    .filter(Boolean)
    .filter((item) => item.family === "IPv4" && !item.internal)
    .map((item) => `http://${item.address}:${port}`);
}

function writeStatus(payload) {
  fs.mkdirSync(path.dirname(runtimeStatusPath), { recursive: true });
  fs.writeFileSync(
    runtimeStatusPath,
    JSON.stringify({ ...payload, lanUrls: lanUrls(payload.port), updatedAt: new Date().toISOString() }, null, 2),
    "utf8",
  );
}

const port = await findPort();
writeStatus({ status: "starting", port, pid: process.pid });

const standaloneServer = path.join(appRoot, "standalone", "server.js");
const legacyStandaloneServer = path.join(appRoot, ".next", "standalone", "server.js");
const fallbackServer = path.join(appRoot, "node_modules", ".bin", "next.cmd");
const serverPath = fs.existsSync(standaloneServer) ? standaloneServer : legacyStandaloneServer;
const command = fs.existsSync(serverPath) ? process.execPath : fallbackServer;
const args = fs.existsSync(serverPath)
  ? [serverPath]
  : ["start", "--hostname", "0.0.0.0", "--port", String(port)];

const child = spawn(command, args, {
  cwd: fs.existsSync(serverPath) ? path.dirname(serverPath) : appRoot,
  stdio: "inherit",
  env: {
    ...process.env,
    PORT: String(port),
    HOSTNAME: "0.0.0.0",
    DATA_DIR: dataDir,
    APP_PACKAGE_KIND: "exe",
  },
});

writeStatus({ status: "running", port, pid: child.pid });

child.on("exit", (code) => {
  writeStatus({ status: code === 0 ? "stopped" : "failed", port, pid: child.pid, exitCode: code });
  process.exit(code || 0);
});
