"use strict";

const fs = require("node:fs");
const http = require("node:http");
const net = require("node:net");
const path = require("node:path");
const { execFile } = require("node:child_process");

const SOCKET_PATH = process.env.LAKE_LAMP_ARCHITECTURE_PROVISION_SOCKET || "/run/guanghu-architecture-provision/provision.sock";
const REPO_DIR = process.env.ARCHITECTURE_PROVISION_REPO_DIR || "/var/lib/guanghu/architecture-provision/repo";
const REPO_URL = process.env.ARCHITECTURE_PROVISION_REPO_URL || "https://guanghulab.com/code/bingshuo/guanghu-ice-heart.git";
const RELEASES_DIR = process.env.ARCHITECTURE_PROVISION_RELEASES_DIR || "/opt/guanghu/architecture-releases";
const UNIT_DIR = process.env.ARCHITECTURE_PROVISION_UNIT_DIR || "/etc/systemd/system";
const RECEIPTS_DIR = process.env.ARCHITECTURE_PROVISION_RECEIPTS_DIR || "/var/lib/guanghu/architecture-provision/receipts";

function parseResource(value) {
  const match = String(value || "").match(/^([A-Z0-9][A-Z0-9._-]{5,119})@([0-9a-f]{40})$/);
  return match ? { requestId: match[1], commit: match[2] } : null;
}

function safeRelative(value) {
  const item = String(value || "");
  return item.length > 0 && item.length <= 240 && !path.isAbsolute(item) && !item.split("/").includes("..") && /^[A-Za-z0-9._/-]+$/.test(item);
}

function validateManifest(manifest, resource) {
  if (!manifest || !["guanghu.architecture-provision-request/v1", "guanghu.existing-service-update-request/v1"].includes(manifest.schema)) throw new Error("invalid_manifest_schema");
  if (manifest.request_id !== resource.requestId || manifest.target_node !== "JD-FD-PRIMARY") throw new Error("manifest_identity_mismatch");
  const unit = String(manifest.module && manifest.module.unit || "");
  if (!/^[A-Za-z0-9_.@-]+\.service$/.test(unit)) throw new Error("invalid_unit_name");
  if (manifest.schema === "guanghu.existing-service-update-request/v1") return validateServiceUpdateManifest(manifest, unit);
  if (manifest.status !== "ARCHITECTURE_PACKAGE_READY · INITIAL_PROVISION_PENDING") throw new Error("manifest_not_pending");
  if (!manifest.initial_provision || manifest.initial_provision.kind !== "new-architecture-unit") throw new Error("not_initial_architecture_unit");
  if (!Array.isArray(manifest.source_paths) || manifest.source_paths.length < 1 || manifest.source_paths.length > 64 || manifest.source_paths.some(item => !safeRelative(item))) throw new Error("invalid_source_paths");
  const unitMatches = manifest.source_paths.filter(item => path.basename(item) === unit);
  if (unitMatches.length !== 1) throw new Error("unit_not_uniquely_declared");
  const check = manifest.runtime_check || {};
  if (!/^http:\/\/127\.0\.0\.1:\d{2,5}\/[A-Za-z0-9._/?=&-]*$/.test(String(check.url || ""))) throw new Error("invalid_loopback_runtime_check");
  if (!check.expected || typeof check.expected !== "object" || Array.isArray(check.expected)) throw new Error("invalid_runtime_expectation");
  return { kind: "initial-provision", unit, unitSource: unitMatches[0], sourcePaths: manifest.source_paths, runtimeCheck: check };
}

function validateServiceUpdateManifest(manifest, unit) {
  if (manifest.status !== "SERVICE_UPDATE_PACKAGE_READY · DEPLOYMENT_PENDING") throw new Error("manifest_not_pending");
  if (!manifest.service_update || manifest.service_update.kind !== "existing-systemd-service" || manifest.service_update.require_existing_unit !== true) throw new Error("not_existing_service_update");
  const installRoot = String(manifest.module && manifest.module.install_root || "");
  if (!/^\/opt\/guanghu\/[a-z0-9][a-z0-9-]{1,62}$/.test(installRoot)) throw new Error("invalid_service_install_root");
  const unitSource = String(manifest.unit_source || "");
  if (!safeRelative(unitSource) || path.basename(unitSource) !== unit) throw new Error("invalid_unit_source");
  if (!Array.isArray(manifest.files) || manifest.files.length < 1 || manifest.files.length > 64) throw new Error("invalid_update_files");
  const seenDestinations = new Set();
  for (const item of manifest.files) {
    if (!item || !safeRelative(item.source) || !safeRelative(item.destination)) throw new Error("invalid_update_file_path");
    if (!/^(?:0?644|0?755)$/.test(String(item.mode || ""))) throw new Error("invalid_update_file_mode");
    if (seenDestinations.has(item.destination)) throw new Error("duplicate_update_destination");
    seenDestinations.add(item.destination);
  }
  const requiredExisting = manifest.service_update.required_existing_files || [];
  if (!Array.isArray(requiredExisting) || requiredExisting.some(item => !safeRelative(item) || !seenDestinations.has(item))) throw new Error("invalid_required_existing_files");
  const check = manifest.runtime_check || {};
  if (!/^http:\/\/127\.0\.0\.1:\d{2,5}\/[A-Za-z0-9._/?=&-]*$/.test(String(check.url || ""))) throw new Error("invalid_loopback_runtime_check");
  if (!check.expected || typeof check.expected !== "object" || Array.isArray(check.expected)) throw new Error("invalid_runtime_expectation");
  const acceptanceChecks = manifest.acceptance_checks || [];
  if (!Array.isArray(acceptanceChecks) || acceptanceChecks.length > 16) throw new Error("invalid_acceptance_checks");
  for (const item of acceptanceChecks) {
    if (!item || !/^http:\/\/127\.0\.0\.1:\d{2,5}\/[A-Za-z0-9._/?=&-]*$/.test(String(item.url || ""))) throw new Error("invalid_acceptance_check_url");
    if (!item.expected || typeof item.expected !== "object" || Array.isArray(item.expected)) throw new Error("invalid_acceptance_expectation");
  }
  return {
    kind: "existing-service-update",
    unit,
    unitSource,
    installRoot,
    files: manifest.files,
    requiredExisting,
    sourcePaths: [unitSource, ...manifest.files.map(item => item.source)],
    runtimeCheck: check,
    acceptanceChecks,
  };
}

function declaredPaths(value) {
  return String(value || "").split(/\s+/).filter(Boolean);
}

function pathAllowed(candidate, allowed) {
  const clean = String(candidate || "").replace(/^-/, "");
  return allowed.some(base => clean === base || clean.startsWith(`${base}/`));
}

function validateUnit(text, expectedUser = "guanghu", policy = {}) {
  const value = String(text || "");
  if (!value.includes("[Service]") || !/^NoNewPrivileges=(true|yes)$/m.test(value) || !/^ProtectSystem=strict$/m.test(value) || !/^ProtectHome=(true|yes)$/m.test(value) || !/^PrivateTmp=(true|yes)$/m.test(value)) throw new Error("unit_hardening_required");
  if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(expectedUser) || expectedUser === "root" || !new RegExp(`^User=${expectedUser}$`, "m").test(value) || !new RegExp(`^Group=${expectedUser}$`, "m").test(value)) throw new Error("dedicated_service_user_required");
  if (/^(SupplementaryGroups|AmbientCapabilities|CapabilityBoundingSet|BindPaths|BindReadOnlyPaths|RootDirectory|RootImage|DeviceAllow)=/m.test(value)) throw new Error("privileged_unit_directive_forbidden");
  const environmentFiles = Array.isArray(policy.environment_files) ? policy.environment_files : [];
  const writablePaths = Array.isArray(policy.writable_paths) ? policy.writable_paths : [];
  const readOnlyPaths = Array.isArray(policy.read_only_paths) ? policy.read_only_paths : [];
  for (const match of value.matchAll(/^EnvironmentFile=(.+)$/gm)) {
    if (!pathAllowed(match[1], environmentFiles) || !String(match[1]).replace(/^-/, "").startsWith("/etc/guanghu/persona-secrets/")) throw new Error("environment_file_not_declared");
  }
  for (const match of value.matchAll(/^ReadWritePaths=(.+)$/gm)) {
    for (const item of declaredPaths(match[1])) if (!pathAllowed(item, writablePaths) || !item.startsWith(`/var/lib/guanghu/personas/${expectedUser}`)) throw new Error("writable_path_not_declared");
  }
  for (const match of value.matchAll(/^ReadOnlyPaths=(.+)$/gm)) {
    for (const item of declaredPaths(match[1])) if (item !== "__RELEASE_ROOT__" && !pathAllowed(item, readOnlyPaths)) throw new Error("read_only_path_not_declared");
  }
  if (!value.includes("__RELEASE_ROOT__")) throw new Error("release_root_placeholder_required");
  return value;
}

function validateUpdateUnit(text, expectedUser, installRoot) {
  const value = String(text || "");
  if (!value.includes("[Service]") || !/^NoNewPrivileges=(true|yes)$/m.test(value) || !/^ProtectSystem=strict$/m.test(value) || !/^ProtectHome=(true|yes)$/m.test(value) || !/^PrivateTmp=(true|yes)$/m.test(value)) throw new Error("unit_hardening_required");
  if (!/^[a-z_][a-z0-9_-]{0,30}$/.test(expectedUser) || expectedUser === "root" || !new RegExp(`^User=${expectedUser}$`, "m").test(value) || !new RegExp(`^Group=${expectedUser}$`, "m").test(value)) throw new Error("dedicated_service_user_required");
  if (!value.includes(`WorkingDirectory=${installRoot}`) || !value.includes(`ReadOnlyPaths=${installRoot}`)) throw new Error("service_install_root_not_confined");
  const execStart = value.match(/^ExecStart=(.+)$/m);
  if (!execStart || !execStart[1].includes(`${installRoot}/`) || /[;&|`$<>]/.test(execStart[1])) throw new Error("service_exec_start_not_confined");
  if (/^(SupplementaryGroups|AmbientCapabilities|BindPaths|BindReadOnlyPaths|RootDirectory|RootImage|DeviceAllow|EnvironmentFile|ReadWritePaths)=/m.test(value)) throw new Error("privileged_unit_directive_forbidden");
  return value;
}

async function provision(request, options = {}) {
  if (!request || request.target !== "JD-FD-PRIMARY" || request.action !== "provision-approved-architecture") return { ok: false, error: "action_not_registered" };
  const resource = parseResource(request.resource);
  if (!resource) return { ok: false, error: "immutable_architecture_resource_required" };
  const repoDir = options.repoDir || REPO_DIR;
  const releasesDir = options.releasesDir || RELEASES_DIR;
  const unitDir = options.unitDir || UNIT_DIR;
  const receiptsDir = options.receiptsDir || RECEIPTS_DIR;
  const run = options.run || runFile;
  let installedUnit = null;
  let unitBackup = null;
  try {
    await prepareRepo(repoDir, resource.commit, run, options.repoUrl || REPO_URL);
    const manifestPath = path.join(repoDir, "deployment", "requests", `${resource.requestId}.json`);
    const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
    let checked = validateManifest(manifest, resource);
    if (checked.kind === "existing-service-update") {
      if (options.installRootOverride) checked = { ...checked, installRoot: options.installRootOverride };
      return await updateExistingService({
        checked, manifest, resource, repoDir, releasesDir, unitDir, receiptsDir, run,
        getJson: options.getJson, healthAttempts: options.healthAttempts, healthDelayMs: options.healthDelayMs,
      });
    }
    const releaseRoot = path.join(releasesDir, resource.commit);
    fs.mkdirSync(releaseRoot, { recursive: true, mode: 0o755 });
    for (const relative of checked.sourcePaths) copyDeclaredFile(repoDir, releaseRoot, relative);
    const unitSource = path.join(releaseRoot, checked.unitSource);
    const unitText = validateUnit(fs.readFileSync(unitSource, "utf8"), String(manifest.module.run_user || ""), manifest.module).replaceAll("__RELEASE_ROOT__", releaseRoot);
    fs.mkdirSync(unitDir, { recursive: true, mode: 0o755 });
    installedUnit = path.join(unitDir, checked.unit);
    if (fs.existsSync(installedUnit)) unitBackup = fs.readFileSync(installedUnit);
    const backupDir = path.join(receiptsDir, "backups", resource.requestId, resource.commit);
    fs.mkdirSync(backupDir, { recursive: true, mode: 0o700 });
    if (unitBackup) fs.writeFileSync(path.join(backupDir, checked.unit), unitBackup, { mode: 0o600 });
    else fs.writeFileSync(path.join(backupDir, `${checked.unit}.previously-absent`), "\n", { mode: 0o600 });
    writeAtomic(installedUnit, unitText, 0o644);
    await run("/usr/bin/systemctl", ["daemon-reload"]);
    await run("/usr/bin/systemctl", ["enable", "--now", checked.unit]);
    const runtime = await getJsonWithRetry(checked.runtimeCheck.url, options.getJson, options.healthAttempts, options.healthDelayMs);
    for (const [key, expected] of Object.entries(checked.runtimeCheck.expected)) if (runtime[key] !== expected) throw new Error(`runtime_check_failed:${key}`);
    const receipt = { schema: "guanghu.architecture-provision-receipt/v1", request_id: resource.requestId, source_commit: resource.commit, target_node: "JD-FD-PRIMARY", unit: checked.unit, runtime_check: checked.runtimeCheck.url, backup: path.join("backups", resource.requestId, resource.commit), rollback: unitBackup ? "restore-previous-unit" : "remove-new-unit", result: "DEPLOYED_AND_VERIFIED", recorded_at: new Date().toISOString() };
    fs.mkdirSync(receiptsDir, { recursive: true, mode: 0o700 });
    writeAtomic(path.join(receiptsDir, `${resource.requestId}.json`), `${JSON.stringify(receipt, null, 2)}\n`, 0o600);
    return { ok: true, request_id: resource.requestId, source_commit: resource.commit, unit: checked.unit, runtime: "verified" };
  } catch (error) {
    if (installedUnit) {
      try {
        await run("/usr/bin/systemctl", ["disable", "--now", path.basename(installedUnit)]);
        if (unitBackup) fs.writeFileSync(installedUnit, unitBackup, { mode: 0o644 });
        else fs.rmSync(installedUnit, { force: true });
        await run("/usr/bin/systemctl", ["daemon-reload"]);
        if (unitBackup) await run("/usr/bin/systemctl", ["enable", "--now", path.basename(installedUnit)]);
      } catch { /* The original error remains authoritative; backup is retained for manual recovery. */ }
    }
    return { ok: false, error: String(error && error.message || "provision_failed").slice(0, 240) };
  }
}

async function updateExistingService(context) {
  const { checked, manifest, resource, repoDir, releasesDir, unitDir, receiptsDir, run } = context;
  const releaseRoot = path.join(releasesDir, resource.commit);
  const installedUnit = path.join(unitDir, checked.unit);
  const backupDir = path.join(receiptsDir, "backups", resource.requestId, resource.commit);
  const backups = [];
  let updateStarted = false;
  try {
    if (!fs.existsSync(installedUnit) || !fs.lstatSync(installedUnit).isFile() || fs.lstatSync(installedUnit).isSymbolicLink()) throw new Error("existing_service_unit_required");
    for (const relative of checked.requiredExisting) {
      const existing = path.join(checked.installRoot, relative);
      if (!fs.existsSync(existing) || !fs.lstatSync(existing).isFile() || fs.lstatSync(existing).isSymbolicLink()) throw new Error(`required_existing_file_missing:${relative}`);
    }
    fs.mkdirSync(releaseRoot, { recursive: true, mode: 0o755 });
    for (const relative of checked.sourcePaths) copyDeclaredFile(repoDir, releaseRoot, relative);
    const unitText = validateUpdateUnit(fs.readFileSync(path.join(releaseRoot, checked.unitSource), "utf8"), String(manifest.module.run_user || ""), checked.installRoot);
    fs.mkdirSync(backupDir, { recursive: true, mode: 0o700 });
    backupFile(installedUnit, path.join(backupDir, "systemd", checked.unit), backups);
    for (const item of checked.files) {
      const destination = path.join(checked.installRoot, item.destination);
      backupFile(destination, path.join(backupDir, "files", item.destination), backups);
    }
    updateStarted = true;
    fs.mkdirSync(checked.installRoot, { recursive: true, mode: 0o755 });
    for (const item of checked.files) {
      const destination = path.join(checked.installRoot, item.destination);
      if (fs.existsSync(destination) && fs.lstatSync(destination).isSymbolicLink()) throw new Error("update_destination_symlink_forbidden");
      fs.mkdirSync(path.dirname(destination), { recursive: true, mode: 0o755 });
      writeAtomic(destination, fs.readFileSync(path.join(releaseRoot, item.source)), Number.parseInt(String(item.mode), 8));
    }
    writeAtomic(installedUnit, unitText, 0o644);
    await run("/usr/bin/systemctl", ["daemon-reload"]);
    await run("/usr/bin/systemctl", ["restart", checked.unit]);
    const runtime = await getJsonWithRetry(checked.runtimeCheck.url, context.getJson, context.healthAttempts, context.healthDelayMs);
    for (const [key, expected] of Object.entries(checked.runtimeCheck.expected)) if (runtime[key] !== expected) throw new Error(`runtime_check_failed:${key}`);
    for (const check of checked.acceptanceChecks) {
      const observed = await getJsonWithRetry(check.url, context.getJson, context.healthAttempts, context.healthDelayMs);
      for (const [key, expected] of Object.entries(check.expected)) if (observed[key] !== expected) throw new Error(`acceptance_check_failed:${key}`);
    }
    const receipt = {
      schema: "guanghu.existing-service-update-receipt/v1",
      request_id: resource.requestId,
      source_commit: resource.commit,
      target_node: "JD-FD-PRIMARY",
      unit: checked.unit,
      install_root: checked.installRoot,
      runtime_check: checked.runtimeCheck.url,
      acceptance_checks: checked.acceptanceChecks.map(item => item.url),
      backup: path.join("backups", resource.requestId, resource.commit),
      rollback: "restore-all-declared-files-and-previous-unit",
      result: "DEPLOYED_AND_VERIFIED",
      recorded_at: new Date().toISOString(),
    };
    fs.mkdirSync(receiptsDir, { recursive: true, mode: 0o700 });
    writeAtomic(path.join(receiptsDir, `${resource.requestId}.json`), `${JSON.stringify(receipt, null, 2)}\n`, 0o600);
    return { ok: true, request_id: resource.requestId, source_commit: resource.commit, unit: checked.unit, runtime: "verified" };
  } catch (error) {
    if (updateStarted) {
      try {
        restoreBackups(backups);
        await run("/usr/bin/systemctl", ["daemon-reload"]);
        await run("/usr/bin/systemctl", ["restart", checked.unit]);
      } catch { /* The original error remains authoritative; backups stay available for recovery. */ }
    }
    return { ok: false, error: String(error && error.message || "service_update_failed").slice(0, 240) };
  }
}

function backupFile(source, backup, records) {
  if (!fs.existsSync(source)) {
    records.push({ source, backup, existed: false });
    return;
  }
  const stat = fs.lstatSync(source);
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("backup_source_not_regular_file");
  fs.mkdirSync(path.dirname(backup), { recursive: true, mode: 0o700 });
  fs.copyFileSync(source, backup, fs.constants.COPYFILE_EXCL);
  fs.chmodSync(backup, stat.mode & 0o777);
  records.push({ source, backup, existed: true, mode: stat.mode & 0o777 });
}

function restoreBackups(records) {
  for (const record of records.slice().reverse()) {
    if (!record.existed) {
      fs.rmSync(record.source, { force: true });
      continue;
    }
    fs.mkdirSync(path.dirname(record.source), { recursive: true, mode: 0o755 });
    writeAtomic(record.source, fs.readFileSync(record.backup), record.mode);
  }
}

async function prepareRepo(repoDir, commit, run, repoUrl) {
  fs.mkdirSync(path.dirname(repoDir), { recursive: true, mode: 0o700 });
  if (!fs.existsSync(path.join(repoDir, ".git"))) await run("/usr/bin/git", ["clone", "--filter=blob:none", "--no-checkout", repoUrl, repoDir]);
  await run("/usr/bin/git", ["-C", repoDir, "fetch", "--depth=1", "origin", commit]);
  await run("/usr/bin/git", ["-C", repoDir, "checkout", "--detach", "--force", commit]);
  const head = (await run("/usr/bin/git", ["-C", repoDir, "rev-parse", "HEAD"])).stdout.trim();
  if (head !== commit) throw new Error("commit_verification_failed");
}

function copyDeclaredFile(repoDir, releaseRoot, relative) {
  const source = path.join(repoDir, relative);
  const stat = fs.lstatSync(source);
  if (!stat.isFile() || stat.isSymbolicLink()) throw new Error("declared_source_not_regular_file");
  const destination = path.join(releaseRoot, relative);
  fs.mkdirSync(path.dirname(destination), { recursive: true, mode: 0o755 });
  if (fs.existsSync(destination)) {
    if (!fs.readFileSync(source).equals(fs.readFileSync(destination))) throw new Error("immutable_release_collision");
    return;
  }
  fs.copyFileSync(source, destination, fs.constants.COPYFILE_EXCL);
  fs.chmodSync(destination, stat.mode & 0o755);
}

function writeAtomic(file, content, mode) {
  const temp = `${file}.${process.pid}.tmp`;
  fs.writeFileSync(temp, content, { mode });
  fs.renameSync(temp, file);
}

function runFile(file, args) {
  return new Promise((resolve, reject) => execFile(file, args, { timeout: 120000, maxBuffer: 200000 }, (error, stdout, stderr) => error ? reject(new Error(`command_failed:${path.basename(file)}:${String(stderr || error.message).slice(0, 120)}`)) : resolve({ stdout: String(stdout || ""), stderr: String(stderr || "") })));
}

function getJson(url, override) {
  if (override) return override(url);
  return new Promise((resolve, reject) => {
    const req = http.get(url, { timeout: 5000 }, response => {
      let body = "";
      response.on("data", chunk => { body += chunk; if (body.length > 100000) req.destroy(); });
      response.on("end", () => { try { resolve(JSON.parse(body)); } catch { reject(new Error("invalid_runtime_response")); } });
    });
    req.on("timeout", () => req.destroy(new Error("runtime_check_timeout")));
    req.on("error", reject);
  });
}

async function getJsonWithRetry(url, override, attempts = 15, delayMs = 1000) {
  let lastError;
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    try { return await getJson(url, override); }
    catch (error) {
      lastError = error;
      if (attempt < attempts) await new Promise(resolve => setTimeout(resolve, delayMs));
    }
  }
  throw lastError;
}

function reply(socket, value) { socket.end(`${JSON.stringify(value)}\n`); }

if (require.main === module) {
  fs.mkdirSync(path.dirname(SOCKET_PATH), { recursive: true, mode: 0o755 });
  try { fs.unlinkSync(SOCKET_PATH); } catch (error) { if (error.code !== "ENOENT") throw error; }
  const server = net.createServer({ allowHalfOpen: true }, socket => {
    let input = "";
    socket.setTimeout(140000, () => socket.destroy());
    socket.on("data", chunk => { input += chunk.toString("utf8"); if (input.length > 4096) socket.destroy(); });
    socket.on("end", async () => {
      let request;
      try { request = JSON.parse(input); } catch { return reply(socket, { ok: false, error: "invalid_request" }); }
      if (!request || request.cmd || request.command || request.shell || request.args) return reply(socket, { ok: false, error: "arbitrary_command_forbidden" });
      reply(socket, await provision(request));
    });
  });
  server.listen(SOCKET_PATH, () => { fs.chownSync(SOCKET_PATH, 0, Number(process.env.LAKE_LAMP_AUTHZ_GID || 0)); fs.chmodSync(SOCKET_PATH, 0o660); });
}

module.exports = { parseResource, safeRelative, validateManifest, validateUnit, validateUpdateUnit, provision };
