<?php

namespace App\Development\Bgp;

use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;

/**
 * This contains mocks responsible for actually forwarding requests to Aristas.
 * The specific things here are:
 *      + Prefix safety checking: Responsible for checking whether a prefix is already on-net or not.
 *      + Address pinging: Pings a destination from a bunch of routers across the network.
 *      + Reroute: Installs a static reroute on routers for a given prefix.
 *      + Unroute: Removes said installed route.
 */
class Forward
{
    // GSL's seeded on-net space - a redirect for anything in here is refused.
    private const ON_NET = ['103.0.0.0/22', '103.1.213.0/24', '113.0.0.0/24', '203.15.120.0/24'];

    // Mimics `show ip bgp <addr>` - on-net prefixes come back as a table entry, everything else isn't in the table.
    public function validate(array $params, Request $request)
    {
        $long = ip2long($params['addr'] ?? '');

        foreach(self::ON_NET as $cidr) {
            [$net, $bits] = explode('/', $cidr);
            $mask = -1 << (32 - (int)$bits);

            if($long !== false && (ip2long($net) & $mask) === ($long & $mask)) {
                return Http::response([ 'results' => "BGP routing table entry for $cidr, version 148\nPaths: (1 available, best #1, table default)\n  Local\n    0.0.0.0 from 0.0.0.0 (10.50.1.1)\n      Origin IGP, localpref 100, weight 32768, valid, sourced, local, best\n" ], 200);
            }
        }

        return Http::response([ 'results' => "% Network not in table\n" ], 200);
    }

    // Pings the address from every transit router, with a deterministic spread of hits, unreachables and timeouts.
    public function ping(array $params, Request $request)
    {
        $addr    = $params['addr'];
        $routers = json_decode(file_get_contents(app_path('Models/RouterList.json')), true);

        $results = [];

        $result = function(string $addr, string $vrf, string $seed) {
            $h      = crc32($seed . $addr);
            $bucket = $h % 100;

            if($bucket < 10) {
                return "ping: connect: Network is unreachable\n";
            }

            if($bucket < 20) {
                return "['enable', 'ping vrf {$vrf} {$addr} repeat 3']::: timed out";
            }

            $avg  = (($h >> 3) % 26000) / 100 + 0.1;
            $min  = max(0.05, $avg - ($h % 40) / 100);
            $max  = $avg + ($h % 55) / 100;
            $mdev = ($h % 90) / 100;
            $ttl  = 48 + ($h % 15);

            // Format last so the numbers keep a decimal and Arista's rtt regex still matches.
            $min  = number_format($min, 3, '.', '');
            $avg  = number_format($avg, 3, '.', '');
            $max  = number_format($max, 3, '.', '');
            $mdev = number_format($mdev, 3, '.', '');

            return implode("\n", [
                "PING {$addr} ({$addr}) 72(100) bytes of data.",
                "80 bytes from {$addr}: icmp_seq=1 ttl={$ttl} time={$avg} ms",
                "80 bytes from {$addr}: icmp_seq=2 ttl={$ttl} time={$avg} ms",
                "80 bytes from {$addr}: icmp_seq=3 ttl={$ttl} time={$avg} ms",
                "",
                "--- {$addr} ping statistics ---",
                "3 packets transmitted, 3 received, 0% packet loss, time 20ms",
                "rtt min/avg/max/mdev = {$min}/{$avg}/{$max}/{$mdev} ms, pipe 3, ipg/ewma 10.142/{$avg} ms",
                "",
            ]);
        };

        foreach($routers as $vrfs) {
            foreach($vrfs as $vrf => $entries) {

                // SITE_ID is just a number and INTERNET isn't a transit VRF we sweep.
                if(!is_array($entries) || $vrf === 'INTERNET') {
                    continue;
                }

                foreach($entries as $entry) {
                    $key = "{$entry['router']}-{$vrf}";
                    $results[$key] = $result($addr, $vrf, $key);
                }
            }
        }

        return Http::response([ 'results' => $results ], 200);
    }

    // Installs a static reroute - the service just hands back a true/false.
    public function reroute(array $params, Request $request)
    {
        return Http::response([ 'results' => true ], 200);
    }

    // Tears that reroute back down.
    public function unroute(array $params, Request $request)
    {
        return Http::response([ 'results' => true ], 200);
    }
}
