// routes/nginx.js
import { Router } from "express";
import { promises as fs } from "fs";
import path from "node:path";
import { execFile } from "node:child_process";
import {
  STATIC_IP,
  PARENT_PATH,
  PUBLIC_WEB_ROOT,
  ERROR_LOG_ROOT,
} from "../server.js";

const router = Router();

// ---- absolute binaries (systemd PATH is tiny) ----
const SUDO_BIN = process.env.SUDO_BIN || "/usr/bin/sudo";
const NGINX_BIN = process.env.NGINX_BIN || "/usr/sbin/nginx";
const SYSTEMCTL_BIN = process.env.SYSTEMCTL_BIN || "/bin/systemctl";

// ---------- helpers ----------
const isValidIPv4 = (ip) =>
  /^(\d{1,3}\.){3}\d{1,3}$/.test(ip) &&
  ip.split(".").every((n) => {
    const v = Number(n);
    return v >= 0 && v <= 255 && String(v) === n;
  });

const isValidDomain = (d) =>
  /^(?!-)(?:[a-zA-Z0-9-]{1,63}\.)+[a-zA-Z]{2,63}$/.test(d);

const isValidPort = (p) =>
  Number.isInteger(Number(p)) && Number(p) >= 1 && Number(p) <= 65535;

// very small sanitizer to avoid path traversal via domain
function safeDomain(d) {
  if (!isValidDomain(d)) throw new Error("Invalid domain");
  return d.toLowerCase();
}

function buildSSL({ ip, domain, port }) {
  const basePath = path.posix.join(PARENT_PATH, domain);
  const publicPath = path.posix.join(PUBLIC_WEB_ROOT, domain);

  return `server {
    listen      ${ip}:443 ssl;
    server_name ${domain};
    error_log   ${path.posix.join(ERROR_LOG_ROOT, `${domain}.error.log`)} error;

    ssl_certificate      ${path.posix.join(basePath, "ssl", `${domain}.pem`)};
    ssl_certificate_key  ${path.posix.join(basePath, "ssl", `${domain}.key`)};

    location / {
        proxy_pass http://127.0.0.1:${port};
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location ~ /\\.(?!well-known\\/|file) {
        deny all;
        return 404;
    }

    location /error/ {
        alias ${path.posix.join(publicPath, "document_errors")}/;
    }

    disable_symlinks if_not_owner from=${path.posix.join(publicPath, "public_html")};

    include ${path.posix.join(basePath, "nginx.ssl.conf_*")};
}`;
}

function buildNonSSL({ ip, domain, port }) {
  const basePath = path.posix.join(PARENT_PATH, domain);
  const publicPath = path.posix.join(PUBLIC_WEB_ROOT, domain);

  return `server {
    listen      ${ip}:80;
    server_name ${domain};
    error_log   ${path.posix.join(ERROR_LOG_ROOT, `${domain}.error.log`)} error;

    include ${path.posix.join(basePath, "nginx.forcessl.conf*")};

    location / {
        proxy_pass http://127.0.0.1:${port};
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;

        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location ~ /\\.(?!well-known\\/|file) {
        deny all;
        return 404;
    }

    location /error/ {
        alias ${path.posix.join(publicPath, "document_errors")}/;
    }

    disable_symlinks if_not_owner from=${path.posix.join(publicPath, "public_html")};

    include ${path.posix.join(basePath, "nginx.conf_*")};
}`;
}

function sh(cmd, args = [], opts = {}) {
  return new Promise((resolve, reject) => {
    execFile(cmd, args, { timeout: 30_000, ...opts }, (err, stdout, stderr) => {
      if (err) return reject({ err, stdout, stderr });
      resolve({ stdout, stderr });
    });
  });
}

async function exists(p) {
  try {
    await fs.stat(p);
    return true;
  } catch {
    return false;
  }
}

// ---------- routes ----------

/**
 * POST /api/nginx/app
 * body: { domain: string, port: number|string, ip?: string, dryRun?: boolean, skipReload?: boolean }
 */
router.post("/nginx/app", async (req, res) => {
  try {
    const { domain, port, ip: ipIn, dryRun = false, skipReload = false } = req.body || {};

    if (!domain || !port) {
      return res.status(400).json({ ok: false, error: "domain and port are required" });
    }

    const ip = ipIn && String(ipIn).trim() ? String(ipIn).trim() : STATIC_IP;

    if (!isValidDomain(domain)) {
      return res.status(400).json({ ok: false, error: "Invalid domain" });
    }
    if (!isValidPort(port)) {
      return res.status(400).json({ ok: false, error: "Invalid port" });
    }
    if (!isValidIPv4(ip)) {
      return res.status(400).json({ ok: false, error: "Invalid ip" });
    }

    const dom = safeDomain(domain);
    const context = { ip, domain: dom, port: Number(port) };

    const sslConf = buildSSL(context);
    const nonsslConf = buildNonSSL(context);

    const domainDir = path.join(PARENT_PATH, dom);
    const sslFile = path.join(domainDir, "nginx.ssl.conf");
    const nonsslFile = path.join(domainDir, "nginx.conf");

    if (dryRun) {
      return res.json({
        ok: true,
        dryRun: true,
        files: {
          [nonsslFile]: nonsslConf,
          [sslFile]: sslConf,
        },
      });
    }

    // ensure directory exists
    await fs.mkdir(domainDir, { recursive: true });

    // preflight: can we write to the dir?
    try {
      await fs.access(domainDir, fs.constants.W_OK | fs.constants.X_OK);
    } catch {
      return res.status(403).json({
        ok: false,
        error: `No write access to ${domainDir} for this process. Fix permissions or run with sudo.`,
      });
    }

    // backup previous if exist (returns backup path or null)
    async function backupIfExists(fpath) {
      try {
        const stat = await fs.stat(fpath);
        if (stat.isFile()) {
          const bkp = `${fpath}.bak-${Date.now()}`;
          await fs.copyFile(fpath, bkp);
          return bkp;
        }
      } catch {}
      return null;
    }

    const nonsslBkp = await backupIfExists(nonsslFile);
    const sslBkp = await backupIfExists(sslFile);

    // write new files
    await fs.writeFile(nonsslFile, nonsslConf, "utf8");
    await fs.writeFile(sslFile, sslConf, "utf8");

    // nginx -t (as root, via sudo, with absolute paths)
    let testOut;
    try {
      //testOut = await sh(SUDO_BIN, [NGINX_BIN, "-t"]);
    } catch (e) {
      // restore backups if test fails
      try {
        if (nonsslBkp && (await exists(nonsslBkp))) await fs.copyFile(nonsslBkp, nonsslFile);
        if (sslBkp && (await exists(sslBkp))) await fs.copyFile(sslBkp, sslFile);
      } catch {}
      return res.status(500).json({
        ok: false,
        error: "nginx -t failed",
        details: e.stderr || e.stdout || String(e.err),
      });
    }

    // // reload (always via sudo/systemctl)
    // let reloadOut = { stdout: "", stderr: "" };
    // if (!skipReload) {
    //   try {
    //     reloadOut = await sh(SUDO_BIN, [SYSTEMCTL_BIN, "reload", "nginx"]);
    //   } catch (e) {
    //     return res.status(500).json({
    //       ok: false,
    //       error: "Failed to reload nginx",
    //       details: e.stderr || e.stdout || String(e.err),
    //       test: testOut,
    //     });
    //   }
    // }


    // reload (always via sudo)
let reloadOut = { stdout: "", stderr: "" };

if (!skipReload) {
  try {
    // 1) Test nginx configuration
    const testOut = await sh("sudo", ["nginx", "-t"]);
    if (testOut.stderr?.length) {
      // nginx -t writes to stderr even on success, so check exit code if sh() gives it
      console.log("nginx -t:", testOut.stderr);
    }

    // 2) Reload nginx if the test passed
    reloadOut = await sh("sudo", ["systemctl", "reload", "nginx"]);

    return res.json({
      ok: true,
      test: testOut,
      reload: reloadOut,
    });
  } catch (e) {
    return res.status(500).json({
      ok: false,
      error: "Failed to validate or reload nginx",
      details: e.stderr || e.stdout || String(e.err),
    });
  }
}


    return res.json({
      ok: true,
      written: [nonsslFile, sslFile],
      tested: testOut,
      reloaded: skipReload ? "skipped" : reloadOut,
      render: { nonssl: nonsslConf, ssl: sslConf },
    });
  } catch (err) {
    return res.status(500).json({ ok: false, error: String(err && err.message ? err.message : err) });
  }
});

export default router;
