/**
 * Windows service installer for Siraya Printer Service.
 * Inno Setup: files already at SIRAYA_INSTALL_DIR; set SIRAYA_INNO_INSTALL=1.
 * Dev: node installer/windows/install-service.js (elevated)
 */
const path = require("path");
const fs = require("fs");
const { execSync } = require("child_process");
const { waitForHealth } = require("./health-probe");

const EXIT_OK = 0;
const EXIT_ERR = 1;

if (process.platform !== "win32") {
  console.error("This installer is for Windows only.");
  process.exit(EXIT_ERR);
}

const innoInstall = process.env.SIRAYA_INNO_INSTALL === "1";
const appRoot = innoInstall
  ? path.resolve(process.env.SIRAYA_INSTALL_DIR || "")
  : path.resolve(__dirname, "..", "..");
const installDir =
  process.env.SIRAYA_INSTALL_DIR ||
  path.join(process.env.ProgramFiles || "C:\\Program Files", "Siraya", "Printer");
const dataDir =
  process.env.SIRAYA_DATA_DIR ||
  path.join(process.env.ProgramData || "C:\\ProgramData", "Siraya", "Printer");
const serviceName = "SirayaPrinterService";
const bundledNode =
  process.env.SIRAYA_NODE_EXE ||
  path.join(installDir, "runtime", "node.exe");

function copyRecursive(src, dest) {
  if (!fs.existsSync(src)) return;
  fs.mkdirSync(dest, { recursive: true });
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
    const s = path.join(src, entry.name);
    const d = path.join(dest, entry.name);
    if (entry.isDirectory()) copyRecursive(s, d);
    else fs.copyFileSync(s, d);
  }
}

function ensureEnvFile() {
  fs.mkdirSync(dataDir, { recursive: true });
  const envExample = path.join(installDir, ".env.example");
  const envFile = path.join(dataDir, ".env");
  if (fs.existsSync(envFile)) return;

  let env;
  if (fs.existsSync(envExample)) {
    env = fs.readFileSync(envExample, "utf8");
  } else {
    env = [
      "HOST=127.0.0.1",
      "PORT=5000",
      "NODE_ENV=production",
      "PRINTER_MODE=spooler",
      "LOG_TO_FILE=true",
      "LOG_LEVEL=info",
    ].join("\n");
  }
  env += `\nPRINT_DATA_DIR=${dataDir.replace(/\\/g, "\\\\")}\\queue\n`;
  env += `SIRAYA_DATA_DIR=${dataDir.replace(/\\/g, "\\\\")}\n`;
  fs.writeFileSync(envFile, env);
}

function copyAppFiles() {
  const exclude = new Set(["node_modules", ".git", "installer", "test", "dist"]);
  fs.mkdirSync(installDir, { recursive: true });
  for (const name of fs.readdirSync(appRoot)) {
    if (exclude.has(name)) continue;
    const src = path.join(appRoot, name);
    const dest = path.join(installDir, name);
    const stat = fs.statSync(src);
    if (stat.isDirectory()) copyRecursive(src, dest);
    else fs.copyFileSync(src, dest);
  }
  if (fs.existsSync(path.join(appRoot, "node_modules"))) {
    copyRecursive(
      path.join(appRoot, "node_modules"),
      path.join(installDir, "node_modules"),
    );
  } else {
    execSync("npm ci --omit=dev", { cwd: installDir, stdio: "inherit" });
  }
}

function resolveNodeExe() {
  if (fs.existsSync(bundledNode)) return bundledNode;
  return process.execPath;
}

function sleepMs(ms) {
  try {
    const sec = Math.max(1, Math.ceil(ms / 1000));
    execSync(`timeout /t ${sec} /nobreak >nul`, { stdio: "ignore", shell: true });
  } catch {
    const end = Date.now() + ms;
    while (Date.now() < end) {
      /* wait */
    }
  }
}

function serviceExists() {
  try {
    execSync(`sc query ${serviceName}`, { stdio: "ignore" });
    return true;
  } catch {
    return false;
  }
}

function forceRemoveService() {
  if (!serviceExists()) return;
  console.log("Removing previous Windows service registration...");
  try {
    execSync(`net stop ${serviceName}`, { stdio: "ignore" });
  } catch {
    /* ignore */
  }
  sleepMs(2000);
  try {
    execSync(`sc delete ${serviceName}`, { stdio: "ignore" });
  } catch {
    /* ignore */
  }
  sleepMs(1500);
  const daemon = path.join(installDir, "daemon");
  if (fs.existsSync(daemon)) {
    fs.rmSync(daemon, { recursive: true, force: true });
  }
}

function installService() {
  const nodeExe = resolveNodeExe();
  const script = path.join(installDir, "index.js");
  if (!fs.existsSync(script)) {
    console.error("Missing index.js at", installDir);
    process.exit(EXIT_ERR);
  }

  forceRemoveService();

  const Service = require(path.join(installDir, "node_modules", "node-windows")).Service;

  const serviceEnv = [
    { name: "NODE_ENV", value: "production" },
    { name: "SIRAYA_WINDOWS_SERVICE", value: "1" },
    { name: "SIRAYA_DATA_DIR", value: dataDir },
    {
      name: "DOTENV_CONFIG_PATH",
      value: path.join(dataDir, ".env"),
    },
    { name: "HOST", value: "127.0.0.1" },
  ];

  const svc = new Service({
    name: serviceName,
    description: "Siraya POS thermal receipt print agent",
    script,
    workingDirectory: installDir,
    execPath: nodeExe,
    env: serviceEnv,
    grow: 0.25,
    wait: 2,
    maxRetries: 3,
  });

  let done = false;
  const finish = (code) => {
    if (done) return;
    done = true;
    process.exit(code);
  };

  const finishWithHealth = async (codeOnFail) => {
    console.log("Waiting for print agent /health...");
    const port = await waitForHealth({
      dataDir,
      timeoutSec: 90,
    });
    if (port) {
      console.log(`Health OK on port ${port}`);
      finish(EXIT_OK);
      return;
    }
    console.error("Health check failed after service start");
    finish(codeOnFail);
  };

  svc.on("install", () => {
    svc.start();
    console.log("Service installed and started:", serviceName);
    try {
      execSync(
        `sc failure ${serviceName} reset= 86400 actions= restart/60000/restart/60000/restart/60000`,
        { stdio: "ignore" },
      );
    } catch {
      /* optional */
    }
    void finishWithHealth(EXIT_ERR);
  });

  svc.on("alreadyinstalled", () => {
    console.log("Service already installed - restarting");
    try {
      execSync(`net stop ${serviceName}`, { stdio: "ignore" });
    } catch {
      /* ignore */
    }
    svc.start();
    void finishWithHealth(EXIT_ERR);
  });

  svc.on("error", (err) => {
    console.error("Service install error:", err);
    finish(EXIT_ERR);
  });

  setTimeout(() => {
    console.error("Service install timed out");
    finish(EXIT_ERR);
  }, 120000);

  svc.install();
}

function main() {
  console.log("Siraya Printer Service installer");
  console.log("  install dir:", installDir);
  console.log("  data dir:  ", dataDir);
  console.log("  inno mode: ", innoInstall);

  if (innoInstall && !fs.existsSync(path.join(installDir, "index.js"))) {
    console.error("Inno install: expected files at", installDir);
    process.exit(EXIT_ERR);
  }

  if (!innoInstall) {
    copyAppFiles();
  }

  ensureEnvFile();
  installService();
}

main();
