<?php

namespace App\Services;

/**
 * Reads + validates the host-written status file run/wg-status.json (bind-mounted to
 * storage/app/restart-signal). Single source for the WireGuard page + the /wg-status.json route.
 * Contains NO secrets — the collector never writes a private key.
 */
class WgStatus
{
    /** Status older than this (seconds) means the host collector is offline. */
    private const STALE_AFTER = 20;

    /**
     * A peer with a handshake within this many seconds is considered online. WireGuard is
     * connectionless, so "offline" can only be inferred from the handshake ageing out. Client configs
     * use PersistentKeepalive = 25, so an active peer re-handshakes well within this window; 150s is
     * the floor that detects a disconnect ~2.5 min after it happens without flapping live peers.
     */
    private const ONLINE_WITHIN = 150;

    private function path(): string
    {
        return storage_path('app/restart-signal/wg-status.json');
    }

    /**
     * @return array{configured:bool, at:int, stale:bool, gate?:array, wg_quick?:string, server?:array, peers?:array}
     */
    public function read(): array
    {
        $path = $this->path();
        $data = is_file($path) ? json_decode((string) @file_get_contents($path), true) : null;

        if (! is_array($data)) {
            return ['configured' => false, 'at' => 0, 'stale' => true];
        }

        $at = (int) ($data['at'] ?? 0);
        $stale = $at < (time() - self::STALE_AFTER);

        if (empty($data['configured'])) {
            return ['configured' => false, 'at' => $at, 'stale' => $stale];
        }

        $peers = [];
        foreach ((array) ($data['peers'] ?? []) as $p) {
            if (! is_array($p)) {
                continue;
            }
            $hs = (int) ($p['latest_handshake'] ?? 0);
            $peers[] = [
                'name' => (string) ($p['name'] ?? ''),
                'pubkey' => (string) ($p['pubkey'] ?? ''),
                'endpoint' => (string) ($p['endpoint'] ?? ''),
                'latest_handshake' => $hs,
                'rx' => (int) ($p['rx'] ?? 0),
                'tx' => (int) ($p['tx'] ?? 0),
                'online' => $hs > (time() - self::ONLINE_WITHIN),
            ];
        }

        return [
            'configured' => true,
            'at' => $at,
            'stale' => $stale,
            'gate' => [
                'marker' => (bool) ($data['gate']['marker'] ?? false),
                'chain' => (bool) ($data['gate']['chain'] ?? false),
                'ssh' => (bool) ($data['gate']['ssh'] ?? false),
            ],
            'wg_quick' => (string) ($data['wg_quick'] ?? 'inactive'),
            'server' => [
                'subnet' => (string) ($data['server']['subnet'] ?? ''),
                'server_ip' => (string) ($data['server']['server_ip'] ?? ''),
                'port' => (int) ($data['server']['port'] ?? 0),
                'endpoint' => (string) ($data['server']['endpoint'] ?? ''),
                'dns' => (string) ($data['server']['dns'] ?? ''),
                // Not rendered by the page, but part of the /wg-status.json response contract (API consumers).
                'pubkey' => (string) ($data['server']['pubkey'] ?? ''),
            ],
            'peers' => $peers,
        ];
    }
}
