import { randomUUID } from "node:crypto";
import { spawn } from "node:child_process";
import { chmod, chown, readFile, rename, stat, writeFile } from "node:fs/promises";
import { z } from "zod";
import { consumeHotspotConfigurationRequest, writeNetworkStatus } from "./network.js";

const config = z
  .object({
    NETWORK_CONFIGURATION_REQUEST_PATH: z.string().default("/var/lib/pi-car-companion/network-configuration-request.json"),
    NETWORK_STATUS_PATH: z.string().default("/var/lib/pi-car-companion/network-status.json"),
    NETWORK_HOTSPOT_INTERFACE: z.string().regex(/^[a-zA-Z0-9_.-]+$/).default("wlan0"),
    NETWORK_UPSTREAM_INTERFACE: z.string().regex(/^[a-zA-Z0-9_.-]+$/).default("wlan1"),
    NETWORK_HOTSPOT_CONNECTION: z.string().min(1).max(128).default("pi-car-hotspot"),
    NETWORK_ENV_PATH: z.string().default("/etc/pi-car-companion/companion.env"),
    NETWORK_PROFILE_DIR: z.string().default("/etc/NetworkManager/system-connections")
  })
  .parse(process.env);

async function command(executable: string, args: string[], timeoutMs = 20_000): Promise<string> {
  return new Promise((resolve, reject) => {
    const child = spawn(executable, args, { stdio: ["ignore", "pipe", "pipe"] });
    let stdout = "";
    let stderr = "";
    const timer = setTimeout(() => {
      child.kill("SIGKILL");
      reject(new Error("Command timed out"));
    }, timeoutMs);
    child.stdout.on("data", (chunk: Buffer) => {
      if (stdout.length < 32_768) stdout += chunk.toString("utf8");
    });
    child.stderr.on("data", (chunk: Buffer) => {
      if (stderr.length < 32_768) stderr += chunk.toString("utf8");
    });
    child.on("error", (error) => {
      clearTimeout(timer);
      reject(error);
    });
    child.on("close", (code) => {
      clearTimeout(timer);
      if (code === 0) resolve(stdout.trim());
      else reject(new Error(stderr.trim() || `Command exited with status ${code ?? "unknown"}`));
    });
  });
}

async function derivePsk(ssid: string, password: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const child = spawn("/usr/bin/wpa_passphrase", [ssid], { stdio: ["pipe", "pipe", "pipe"] });
    let stdout = "";
    let stderr = "";
    const timer = setTimeout(() => {
      child.kill("SIGKILL");
      reject(new Error("WPA key derivation timed out"));
    }, 10_000);
    child.stdout.on("data", (chunk: Buffer) => {
      if (stdout.length < 4096) stdout += chunk.toString("utf8");
    });
    child.stderr.on("data", (chunk: Buffer) => {
      if (stderr.length < 4096) stderr += chunk.toString("utf8");
    });
    child.on("error", (error) => {
      clearTimeout(timer);
      reject(error);
    });
    child.on("close", (code) => {
      clearTimeout(timer);
      const psk = stdout.match(/^\s*psk=([a-fA-F0-9]{64})$/m)?.[1];
      if (code === 0 && psk) resolve(psk);
      else reject(new Error(stderr.trim() || "The WPA key could not be derived"));
    });
    child.stdin.end(`${password}\n`);
  });
}

async function updateEnvironment(path: string): Promise<void> {
  const values: Record<string, string> = {
    NETWORK_MANAGER_ENABLED: "true",
    NETWORK_HOTSPOT_INTERFACE: config.NETWORK_HOTSPOT_INTERFACE,
    NETWORK_UPSTREAM_INTERFACE: config.NETWORK_UPSTREAM_INTERFACE,
    NETWORK_HOTSPOT_CONNECTION: config.NETWORK_HOTSPOT_CONNECTION,
    NETWORK_PROBE_TIMEOUT_SECONDS: "60",
    NETWORK_STABILITY_SECONDS: "10",
    NETWORK_POLL_SECONDS: "5"
  };
  const metadata = await stat(path);
  let content = await readFile(path, "utf8");
  for (const [key, value] of Object.entries(values)) {
    const expression = new RegExp(`^${key}=.*$`, "m");
    content = expression.test(content) ? content.replace(expression, `${key}=${value}`) : `${content.trimEnd()}\n${key}=${value}\n`;
  }
  const temporary = `${path}.network-${randomUUID()}.tmp`;
  await writeFile(temporary, content, { mode: 0o640 });
  await chown(temporary, metadata.uid, metadata.gid);
  await chmod(temporary, 0o640);
  await rename(temporary, path);
}

async function writeFailure(message: string): Promise<void> {
  const now = new Date().toISOString();
  await writeNetworkStatus(config.NETWORK_STATUS_PATH, {
    enabled: false,
    mode: "error",
    message: "Hotspot configuration failed. The previous network profile was left unchanged where possible.",
    updatedAt: now,
    lastTransitionAt: now,
    probeDeadline: null,
    lastFailure: message.slice(0, 500),
    connectivity: "unknown",
    hotspotSsid: null,
    hotspot: { interface: config.NETWORK_HOTSPOT_INTERFACE, available: true, connected: false, connection: null, address: null },
    upstream: { interface: config.NETWORK_UPSTREAM_INTERFACE, available: true, connected: false, connection: null, address: null }
  });
}

try {
  const request = await consumeHotspotConfigurationRequest(config.NETWORK_CONFIGURATION_REQUEST_PATH);
  const psk = await derivePsk(request.ssid, request.password);
  const profilePath = `${config.NETWORK_PROFILE_DIR}/${config.NETWORK_HOTSPOT_CONNECTION}.nmconnection`;
  const temporaryProfile = `${profilePath}.${randomUUID()}.tmp`;
  const profile = `[connection]\nid=${config.NETWORK_HOTSPOT_CONNECTION}\nuuid=${randomUUID()}\ntype=wifi\ninterface-name=${config.NETWORK_HOTSPOT_INTERFACE}\nautoconnect=false\n\n[wifi]\nband=bg\nmode=ap\nssid=${request.ssid}\n\n[wifi-security]\nkey-mgmt=wpa-psk\npsk=${psk}\n\n[ipv4]\naddress1=10.42.0.1/24\nmethod=shared\n\n[ipv6]\nmethod=disabled\n`;

  await writeFile(temporaryProfile, profile, { mode: 0o600 });
  await chmod(temporaryProfile, 0o600);
  await command("/usr/bin/systemctl", ["stop", "pi-car-companion-network.service"]).catch(() => undefined);
  await command("/usr/bin/nmcli", ["connection", "down", config.NETWORK_HOTSPOT_CONNECTION]).catch(() => undefined);
  await command("/usr/bin/nmcli", ["connection", "delete", config.NETWORK_HOTSPOT_CONNECTION]).catch(() => undefined);
  await rename(temporaryProfile, profilePath);
  await command("/usr/bin/nmcli", ["connection", "load", profilePath]);
  await updateEnvironment(config.NETWORK_ENV_PATH);
  await command("/usr/bin/systemctl", ["enable", "pi-car-companion-network.service"]);
  await command("/usr/bin/systemctl", ["restart", "pi-car-companion-network.service"]);
} catch (error) {
  const message = error instanceof Error ? error.message : "Unknown configuration failure";
  await writeFailure(message);
  process.exitCode = 1;
}
