<?php

namespace App\Services;

use App\Models\Device;

class PingService
{
    /**
     * Ping a device via TCP socket (no exec/shell — safe on shared hosting).
     * Returns ['online'=>bool, 'latency_ms'=>int|null, 'message'=>string]
     */
    public static function ping(Device $device): array
    {
        $start = microtime(true);

        // TCP connect to the device port (Huidu :9527)
        $sock = @fsockopen($device->ip_address, $device->port, $errno, $errstr, 3);
        if ($sock) {
            fclose($sock);
            $ms = (int) round((microtime(true) - $start) * 1000);
            return [
                'online'     => true,
                'latency_ms' => $ms,
                'message'    => "TCP :{$device->port} reachable ({$ms} ms)",
            ];
        }

        // Fallback: try ICMP via raw socket (works without exec on most Linux servers)
        $icmpMs = self::icmpPing($device->ip_address);
        if ($icmpMs !== null) {
            return [
                'online'     => true,
                'latency_ms' => $icmpMs,
                'message'    => "ICMP reachable ({$icmpMs} ms)",
            ];
        }

        $ms = (int) round((microtime(true) - $start) * 1000);
        return [
            'online'     => false,
            'latency_ms' => null,
            'message'    => trim("Unreachable — {$errstr}") ?: 'No route to host',
        ];
    }

    /**
     * ICMP ping via raw socket — no exec() needed.
     * Returns latency in ms or null on failure.
     */
    private static function icmpPing(string $ip): ?int
    {
        if (!function_exists('socket_create')) return null;

        $sock = @socket_create(AF_INET, SOCK_RAW, 1); // IPPROTO_ICMP = 1
        if (!$sock) return null;

        @socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]);
        @socket_set_option($sock, SOL_SOCKET, SO_SNDTIMEO, ['sec' => 2, 'usec' => 0]);

        // Build ICMP echo request
        $data    = str_repeat('x', 32);
        $checksum = 0;
        $packet  = pack('C2n3A*', 8, 0, 0, 1, 1, $data);
        $bytes   = unpack('n*', $packet);
        foreach ($bytes as $b) $checksum += $b;
        while ($checksum >> 16) $checksum = ($checksum & 0xffff) + ($checksum >> 16);
        $packet = pack('C2n3A*', 8, 0, ~$checksum & 0xffff, 1, 1, $data);

        $start = microtime(true);
        $sent  = @socket_sendto($sock, $packet, strlen($packet), 0, $ip, 0);
        if (!$sent) { @socket_close($sock); return null; }

        $buf  = '';
        $from = '';
        $port = 0;
        $recv = @socket_recvfrom($sock, $buf, 255, 0, $from, $port);
        @socket_close($sock);

        if ($recv === false || $recv === 0) return null;

        $ms = (int) round((microtime(true) - $start) * 1000);
        return $ms;
    }

    /** Ping all active devices and update their last_status in the DB. */
    public static function pingAll(): array
    {
        $devices = Device::where('is_active', true)->get();
        $results = [];

        foreach ($devices as $device) {
            $r = self::ping($device);
            $device->update([
                'last_status'  => $r['online'] ? 'online' : 'offline',
                'last_seen_at' => $r['online'] ? now() : $device->last_seen_at,
            ]);
            $results[] = array_merge(['device' => $device], $r);
        }

        return $results;
    }
}
