<?php
/**
 * SNMP Helper — Funções para coletar dados via SNMP v2c/v3
 */

// OIDs padrão utilizados
define('OID_UPTIME',        '1.3.6.1.2.1.1.3.0');
define('OID_CPU_LOAD',      '1.3.6.1.2.1.25.3.3.1.2');       // tabela: .1, .2...
define('OID_MEM_TOTAL',     '1.3.6.1.2.1.25.2.2.0');
define('OID_IF_DESCR',      '1.3.6.1.2.1.2.2.1.2');
define('OID_IF_NAME',       '1.3.6.1.2.1.31.1.1.1.1');   // ifName (fallback para ifDescr vazio)
define('OID_IF_ALIAS',      '1.3.6.1.2.1.31.1.1.1.18');
define('OID_IF_OPER',       '1.3.6.1.2.1.2.2.1.8');
define('OID_IF_SPEED',      '1.3.6.1.2.1.2.2.1.5');
define('OID_IF_IN_OCT',     '1.3.6.1.2.1.2.2.1.10');
define('OID_IF_OUT_OCT',    '1.3.6.1.2.1.2.2.1.16');
define('OID_IF_HC_IN',      '1.3.6.1.2.1.31.1.1.1.6');       // 64-bit
define('OID_IF_HC_OUT',     '1.3.6.1.2.1.31.1.1.1.10');      // 64-bit

function snmp_session(array $device): array {
    $version = $device['snmp_version'] ?? '2c';
    $s = [
        'host'    => $device['hostname'] . ':' . ($device['snmp_port'] ?? 161),
        'version' => $version,
        'timeout' => 5000000, // 5 segundos em microssegundos
        'retries' => 1,
    ];
    if ($version === '3') {
        $s['sec_name']   = $device['snmp_user']       ?? '';
        $s['auth_proto'] = $device['snmp_auth_proto'] ?? 'MD5';
        $s['auth_pass']  = $device['snmp_auth_pass']  ?? '';
        $s['priv_proto'] = $device['snmp_priv_proto'] ?? 'DES';
        $s['priv_pass']  = $device['snmp_priv_pass']  ?? '';
        // Determina nível de segurança
        if (!empty($s['auth_pass']) && !empty($s['priv_pass'])) {
            $s['sec_level'] = 'authPriv';
        } elseif (!empty($s['auth_pass'])) {
            $s['sec_level'] = 'authNoPriv';
        } else {
            $s['sec_level'] = 'noAuthNoPriv';
        }
    } else {
        $s['community'] = $device['snmp_community'] ?? 'public';
    }
    return $s;
}

/**
 * Faz snmpget simples (v2c ou v3)
 */
function nms_snmpget(array $device, string $oid): ?string {
    if (!function_exists('snmpget')) return null;
    try {
        $s = snmp_session($device);
        if ($s['version'] === '3') {
            if (!function_exists('snmp3_get')) return null;
            $val = @snmp3_get(
                $s['host'], $s['sec_name'], $s['sec_level'],
                $s['auth_proto'], $s['auth_pass'],
                $s['priv_proto'], $s['priv_pass'],
                $oid, $s['timeout'], $s['retries']
            );
        } else {
            $val = @snmp2_get($s['host'], $s['community'], $oid, $s['timeout'], $s['retries']);
        }
        if ($val === false) return null;
        return snmp_strip_type($val);
    } catch (Throwable $e) {
        return null;
    }
}

/**
 * Faz snmpwalk e retorna array [index => value]
 */
function nms_snmpwalk(array $device, string $oid): array {
    try {
        $s = snmp_session($device);
        if ($s['version'] === '3') {
            if (!function_exists('snmp3_real_walk')) return [];
            $raw = @snmp3_real_walk(
                $s['host'], $s['sec_name'], $s['sec_level'],
                $s['auth_proto'], $s['auth_pass'],
                $s['priv_proto'], $s['priv_pass'],
                $oid, $s['timeout'], $s['retries']
            );
        } else {
            if (!function_exists('snmp2_real_walk')) return [];
            $raw = @snmp2_real_walk($s['host'], $s['community'], $oid, $s['timeout'], $s['retries']);
        }
        if (!is_array($raw)) return [];
        $result = [];
        foreach ($raw as $key => $val) {
            // Extrai o índice numérico do final da OID
            $parts = explode('.', $key);
            $idx = end($parts);
            $result[$idx] = snmp_strip_type($val);
        }
        return $result;
    } catch (Throwable $e) {
        return [];
    }
}

/**
 * Remove o tipo SNMP do valor retornado (ex: "INTEGER: 5" → "5")
 */
function snmp_strip_type(string $val): string {
    if (preg_match('/:\s*(.+)$/', $val, $m)) {
        return trim($m[1]);
    }
    return trim($val);
}

/**
 * Coleta CPU do dispositivo.
 * Cascata: hrProcessorLoad (padrão) → MikroTik proprietário → Cisco PROCESS-MIB
 */
function nms_get_cpu(array $device): ?int {
    // 1ª tentativa: hrProcessorLoad (HOST-RESOURCES-MIB padrão) — MikroTik, Linux, etc.
    $vals = nms_snmpwalk($device, OID_CPU_LOAD);
    if (!empty($vals)) {
        $numeric = array_filter(array_map('intval', $vals), fn($v) => $v >= 0 && $v <= 100);
        if (!empty($numeric)) return (int)round(array_sum($numeric) / count($numeric));
    }

    // 2ª tentativa: OID proprietário MikroTik (laLoad.1 — CPU load 1min %)
    $val = nms_snmpget($device, '1.3.6.1.4.1.14988.1.1.3.1.0');
    if ($val !== null) {
        $cpu = (int)$val;
        if ($cpu >= 0 && $cpu <= 100) return $cpu;
    }

    // 3ª tentativa: Cisco PROCESS-MIB — cpmCPUTotal1minRev (média 1 min, todos os CPUs)
    $ciscoCpu = nms_snmpwalk($device, '1.3.6.1.4.1.9.9.109.1.1.1.1.7');
    if (!empty($ciscoCpu)) {
        $numeric = array_filter(array_map('intval', $ciscoCpu), fn($v) => $v >= 0 && $v <= 100);
        if (!empty($numeric)) return (int)round(array_sum($numeric) / count($numeric));
    }

    return null;
}

/**
 * Coleta uso de memória em %.
 * Cascata: hrStorageTable (padrão) → Cisco MEMORY-POOL-MIB
 */
function nms_get_memory_pct(array $device): ?int {
    // 1ª tentativa: hrStorageTable (padrão) — MikroTik, Linux, etc.
    $types = nms_snmpwalk($device, '1.3.6.1.2.1.25.2.3.1.2');
    $sizes = nms_snmpwalk($device, '1.3.6.1.2.1.25.2.3.1.5');
    $used  = nms_snmpwalk($device, '1.3.6.1.2.1.25.2.3.1.6');

    foreach ($types as $idx => $type) {
        // hrStorageRam = .1.3.6.1.2.1.25.2.1.2
        $isRam = str_ends_with($type, '25.2.1.2')
              || str_ends_with($type, '.1.2')
              || stripos($type, 'physicalMemory') !== false
              || stripos($type, 'ram') !== false;

        if ($isRam) {
            $size = (int)($sizes[$idx] ?? 0);
            $use  = (int)($used[$idx] ?? 0);
            if ($size > 0) {
                return (int)round(($use / $size) * 100);
            }
        }
    }

    // 2ª tentativa: Cisco MEMORY-POOL-MIB (ciscoMemoryPoolUsed + ciscoMemoryPoolFree)
    $ciscoUsed = nms_snmpwalk($device, '1.3.6.1.4.1.9.9.48.1.1.1.5');
    $ciscoFree = nms_snmpwalk($device, '1.3.6.1.4.1.9.9.48.1.1.1.6');
    if (!empty($ciscoUsed) && !empty($ciscoFree)) {
        $totalUsed = array_sum($ciscoUsed);
        $totalFree = array_sum($ciscoFree);
        $total = $totalUsed + $totalFree;
        if ($total > 0) {
            return (int)round(($totalUsed / $total) * 100);
        }
    }

    return null;
}

/**
 * Coleta uptime em segundos
 */
function nms_get_uptime(array $device): ?int {
    $val = nms_snmpget($device, OID_UPTIME);
    if ($val === null) return null;
    // Formato: "(123456) 0:20:34.56" ou "123456"
    if (preg_match('/\((\d+)\)/', $val, $m)) {
        return (int)((int)$m[1] / 100); // centisegundos → segundos
    }
    return (int)$val;
}

/**
 * Coleta tabela de interfaces: retorna array indexado por ifIndex
 */
function nms_get_interfaces(array $device): array {
    $descrs  = nms_snmpwalk($device, OID_IF_DESCR);
    $ifNames = nms_snmpwalk($device, OID_IF_NAME);   // fallback quando ifDescr vazio
    $aliases = nms_snmpwalk($device, OID_IF_ALIAS);
    $opers   = nms_snmpwalk($device, OID_IF_OPER);
    $speeds  = nms_snmpwalk($device, OID_IF_SPEED);
    $hcIn    = nms_snmpwalk($device, OID_IF_HC_IN);
    $hcOut   = nms_snmpwalk($device, OID_IF_HC_OUT);

    // Fallback para 32-bit se não tiver HC
    if (empty($hcIn))  $hcIn  = nms_snmpwalk($device, OID_IF_IN_OCT);
    if (empty($hcOut)) $hcOut = nms_snmpwalk($device, OID_IF_OUT_OCT);

    // União dos índices de ifDescr e ifName (cobre interfaces que só aparecem num dos dois)
    $allIndexes = array_unique(array_merge(array_keys($descrs), array_keys($ifNames)));

    $interfaces = [];
    foreach ($allIndexes as $idx) {
        $descr = trim($descrs[$idx] ?? '');
        $iName = trim($ifNames[$idx] ?? '');
        // Usa ifDescr se não estiver vazio, senão ifName, senão "Interface {idx}"
        $resolvedName = $descr !== '' ? $descr : ($iName !== '' ? $iName : "Interface {$idx}");

        $oper = $opers[$idx] ?? '2';
        $interfaces[$idx] = [
            'if_index'      => (int)$idx,
            'if_name'       => $resolvedName,
            'if_alias'      => $aliases[$idx] ?? null,
            'if_type'       => null,
            'if_speed_bps'  => isset($speeds[$idx]) ? (int)$speeds[$idx] : null,
            'status'        => ($oper === '1') ? 'up' : 'down',
            'in_octets'     => isset($hcIn[$idx])  ? (string)(int)$hcIn[$idx]  : null,
            'out_octets'    => isset($hcOut[$idx]) ? (string)(int)$hcOut[$idx] : null,
        ];
    }
    return $interfaces;
}
