<?php

namespace App\Services\Router;

use App\Models\Router;
use App\Support\RouterHost;
use RouterOS\Client;
use RouterOS\Query;
use Throwable;

/**
 * MikroTikLive
 *
 * Helper service for talking to MikroTik routers using the routeros-api-php client.
 * - All methods return arrays, never throw (catch and return error info) so controllers can easily respond JSON.
 */
class MikroTikLive
{
    /**
     * Build a RouterOS client for the given Router model.
     *
     * @param Router $router
     * @param int $timeout seconds
     * @return Client
     *
     * @throws \RuntimeException if connection parameters are missing (the caller should catch this).
     */
    public static function client(Router $router, int $timeout = 4): Client
    {
        $resolved = RouterHost::resolve($router);

        if (empty($resolved)) {
            throw new \RuntimeException('Router host could not be resolved');
        }

        $user = $router->username ?? null;
        $pass = null;

        if (!empty($router->password_enc)) {
            try {
                $pass = decrypt($router->password_enc);
            } catch (Throwable $e) {
                // fallback to plain password if decrypt fails
                $pass = $router->password ?? null;
            }
        } else {
            $pass = $router->password ?? null;
        }

        if (!$user || !$pass) {
            throw new \RuntimeException('Missing router credentials');
        }

        $config = [
            'host'    => $resolved,
            'user'    => $user,
            'pass'    => $pass,
            'port'    => (int)($router->api_port ?: 8728),
            'timeout' => max(2, $timeout),
            'ssl'     => (bool)$router->use_tls,
        ];

        return new Client($config);
    }

    /**
     * Read system resource summary (cpu, memory, hdd, uptime, identity, board)
     *
     * @param Router $router
     * @return array
     */
    public static function resources(Router $router): array
    {
        try {
            $c = self::client($router);

            $res = $c->query(new Query('/system/resource/print'))->read()[0] ?? [];
            $brd = $c->query(new Query('/system/routerboard/print'))->read()[0] ?? [];
            $idn = $c->query(new Query('/system/identity/print'))->read()[0] ?? [];

            return [
                'ok'            => true,
                'identity'      => $idn['name'] ?? null,
                'model'         => $res['platform'] ?? $brd['model'] ?? null,
                'board_name'    => $brd['board-name'] ?? null,
                'serial'        => $brd['serial-number'] ?? null,
                'cpu_load'      => isset($res['cpu-load']) ? (int)$res['cpu-load'] : null,
                'total_memory'  => isset($res['total-memory']) ? (int)$res['total-memory'] : null,
                'free_memory'   => isset($res['free-memory']) ? (int)$res['free-memory'] : null,
                'total_hdd'     => isset($res['total-hdd-space']) ? (int)$res['total-hdd-space'] : null,
                'free_hdd'      => isset($res['free-hdd-space']) ? (int)$res['free-hdd-space'] : null,
                'uptime'        => $res['uptime'] ?? null,
            ];
        } catch (Throwable $e) {
            return ['ok' => false, 'error' => $e->getMessage()];
        }
    }

    /**
     * List interfaces (name, enabled, running)
     *
     * @param Router $router
     * @return array
     */
    public static function interfaces(Router $router): array
    {
        try {
            $c = self::client($router);

            $ifs = $c->query((new Query('/interface/print'))->proplist('name,disabled,running'))->read();

            $out = [];
            foreach ($ifs as $i) {
                $name = $i['name'] ?? null;
                if (!$name) continue;
                $out[] = [
                    'name'    => $name,
                    'enabled' => ($i['disabled'] ?? 'false') !== 'true',
                    'running' => ($i['running'] ?? 'true') === 'true',
                ];
            }

            return ['ok' => true, 'data' => $out];
        } catch (Throwable $e) {
            return ['ok' => false, 'error' => $e->getMessage()];
        }
    }

    /**
     * Capture interface traffic bps by doing a two-sample read of tx/rx bytes.
     *
     * @param Router $router
     * @param int $sleepMs milliseconds between samples (default 900 ms)
     * @return array
     */
    public static function interfacesTraffic(Router $router, int $sleepMs = 900): array
    {
        try {
            $c = self::client($router);

            $readSnapshot = function() use ($c) {
                $q = (new Query('/interface/print'))->proplist('name,tx-byte,rx-byte,disabled,running');
                $rows = $c->query($q)->read();
                $snap = [];
                foreach ($rows as $r) {
                    $name = $r['name'] ?? null;
                    if (!$name) continue;
                    $snap[$name] = [
                        'rx' => isset($r['rx-byte']) ? (int)$r['rx-byte'] : 0,
                        'tx' => isset($r['tx-byte']) ? (int)$r['tx-byte'] : 0,
                        'enabled' => ($r['disabled'] ?? 'false') !== 'true',
                        'running' => ($r['running'] ?? 'true') === 'true',
                    ];
                }
                return $snap;
            };

            $a = $readSnapshot();
            // sleep between samples (microseconds)
            usleep(max(100, $sleepMs) * 1000);
            $b = $readSnapshot();

            $dt = max(0.001, $sleepMs / 1000.0);
            $bps = [];
            foreach ($b as $name => $curr) {
                $prev = $a[$name] ?? ['rx' => 0, 'tx' => 0];
                $rxBytes = max(0, $curr['rx'] - ($prev['rx'] ?? 0));
                $txBytes = max(0, $curr['tx'] - ($prev['tx'] ?? 0));
                $bps[] = [
                    'name'    => $name,
                    'enabled' => $curr['enabled'],
                    'running' => $curr['running'],
                    'rx_bps'  => (int) round($rxBytes * 8 / $dt),
                    'tx_bps'  => (int) round($txBytes * 8 / $dt),
                ];
            }

            // stable ordering
            usort($bps, fn($x, $y) => strcmp($x['name'], $y['name']));

            return ['ok' => true, 'data' => $bps];
        } catch (Throwable $e) {
            return ['ok' => false, 'error' => $e->getMessage()];
        }
    }

    /**
     * Lightweight raw test for ad-hoc connection testing (used by the "Test Connection" button).
     * Accepts an array with keys: host, username, password, api_port, use_tls
     *
     * @param array $cfg
     * @return array
     */
    public static function testRaw(array $cfg): array
{
    $host = $cfg['host'] ?? null;
    $user = $cfg['username'] ?? null;
    $pass = $cfg['password'] ?? null;
    $port = (int)($cfg['api_port'] ?? 8728);
    $ssl  = (bool)($cfg['use_tls'] ?? false);

    if (empty($host) || empty($user) || empty($pass)) {
        return ['ok' => false, 'error' => 'Missing host/username/password'];
    }

    $t0 = microtime(true);
    try {
        $client = new \RouterOS\Client([
            'host'    => $host,
            'user'    => $user,
            'pass'    => $pass,
            'port'    => $port,
            'timeout' => 4,
            'ssl'     => $ssl,
        ]);

        $res = $client->query(new \RouterOS\Query('/system/resource/print'))->read()[0] ?? [];
        $rb  = $client->query(new \RouterOS\Query('/system/routerboard/print'))->read()[0] ?? [];
        $id  = $client->query(new \RouterOS\Query('/system/identity/print'))->read()[0] ?? [];

        $ms = (int) round((microtime(true) - $t0) * 1000);

        return [
            'ok'        => true,
            'ms'        => $ms,
            'identity'  => $id['name'] ?? null,
            'model'     => $res['platform'] ?? $rb['model'] ?? null,
            'cpu_load'  => isset($res['cpu-load']) ? (int)$res['cpu-load'] : null,
            'uptime'    => $res['uptime'] ?? null,
            'total_ram' => $res['total-memory'] ?? null,
        ];
    } catch (\Throwable $e) {
        return ['ok' => false, 'error' => $e->getMessage()];
    }
}

}
