import { execFile } from "node:child_process";
import { promisify } from "node:util";
import { getConfig, SSH_KEY_PATH, SSH_TIMEOUT } from "../config.js";
import { setHost } from "../cache.js";
import type { HostConfig, HostStatus } from "../types.js";

const execFileAsync = promisify(execFile);

const NOTIFICATIONS_URL = process.env.NOTIFICATIONS_SERVICE_URL || "http://127.0.0.1:8087";
const previousHostStatus = new Map<string, string>();

function postNotification(title: string, description: string, level: string): void {
  fetch(`${NOTIFICATIONS_URL}/notifications`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title, description, source: "status", level }),
  }).catch(console.error);
}

function sshArgs(host: HostConfig): string[] {
  return [
    "-i", SSH_KEY_PATH,
    "-o", "StrictHostKeyChecking=accept-new",
    "-o", `ConnectTimeout=${SSH_TIMEOUT}`,
    "-o", "BatchMode=yes",
    `${host.user}@${host.ip}`,
  ];
}

async function runCommand(host: HostConfig, command: string): Promise<string> {
  if (host.type === "local") {
    const { stdout } = await execFileAsync("bash", ["-c", command], { timeout: 30_000 });
    return stdout;
  }
  const { stdout } = await execFileAsync("ssh", [...sshArgs(host), command], { timeout: (SSH_TIMEOUT + 5) * 1000 });
  return stdout;
}

function parseLinuxStats(raw: string): Partial<HostStatus> {
  const lines = raw.split("\n");
  const result: Partial<HostStatus> = {};

  for (const line of lines) {
    if (line.startsWith("CPU:")) result.cpu_percent = parseFloat(line.split(":")[1]) || 0;
    if (line.startsWith("MEM_USED:")) result.memory_used_bytes = parseInt(line.split(":")[1]) || 0;
    if (line.startsWith("MEM_TOTAL:")) result.memory_total_bytes = parseInt(line.split(":")[1]) || 0;
    if (line.startsWith("DISK_USED:")) result.disk_used_bytes = parseInt(line.split(":")[1]) || 0;
    if (line.startsWith("DISK_TOTAL:")) result.disk_total_bytes = parseInt(line.split(":")[1]) || 0;
    if (line.startsWith("UPTIME:")) result.uptime_seconds = parseFloat(line.split(":")[1]) || 0;
    if (line.startsWith("OS:")) result.os_version = line.substring(3);
    if (line.startsWith("UPDATES:")) result.pending_updates = parseInt(line.split(":")[1]) ?? -1;
    if (line.startsWith("SEC_UPDATES:")) result.pending_security_updates = parseInt(line.split(":")[1]) ?? -1;
  }

  return result;
}

const LINUX_STATS_SCRIPT = `
echo "CPU:$(top -bn1 | grep 'Cpu(s)' | awk '{print 100 - $8}')"
echo "MEM_USED:$(free -b | awk '/Mem:/{print $3}')"
echo "MEM_TOTAL:$(free -b | awk '/Mem:/{print $2}')"
echo "DISK_USED:$(df -B1 / | awk 'NR==2{print $3}')"
echo "DISK_TOTAL:$(df -B1 / | awk 'NR==2{print $2}')"
echo "UPTIME:$(awk '{print $1}' /proc/uptime)"
echo "OS:$(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME" || echo "Unknown")"
echo "UPDATES:$(apt list --upgradable 2>/dev/null | grep -c upgradable || echo -1)"
echo "SEC_UPDATES:$(apt list --upgradable 2>/dev/null | grep -c security || echo -1)"
`.trim().replace(/\n/g, " && ");

const QNAP_STATS_SCRIPT = `
echo "CPU:$(top -bn1 | grep 'CPU:' | awk '{print 100 - $8}' | tr -d '%')"
echo "MEM_USED:$(free | awk '/Mem:/{print $3 * 1024}')"
echo "MEM_TOTAL:$(free | awk '/Mem:/{print $2 * 1024}')"
echo "DISK_USED:$(df /share/CACHEDEV1_DATA 2>/dev/null | awk 'NR==2{print $3 * 1024}')"
echo "DISK_TOTAL:$(df /share/CACHEDEV1_DATA 2>/dev/null | awk 'NR==2{print $2 * 1024}')"
echo "UPTIME:$(awk '{print $1}' /proc/uptime)"
echo "OS:QNAP QTS $(cat /etc/version 2>/dev/null || echo Unknown)"
echo "UPDATES:-1"
echo "SEC_UPDATES:-1"
`.trim().replace(/\n/g, " && ");

const MAC_STATS_SCRIPT = `
echo "CPU:$(top -l 1 -n 0 | grep 'CPU usage' | awk '{print $3}' | sed 's/%//')"
echo "MEM_USED:$(vm_stat | awk '/Pages active|Pages wired/ {sum += $NF} END {print sum * 16384}')"
echo "MEM_TOTAL:$(sysctl -n hw.memsize)"
echo "DISK_USED:$(df -b / | awk 'NR==2{print $3}')"
echo "DISK_TOTAL:$(df -b / | awk 'NR==2{print $2}')"
echo "UPTIME:$(( $(date +%s) - $(sysctl -n kern.boottime | awk '{print $4}' | tr -d ',') ))"
echo "OS:$(sw_vers -productName) $(sw_vers -productVersion)"
echo "UPDATES:$(softwareupdate -l 2>&1 | grep -c 'recommended' || echo -1)"
echo "SEC_UPDATES:-1"
`.trim().replace(/\n/g, " && ");

function evaluateHostHealth(stats: Partial<HostStatus>): "online" | "degraded" {
  const cpuHigh = (stats.cpu_percent ?? 0) > 80;
  const memHigh = stats.memory_total_bytes && stats.memory_total_bytes > 0
    ? stats.memory_used_bytes! / stats.memory_total_bytes > 0.85
    : false;
  const diskHigh = stats.disk_total_bytes && stats.disk_total_bytes > 0
    ? stats.disk_used_bytes! / stats.disk_total_bytes > 0.8
    : false;
  const secUpdates = (stats.pending_security_updates ?? 0) > 0;

  return (cpuHigh || memHigh || diskHigh || secUpdates) ? "degraded" : "online";
}

async function collectHost(host: HostConfig): Promise<void> {
  const now = new Date().toISOString();
  try {
    const script = host.type === "local" ? MAC_STATS_SCRIPT : host.id === "qnapnas" ? QNAP_STATS_SCRIPT : LINUX_STATS_SCRIPT;
    const raw = await runCommand(host, script);
    const stats = parseLinuxStats(raw);

    const newStatus = evaluateHostHealth(stats);
    setHost({
      id: host.id,
      name: host.name,
      ip: host.ip,
      status: newStatus,
      cpu_percent: stats.cpu_percent ?? 0,
      memory_used_bytes: stats.memory_used_bytes ?? 0,
      memory_total_bytes: stats.memory_total_bytes ?? 0,
      disk_used_bytes: stats.disk_used_bytes ?? 0,
      disk_total_bytes: stats.disk_total_bytes ?? 0,
      uptime_seconds: stats.uptime_seconds ?? 0,
      os_version: stats.os_version ?? "Unknown",
      pending_updates: stats.pending_updates ?? -1,
      pending_security_updates: stats.pending_security_updates ?? -1,
      last_checked: now,
    });
    const prev = previousHostStatus.get(host.id);
    previousHostStatus.set(host.id, newStatus);
    if (prev === "offline") {
      postNotification(`Host Online: ${host.name}`, `${host.name} (${host.ip}) is back online`, "info");
    }
  } catch (e) {
    setHost({
      id: host.id,
      name: host.name,
      ip: host.ip,
      status: "offline",
      cpu_percent: 0,
      memory_used_bytes: 0,
      memory_total_bytes: 0,
      disk_used_bytes: 0,
      disk_total_bytes: 0,
      uptime_seconds: 0,
      os_version: "Unknown",
      pending_updates: -1,
      pending_security_updates: -1,
      last_checked: now,
    });
    const prev = previousHostStatus.get(host.id);
    previousHostStatus.set(host.id, "offline");
    if (prev !== undefined && prev !== "offline") {
      postNotification(`Host Offline: ${host.name}`, `${host.name} (${host.ip}) is unreachable`, "critical");
    }
    console.error(`Host ${host.name} (${host.ip}) unreachable:`, (e as Error).message);
  }
}

export async function collectAllHosts(): Promise<void> {
  const config = getConfig();
  await Promise.allSettled(config.hosts.map((h) => collectHost(h)));
}
