<?php

declare(strict_types=1);

namespace App\Services\Snmp;

use RuntimeException;

/**
 * Service zur Ermittlung des Serverstatus per SNMP.
 *
 * Features:
 * - Robuste Fehlerbehandlung mit aussagekräftigen Exceptions
 * - Intelligente Fallback-Logik bei fehlenden oder unerwarteten OID-Beschreibungen
 * - Unterstützung für Windows (C:\) und Linux (/) Systeme
 * - Detailliertes Logging über Exceptions
 *
 * Wird vom DashboardController beim initialen Laden aufgerufen.
 * Das Live-Polling erfolgt über das API-Endpunkt (public/api/snmp_status.php).
 */
class SnmpServerStatusService
{
    /** @var array<string, mixed> SNMP-Konfiguration (Host, Community, Timeout, OIDs, etc.) */
    private array $config;

    /**
     * Erwartet den Teilbereich "snmp" aus der allgemeinen Konfiguration (config.php).
     *
     * @param array<string, mixed> $snmpConfig Konfiguration für die SNMP-Abfragen
     */
    public function __construct(array $snmpConfig)
    {
        $this->config = $snmpConfig;
    }

    /**
     * Liefert den aktuellen Serverstatus zurück.
     *
     * @return array<string, mixed> Assoziatives Array mit Statuswerten (Hostname, CPU%, RAM%, etc.)
     *
     * @throws RuntimeException wenn die SNMP-Konfiguration unvollständig ist oder Abfragen fehlschlagen
     */
    public function getServerStatus(): array
    {
        // --- 1. Konfiguration auslesen ---
        $host      = (string)($this->config['host'] ?? '127.0.0.1');
        $community = (string)($this->config['community'] ?? '');
        $oids      = $this->config['oids'] ?? [];
        $timeout   = (int)($this->config['timeout'] ?? 1) * 1_000_000; // in Mikrosekunden
        $retries   = (int)($this->config['retries'] ?? 1);

        // --- Prüfungen für essentielle Konfig-Werte ---
        if ($host === '') {
            throw new RuntimeException('SNMP-Konfiguration ist unvollständig (host fehlt).');
        }
        if ($community === '') {
            throw new RuntimeException('SNMP-Konfiguration ist unvollständig (community fehlt).');
        }
        if (empty($oids)) {
            throw new RuntimeException('SNMP-Konfiguration ist unvollständig (oids fehlen).');
        }

        if (!function_exists('snmpget')) {
            throw new RuntimeException('PHP-SNMP-Erweiterung ist nicht installiert.');
        }

        // Hilfsfunktion: SNMP-Werte bereinigen (z.B. "INTEGER: 123" -> 123)
        $cleanSnmpValue = fn($v) => (int)filter_var($v, FILTER_SANITIZE_NUMBER_INT);

        // --- 2. Hostname abfragen (dynamisch, nicht hardcoded) ---
        $hostnameOid = $oids['hostname'] ?? '1.3.6.1.2.1.1.5.0'; // sysName OID
        $hostnameResult = @snmpget($host, $community, $hostnameOid, $timeout, $retries);
        $hostnameFromSnmp = $hostnameResult ? trim(str_ireplace('STRING:', '', $hostnameResult), ' "') : $host;

        // --- 3. Uptime abfragen ---
        $uptimeOid = $oids['uptime'] ?? '1.3.6.1.2.1.1.3.0';
        $uptimeResult = @snmpget($host, $community, $uptimeOid, $timeout, $retries);
        if ($uptimeResult === false) {
            throw new RuntimeException("SNMP Uptime GET fehlgeschlagen.");
        }

        // Uptime aus TimeTicks (Hundertstel-Sekunden) in lesbar konvertieren
        preg_match('/\((.*?)\)/', $uptimeResult, $matches);
        $uptimeTicks = (int)($matches[1] ?? 0);
        $uptimeSeconds = $uptimeTicks / 100;
        $uptimeFormatted = sprintf(
            '%d Tage, %02d:%02d:%02d',
            (int)floor($uptimeSeconds / 86400),
            (int)floor(($uptimeSeconds % 86400) / 3600),
            (int)floor(($uptimeSeconds % 3600) / 60),
            (int)($uptimeSeconds % 60)
        );

        // --- 4. CPU (Durchschnitt über alle Kerne) ---
        $cpuTable = $oids['cpu_table'] ?? '1.3.6.1.2.1.25.3.3.1.2';
        $cpuValues = @snmpwalk($host, $community, $cpuTable, $timeout, $retries);

        if (!is_array($cpuValues) || empty($cpuValues)) {
            throw new RuntimeException("SNMP CPU WALK fehlgeschlagen.");
        }

        $cpuValues = array_map($cleanSnmpValue, $cpuValues);
        $cpuAvg = (int)round(array_sum($cpuValues) / count($cpuValues));

        // --- 5. Storage-Tabellen (RAM + Disks) ---
        $descrOid = $oids['storage_descr'] ?? '1.3.6.1.2.1.25.2.3.1.3';
        $unitsOid = $oids['storage_units'] ?? '1.3.6.1.2.1.25.2.3.1.4';
        $sizeOid = $oids['storage_size'] ?? '1.3.6.1.2.1.25.2.3.1.5';
        $usedOid = $oids['storage_used'] ?? '1.3.6.1.2.1.25.2.3.1.6';

        $descr = @snmpwalk($host, $community, $descrOid, $timeout, $retries);
        $units = @snmpwalk($host, $community, $unitsOid, $timeout, $retries);
        $size = @snmpwalk($host, $community, $sizeOid, $timeout, $retries);
        $used = @snmpwalk($host, $community, $usedOid, $timeout, $retries);

        if (!is_array($descr) || !is_array($units) || !is_array($size) || !is_array($used)) {
            throw new RuntimeException("SNMP Storage WALK fehlgeschlagen.");
        }

        // Werte bereinigen
        $descr = array_map(fn($v) => trim(str_ireplace('STRING:', '', $v), ' "'), $descr);
        $units = array_map($cleanSnmpValue, $units);
        $size = array_map($cleanSnmpValue, $size);
        $used = array_map($cleanSnmpValue, $used);

        // --- 6. RAM mit Fallback-Logik ---
        $ramPercent = null;
        $memTotalBytes = null;

        // Heuristik 1: Suche nach "Physical Memory"
        $ramIndex = array_search("Physical Memory", $descr);
        if ($ramIndex !== false) {
            $memTotalBytes = $units[$ramIndex] * $size[$ramIndex];
            $ramUsedBytes = $units[$ramIndex] * $used[$ramIndex];
            $ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0;
        }

        // Fallback 1: Wenn nicht gefunden, suche nach ähnlichen Labels
        if ($ramPercent === null) {
            foreach ($descr as $index => $description) {
                $lower = strtolower($description);
                if (strpos($lower, 'physical') !== false || strpos($lower, 'memory') !== false || strpos($lower, 'ram') !== false) {
                    $memTotalBytes = $units[$index] * $size[$index];
                    $ramUsedBytes = $units[$index] * $used[$index];
                    $ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0;
                    break;
                }
            }
        }

        // Fallback 2: Wenn immer noch nicht gefunden, nimm den kleinsten Eintrag (i.d.R. RAM)
        if ($ramPercent === null && count($descr) > 0) {
            $minIndex = 0;
            $minSize = PHP_INT_MAX;
            foreach ($size as $index => $s) {
                if ($s > 0 && $s < $minSize) {
                    $minSize = $s;
                    $minIndex = $index;
                }
            }
            $memTotalBytes = $units[$minIndex] * $size[$minIndex];
            $ramUsedBytes = $units[$minIndex] * $used[$minIndex];
            $ramPercent = ($memTotalBytes > 0) ? ($ramUsedBytes / $memTotalBytes) * 100 : 0;
        }

        // Fallback 3: Wenn gar nichts geht, Exception
        if ($ramPercent === null) {
            throw new RuntimeException("Konnte 'Physical Memory' in der SNMP Storage-Tabelle nicht finden.");
        }

        // --- 7. Disk C: / Root mit Fallback-Logik ---
        $diskCPercent = null;

        // Heuristik 1: Suche nach C:\
        foreach ($descr as $index => $description) {
            if (str_starts_with($description, 'C:\\')) {
                $cTotal = $units[$index] * $size[$index];
                $cUsed = $units[$index] * $used[$index];
                $diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0;
                break;
            }
        }

        // Fallback 1: Suche nach "C:" oder "root" oder "/"
        if ($diskCPercent === null) {
            foreach ($descr as $index => $description) {
                $lower = strtolower($description);
                if (strpos($lower, 'c:') !== false || $lower === '/' || strpos($lower, 'root') !== false) {
                    $cTotal = $units[$index] * $size[$index];
                    $cUsed = $units[$index] * $used[$index];
                    $diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0;
                    break;
                }
            }
        }

        // Fallback 2: Nimm den größten Eintrag > 1GB (wahrscheinlich der Hauptdatenträger)
        if ($diskCPercent === null) {
            $maxIndex = 0;
            $maxSize = 0;
            foreach ($size as $index => $s) {
                $sizeGB = ($s * $units[$index]) / (1024 ** 3);
                if ($sizeGB > 1 && $s > $maxSize) {
                    $maxSize = $s;
                    $maxIndex = $index;
                }
            }
            if ($maxSize > 0) {
                $cTotal = $units[$maxIndex] * $size[$maxIndex];
                $cUsed = $units[$maxIndex] * $used[$maxIndex];
                $diskCPercent = ($cTotal > 0) ? ($cUsed / $cTotal) * 100 : 0;
            }
        }

        // Fallback 3: Wenn immer noch nichts, Exception
        if ($diskCPercent === null) {
            throw new RuntimeException("Konnte Laufwerk 'C:\\' oder Root-Partition in der SNMP Storage-Tabelle nicht finden.");
        }

        // --- 8. Status-Array zusammenbauen ---
        $status = [
            'hostname' => $hostnameFromSnmp,
            'os' => 'Windows Server', // TODO: OS dynamisch per SNMP abfragen (OID 1.3.6.1.2.1.1.1.0)
            'uptime' => $uptimeFormatted,
            'cpu_usage' => $cpuAvg,
            'memory_usage' => (int)round($ramPercent),
            'disk_usage_c' => (int)round($diskCPercent),
            'last_update' => date('d.m.Y H:i:s'),
        ];

        return $status;
    }
}
