import { readFile } from "node:fs/promises";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import os from "node:os";
import type { Availability } from "./types.js";
import { collectAdbStatus, DISABLED_ADB_CONFIG, type AdbConfig } from "./adb.js";

const execFileAsync = promisify(execFile);

type DiskStatus = { totalBytes: number; usedBytes: number; availableBytes: number; mount: string };

function available<T>(value: T): Availability<T> {
  return { available: true, value };
}

function unavailable<T>(reason: string): Availability<T> {
  return { available: false, reason };
}

async function cpuTemperature(): Promise<Availability<number>> {
  try {
    const raw = await readFile("/sys/class/thermal/thermal_zone0/temp", "utf8");
    const value = Number.parseInt(raw.trim(), 10) / 1000;
    return Number.isFinite(value) ? available(value) : unavailable("Kernel returned an invalid value");
  } catch {
    return unavailable("Thermal sensor is not exposed by this host");
  }
}

async function diskUsage(): Promise<Availability<DiskStatus>> {
  try {
    const { stdout } = await execFileAsync("df", ["-Pk", "/"], { timeout: 2_000 });
    const line = stdout.trim().split("\n").at(-1)?.trim().split(/\s+/);
    if (!line || line.length < 6) return unavailable("Could not parse disk usage");
    const total = Number(line[1]) * 1024;
    const used = Number(line[2]) * 1024;
    const free = Number(line[3]) * 1024;
    if (![total, used, free].every(Number.isFinite)) return unavailable("Disk usage was invalid");
    return available({ totalBytes: total, usedBytes: used, availableBytes: free, mount: line[5] ?? "/" });
  } catch {
    return unavailable("The df utility was unavailable or timed out");
  }
}

export async function collectSystemStatus(adbConfig: AdbConfig = DISABLED_ADB_CONFIG) {
  let interfaces: Array<{ name: string; address: string; family: string }> = [];
  let interfaceReason: string | null = null;
  try {
    interfaces = Object.entries(os.networkInterfaces()).flatMap(([name, addresses]) =>
      (addresses ?? [])
        .filter((address) => !address.internal)
        .map((address) => ({ name, address: address.address, family: address.family }))
    );
    if (interfaces.length === 0) interfaceReason = "No active non-loopback interface was found";
  } catch {
    interfaceReason = "Network interfaces are not exposed by this host";
  }
  const totalMemory = os.totalmem();
  const freeMemory = os.freemem();

  const [temperature, disk, headUnit] = await Promise.all([cpuTemperature(), diskUsage(), collectAdbStatus(adbConfig)]);
  return {
    collectedAt: new Date().toISOString(),
    hostname: os.hostname(),
    uptimeSeconds: os.uptime(),
    operatingSystem: `${os.type()} ${os.release()}`,
    cpu: {
      model: os.cpus()[0]?.model ?? "Unavailable",
      loadAverage: os.loadavg(),
      temperatureCelsius: temperature
    },
    memory: { totalBytes: totalMemory, usedBytes: totalMemory - freeMemory, availableBytes: freeMemory },
    disk,
    network: {
      interfaces,
      interfaceReason,
      wifi: unavailable<string>("Wi-Fi capability detection is scheduled for the next milestone")
    },
    service: { state: "healthy" as const, processUptimeSeconds: process.uptime() },
    headUnit
  };
}
