<?php

namespace App\Console\Commands\Network;

use App\Console\Commands\BaseCommand;
use App\Models\Goliath\GoliathRuleset;
use App\Models\Goliath\GoliathSubregion;
use App\Models\Goliath\PrefixGoliathLoose;
use App\Models\PrefixCommunity;
use App\Models\Prefixes;
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use Symfony\Component\Process\Process;

class GoliathStoreConfig extends BaseCommand
{

    protected $signature = 'goliath:store-config';

    protected $description = 'Builds configuration for later distribution to goliath appliances.';

    // Creatia's name for a filter wherever it differs from goliath's (appliance/config/apps.json). The name a customer
    // sees says what the filter achieves rather than how it works.
    private const GOLIATH_FILTER_NAMES = [
        'fivem_double_handshake'    => 'fivem_tcp_gated',
        'fivemx_double_handshake'   => 'fivemx_tcp_gated'
    ];

    private function getRegions() {
        return [ ...GoliathSubregion::select(DB::raw('MIN(region_id) as region_id'), DB::raw('MIN(id) as subregion_id'), DB::raw('MIN(telemetry_identifier) as tag'))->whereNotNull('telemetry_identifier')->groupBy('telemetry_identifier')->get()->toArray() ];
    }

    private static function getRulesets(array $_ruleset_ids) {
        $current_rulesets = GoliathRuleset::withTrashed()
            ->whereIn('id', $_ruleset_ids)
            ->select('id', 'rules')
            ->get();

        // We first get the identifiers. We then merge in redirect values. We need to repeat this until the goto identifiers don't change.
        $goto_identifiers = [];
        $old_goto_count = 0;

        do {
            $old_goto_count = sizeof($goto_identifiers);

            foreach($current_rulesets as $x) {
                foreach(json_decode($x['rules'], true) as $y) {
                    if(isset($y['gotoNext'])) {
                        $goto_identifiers[] = $y['gotoNext'];
                    }
                }
            }
    
            $goto_identifiers = array_unique($goto_identifiers);
    
            $current_rulesets = $current_rulesets->merge(
                GoliathRuleset::withTrashed()
                    ->whereIn('id', array_merge($_ruleset_ids, $goto_identifiers))
                    ->select('id', 'rules')
                    ->get()
            )->unique()->sortBy('id');

        } while($old_goto_count != sizeof($goto_identifiers));

        // We then build the full configuration.
        $configuration = [];

        foreach($current_rulesets as $x) {
            $rules = [];

            foreach(json_decode($x['rules'], true) as $y) {
                switch($y['matchAction']) {
                    case 'police':
                        $rules[] = [
                            'bpfRule'       => $y['bpfRule'] ?? null,
                            'matchAction'   => 'police',


                            // These should be present, it is bad to assume all data is clean even with adequate steps. Mistakes happen. But they should not detonate a ruleset.
                            'bpsLimit' => $y['bpsLimit'] ?? 0,
                            'ppsLimit' => $y['ppsLimit'] ?? 0,

                            // These may not be present. If they're not, don't include them.
                            ...( isset($y['srcMask']) ? [ 'srcMask' => $y['srcMask'] ] : [] ),
                            ...( isset($y['dstMask']) ? [ 'srcMask' => $y['dstMask'] ] : [] )
                        ];
                    break;
                    case 'goto':
                        $rules[] = [
                            'bpfRule'       => $y['bpfRule'] ?? null,
                            'matchAction'   => 'redirect',
                            'gotoID'      => $y['gotoNext']
                        ];
                    break;
                    case 'accept':
                    case 'drop':
                        $rules[] = [
                            'bpfRule'       => $y['bpfRule'] ?? null,
                            'matchAction'   => $y['matchAction']
                        ];
                    break;
                }
            }

            // Check if the last rule has no bpfRule, and if it does not, and it is an accept, remove it.
            $last_key = array_key_last($rules);
            if($rules[$last_key]['bpfRule'] === null && $rules[$last_key]['matchAction'] === 'accept') {
                unset($rules[$last_key]);
            }
            elseif($rules[$last_key]['bpfRule'] === null) {
                unset($rules[$last_key]['bpfRule']);
            }

            $configuration[$x->id] = $rules;
        }

        return $configuration;
    }

    private static function getFlags(int $_region_id, int $_subregion_id, string $_dedi_lookup_key, string $_mercury_lookup_key, string $_origin_lookup_key) {

        // Select all prefixes and their chain identifiers, flags, etc where they are not deleted - Only select V4
        $nonspecific_rows = Prefixes::leftJoin('prefix_goliath_flags', function($join) {
                $join->on('prefix_goliath_flags.prefix_id', '=', 'prefixes.id')->whereNull('prefix_goliath_flags.deleted_at');
            })
            ->leftJoin('prefix_goliath_firewall', function($join) {
                $join->on('prefix_goliath_firewall.prefix_id', '=', 'prefixes.id')->whereNull('prefix_goliath_firewall.deleted_at');
            })
            ->join('asns', 'asn_id', 'asns.id')
            ->where(function($query) use($_region_id) { // Select region specifics for flags
                if($_region_id !== null) {
                    $query->orWhere('prefix_goliath_flags.region_id', '=', $_region_id)->orWhereNull('prefix_goliath_flags.region_id');
                }
                else {
                    $query->whereNull('prefix_goliath_flags.region_id');
                }
            })
            ->where(function($query) use($_subregion_id) {
                if($_subregion_id !== null) {
                    $query->orWhere('prefix_goliath_flags.subregion_id', '=', $_subregion_id)->orWhereNull('prefix_goliath_flags.subregion_id');
                }
                else {
                    $query->whereNull('prefix_goliath_flags.subregion_id');
                }
            })
            ->where(function($query) use($_region_id) { // Select region specifics for chains
                if($_region_id !== null) {
                    $query->orWhere('prefix_goliath_firewall.region_id', '=', $_region_id)->orWhereNull('prefix_goliath_firewall.region_id');
                }
                else {
                    $query->whereNull('prefix_goliath_firewall.region_id');
                }
            })
            ->where(function($query) use($_subregion_id) {
                if($_subregion_id !== null) {
                    $query->orWhere('prefix_goliath_firewall.subregion_id', '=', $_subregion_id)->orWhereNull('prefix_goliath_firewall.subregion_id');
                }
                else {
                    $query->whereNull('prefix_goliath_firewall.subregion_id');
                }
            })
            ->where(DB::raw('family(address)'), '=', 4)
            ->whereNotNull('prefix_goliath_flags.id') // Important - This stops yet to be configured prefixes from leaking through...
            ->select(
                DB::raw('prefixes.id as prefix_id'),
                DB::raw("CONCAT(prefixes.address, '/', prefixes.mask) as prefix"),
                'flag_heuristics',
                'flag_heuristics_level',
                'flag_synproxy',
                'flag_filters',
                'flag_special',
                'flag_block_udp',
                'flag_block_tcp',
                'flag_block_fragments',
                'flag_syn_cookies',
                'flag_drop_tcp_out_of_state',
                'flag_app_filter_strict',
                'flag_null_route',
                'flag_syn_cookie_data_promote',
                'flag_syn_cookie_os_gate',
                'flag_syn_challenge',
                'flag_syn_drop_first',
                'flag_src_limit_pps',
                'flag_src_limit_mbps',
                'flag_drop_unexpected_protocols',
                'flag_drop_non_core_protocols',
                'flag_drop_udp_to_tcp_ports',
                'flag_udp_established_lane',
                'ruleset_id',
                'prefixes.organisation_id as organisation_id',
                'asn',
                DB::raw('prefix_goliath_flags.region_id AS flag_region_id'),
                DB::raw('prefix_goliath_flags.subregion_id AS flag_subregion_id'),
                DB::raw('prefix_goliath_flags.metro_id AS flag_metro_id'),
                DB::raw('prefix_goliath_firewall.region_id AS chain_region_id'),
                DB::raw('prefix_goliath_firewall.subregion_id AS chain_subregion_id'),
                DB::raw('prefix_goliath_firewall.metro_id AS chain_metro_id'),
                DB::raw('CASE WHEN prefixes.organisation_id = 298 THEN 1 ELSE 0 END AS is_citec')
            )
            ->get()
            ->toArray();

        // Go over this to group specific rows
        $grouped_rows = [];

        foreach($nonspecific_rows as $row) {
            if(isset($grouped_rows[$row['prefix']])) {
                $grouped_rows[$row['prefix']][] = $row;
            }
            else {
                $grouped_rows[$row['prefix']] = [ $row ];
            }
        }

        // Select most specific row from group
        $specific_rows = [];

        // Reserve this for CITEC specific Australian functionality
        $in_australia = GoliathSubregion::whereIn('telemetry_identifier', ['adl', 'bne', 'mel', 'per', 'syd', 'akl'])->where('id', '=', $_subregion_id)->count();

        // Mercury lookup
        $mercury_lookup = json_decode(Redis::get($_mercury_lookup_key), true);

        // Dedicated BGP lookup
        $dedicated_bgp_lookup = json_decode(Redis::get($_dedi_lookup_key), true);
        
        foreach($grouped_rows as $rows) {

            if(sizeof($rows) === 1) {
                $to_add = [
                    'id'                    => $rows[0]['prefix_id'],
                    'prefix'                => $rows[0]['prefix'],
                    'asn'                   => $rows[0]['asn'],
                    'organisation'          => $rows[0]['organisation_id'],
                    'ruleset_id'            => $rows[0]['ruleset_id'],
                    'flag_heuristics_level' => $rows[0]['flag_heuristics_level'],
                    'flag_synproxy'         => $rows[0]['is_citec'] == 1 && $in_australia ? false : $rows[0]['flag_synproxy'], // Synproxy should always be off for CITEC in Australia
                    'flag_filters'          => $rows[0]['flag_filters'],
                    'flag_special'          => $rows[0]['flag_special'],
                    'flag_block_udp'        => $rows[0]['flag_block_udp'],
                    'flag_block_tcp'        => $rows[0]['flag_block_tcp'],
                    'flag_block_fragments'  => $rows[0]['flag_block_fragments'],
                    'flag_syn_cookies'      => $rows[0]['flag_syn_cookies'],
                    'flag_drop_tcp_out_of_state' => $rows[0]['flag_drop_tcp_out_of_state'],
                    'flag_app_filter_strict'     => $rows[0]['flag_app_filter_strict'],
                    'flag_null_route'               => $rows[0]['flag_null_route'],
                    'flag_syn_cookie_data_promote'  => $rows[0]['flag_syn_cookie_data_promote'],
                    'flag_syn_cookie_os_gate'       => $rows[0]['flag_syn_cookie_os_gate'],
                    'flag_syn_challenge'            => $rows[0]['flag_syn_challenge'],
                    'flag_syn_drop_first'           => $rows[0]['flag_syn_drop_first'],
                    'flag_src_limit_pps'            => $rows[0]['flag_src_limit_pps'],
                    'flag_src_limit_mbps'           => $rows[0]['flag_src_limit_mbps'],
                    'flag_drop_unexpected_protocols' => $rows[0]['flag_drop_unexpected_protocols'],
                    'flag_drop_non_core_protocols'  => $rows[0]['flag_drop_non_core_protocols'],
                    'flag_drop_udp_to_tcp_ports'    => $rows[0]['flag_drop_udp_to_tcp_ports'],
                    'flag_udp_established_lane'     => $rows[0]['flag_udp_established_lane']
                ];

                if(isset($mercury_lookup[$rows[0]['prefix_id']])) {
                    $to_add['mercury'] = true;
                }

                if(isset($dedicated_bgp_lookup[$rows[0]['prefix_id']])) {
                    $to_add['dedicated-bgp'] = true;
                }

                $specific_rows[] = $to_add;
            }
            else {
                $most_specific_flags = [
                    'ruleset_id'            => null,
                    'flag_heuristics_level' => null,
                    'flag_synproxy'         => null,
                    'flag_filters'          => null,
                    'flag_special'          => null,
                    'flag_block_udp'        => null,
                    'flag_block_tcp'        => null,
                    'flag_block_fragments'  => null,
                    'flag_syn_cookies'      => null,
                    'flag_drop_tcp_out_of_state' => null,
                    'flag_app_filter_strict'     => null,
                    'flag_null_route'               => null,
                    'flag_syn_cookie_data_promote'  => null,
                    'flag_syn_cookie_os_gate'       => null,
                    'flag_syn_challenge'            => null,
                    'flag_syn_drop_first'           => null,
                    'flag_src_limit_pps'            => null,
                    'flag_src_limit_mbps'           => null,
                    'flag_drop_unexpected_protocols' => null,
                    'flag_drop_non_core_protocols'  => null,
                    'flag_drop_udp_to_tcp_ports'    => null,
                    'flag_udp_established_lane'     => null
                ];

                $chain_metro_candidate = $chain_subregion_candidate = $chain_region_candidate = $flag_metro_candidate = $flag_subregion_candidate = $flag_region_candidate = null;
                
                $fallback_candidate = $rows[0];

                $is_citec = false;

                foreach($rows as $row) {
                    $is_citec = $row['is_citec'] == 1 ? true : false;

                    if($row['flag_metro_id'] !== null) {
                        $flag_metro_candidate = $row;
                    }
                    elseif($row['flag_subregion_id'] !== null) {
                        $flag_subregion_candidate = $row;
                    }
                    elseif($row['flag_region_id'] !== null) {
                        $flag_region_candidate = $row;
                    }

                    if($row['chain_metro_id'] !== null) {
                        $chain_metro_candidate = $row;
                    }
                    elseif($row['chain_subregion_id'] !== null) {
                        $chain_subregion_candidate = $row;
                    }
                    elseif($row['chain_region_id'] !== null) {
                        $chain_region_candidate = $row;
                    }
                }

                foreach(['chain_metro_candidate', 'chain_subregion_candidate', 'chain_region_candidate', 'fallback_candidate'] as $x) {
                    if($$x !== null) {
                        $most_specific_flags['ruleset_id'] = $$x['ruleset_id'];
                        break;
                    }
                }

                foreach(['flag_metro_candidate', 'flag_subregion_candidate', 'flag_region_candidate', 'fallback_candidate'] as $x) {
                    if($$x !== null) {
                        $most_specific_flags['flag_heuristics_level'] = $$x['flag_heuristics_level'];
                        $most_specific_flags['flag_synproxy'] = ($is_citec && $in_australia) ? false : $$x['flag_synproxy'];
                        $most_specific_flags['flag_filters'] = $$x['flag_filters'];
                        $most_specific_flags['flag_special'] = $$x['flag_special'];
                        $most_specific_flags['flag_block_udp'] = $$x['flag_block_udp'];
                        $most_specific_flags['flag_block_tcp'] = $$x['flag_block_tcp'];
                        $most_specific_flags['flag_block_fragments'] = $$x['flag_block_fragments'];
                        $most_specific_flags['flag_syn_cookies'] = $$x['flag_syn_cookies'];
                        $most_specific_flags['flag_drop_tcp_out_of_state'] = $$x['flag_drop_tcp_out_of_state'];
                        $most_specific_flags['flag_app_filter_strict'] = $$x['flag_app_filter_strict'];
                        $most_specific_flags['flag_null_route'] = $$x['flag_null_route'];
                        $most_specific_flags['flag_syn_cookie_data_promote'] = $$x['flag_syn_cookie_data_promote'];
                        $most_specific_flags['flag_syn_cookie_os_gate'] = $$x['flag_syn_cookie_os_gate'];
                        $most_specific_flags['flag_syn_challenge'] = $$x['flag_syn_challenge'];
                        $most_specific_flags['flag_syn_drop_first'] = $$x['flag_syn_drop_first'];
                        $most_specific_flags['flag_src_limit_pps'] = $$x['flag_src_limit_pps'];
                        $most_specific_flags['flag_src_limit_mbps'] = $$x['flag_src_limit_mbps'];
                        $most_specific_flags['flag_drop_unexpected_protocols'] = $$x['flag_drop_unexpected_protocols'];
                        $most_specific_flags['flag_drop_non_core_protocols'] = $$x['flag_drop_non_core_protocols'];
                        $most_specific_flags['flag_drop_udp_to_tcp_ports'] = $$x['flag_drop_udp_to_tcp_ports'];
                        $most_specific_flags['flag_udp_established_lane'] = $$x['flag_udp_established_lane'];

                        break;
                    }
                }

                if(isset($mercury_lookup[$rows[0]['prefix_id']])) {
                    $most_specific_flags['mercury'] = true;
                }

                if(isset($dedicated_bgp_lookup[$rows[0]['prefix_id']])) {
                    $most_specific_flags['dedicated-bgp'] = true;
                }

                $specific_rows[] = [
                    'id'                => $rows[0]['prefix_id'],
                    'prefix'            => $rows[0]['prefix'],
                    'asn'               => $rows[0]['asn'],
                    'organisation'      => $rows[0]['organisation_id'],
                    ...$most_specific_flags
                ];   
            }
        }

        // Sort specific rows
        $specific_rows = collect($specific_rows)->sortBy('prefix')->toArray();

        // Assemble data
        $assembled_config = [];
        $chain_identifiers = [];

        // Track whether we're doing origin aware heuristics.
        $origin_aware_heuristics = json_decode(Redis::get($_origin_lookup_key), true);
        
        foreach($specific_rows as $i => $row) {
            $to_add = [
                'organisation'      => $row['organisation'],
                'asn'               => $row['asn'],
                'synproxy'          => boolval($row['flag_synproxy']),
                'heuristicsLevel'   => $row['flag_heuristics_level'] ?? 0
            ];

            if(isset($row['mercury'])) {
                $to_add['mercury'] = true;
            }

            if(isset($row['dedicated-bgp'])) {
                $to_add['dedicatedBgp'] = true;
            }

            if(isset($origin_aware_heuristics[$row['id']])) { // If there is an OAH entry and the location for this configuration is accurate, step down strict to standard.
                try {
                    if(array_search($_subregion_id, array_values(json_decode($origin_aware_heuristics[$row['id']]['subregion_info'], true))) !== false) {
                        $to_add['heuristicsLevel'] = $to_add['heuristicsLevel'] == 3 ? 2 : $to_add['heuristicsLevel']; 
                    }
                }
                catch(\Exception $ex) {}

            }

            // Setup our filters where necessary
            if($row['flag_filters'] !== null && boolval($row['flag_filters'])) {
                $filters = json_decode($row['flag_filters'], true);

                $final = [];

                foreach($filters as $filter => $ports) {
                    $goliath_filter = self::GOLIATH_FILTER_NAMES[$filter] ?? $filter;

                    foreach($ports as $portrange) {
                        $final["$portrange"] = "$goliath_filter";
                    }
                }

                $to_add['gameFilters'] = $final;

                if($row['flag_block_udp']) {
                    $to_add['dropUDPUnknown'] = boolval($row['flag_block_udp']);
                }

                if($row['flag_block_tcp']) {
                    $to_add['dropTCPUnknown'] = boolval($row['flag_block_tcp']);
                }

                if($row['flag_app_filter_strict']) {
                    $to_add['appFilterStrict'] = boolval($row['flag_app_filter_strict']);
                }
            }

            if($row['ruleset_id'] !== null) {
                $to_add['chainID'] = $row['ruleset_id'];
            }

            if($row['flag_block_fragments'] !== null && $row['flag_block_fragments']) {
                $to_add['dropFragments'] = boolval($row['flag_block_fragments']);
            }

            if($row['flag_syn_cookies'] !== null && $row['flag_syn_cookies']) {
                $to_add['synCookies'] = boolval($row['flag_syn_cookies']);
            }

            if($row['flag_drop_tcp_out_of_state'] !== null && $row['flag_drop_tcp_out_of_state']) {
                $to_add['dropTCPOutOfState'] = boolval($row['flag_drop_tcp_out_of_state']);
            }

            if($row['flag_syn_cookie_data_promote'] !== null && $row['flag_syn_cookie_data_promote']) {
                $to_add['synCookieDataPromote'] = boolval($row['flag_syn_cookie_data_promote']);
            }

            if($row['flag_syn_cookie_os_gate'] !== null) {
                $to_add['synCookieOsGate'] = $row['flag_syn_cookie_os_gate'];
            }

            if($row['flag_syn_challenge'] !== null) {
                $to_add['synChallenge'] = $row['flag_syn_challenge'];
            }

            if($row['flag_syn_drop_first'] !== null && $row['flag_syn_drop_first']) {
                $to_add['synDropFirst'] = boolval($row['flag_syn_drop_first']);
            }

            if($row['flag_null_route'] !== null && $row['flag_null_route']) {
                $to_add['nullRoute'] = boolval($row['flag_null_route']);
            }

            // One limit per prefix. Validation refuses both, and goliath would run a prefix naming both with the pps one.
            if($row['flag_src_limit_pps'] !== null && $row['flag_src_limit_pps'] > 0) {
                $to_add['srcLimitPps'] = intval($row['flag_src_limit_pps']);
            }
            elseif($row['flag_src_limit_mbps'] !== null && $row['flag_src_limit_mbps'] > 0) {
                $to_add['srcLimitMbps'] = intval($row['flag_src_limit_mbps']);
            }

            // The strict protocol policy (ICMP, TCP and UDP only) supersedes the lenient one, so only one is ever sent.
            if($row['flag_drop_non_core_protocols'] !== null && $row['flag_drop_non_core_protocols']) {
                $to_add['dropNonCoreProtocols'] = boolval($row['flag_drop_non_core_protocols']);
            }
            elseif($row['flag_drop_unexpected_protocols'] !== null && $row['flag_drop_unexpected_protocols']) {
                $to_add['dropUnexpectedProtocols'] = boolval($row['flag_drop_unexpected_protocols']);
            }

            if($row['flag_drop_udp_to_tcp_ports'] !== null && $row['flag_drop_udp_to_tcp_ports']) {
                $to_add['dropUDPToTCPPorts'] = boolval($row['flag_drop_udp_to_tcp_ports']);
            }

            if($row['flag_udp_established_lane'] !== null && $row['flag_udp_established_lane']) {
                $to_add['udpEstablishedLane'] = boolval($row['flag_udp_established_lane']);
            }

            $chain_identifiers[$row['ruleset_id']] = $row['ruleset_id'];

            $assembled_config[$row['prefix']] = $to_add;
        }

        return [$assembled_config, $chain_identifiers];
    }

    private static function getConfiguration(int $_region_id, int $_subregion_id, string $_tag, string $_dedi_lookup_key, string $_mercury_lookup_key, string $_origin_lookup_key) {

        // Retrieve flags
        [ $flags, $ruleset_identifiers ] = self::getFlags($_region_id, $_subregion_id, $_dedi_lookup_key, $_mercury_lookup_key, $_origin_lookup_key);
    
        // Get chains
        $chains = self::getRulesets($ruleset_identifiers);

        // We should only store a given value if it's changed. We're also going to make the configuration identifier.
        $flag_label = "goliath-flags.$_tag";
        $chain_label = "goliath-chains.$_tag";

        $current_flags = Redis::get($flag_label);
        $current_chains = Redis::get($chain_label);

        // Collapse the configuration down
        $process = new Process(['python3', app_path('Bin/ConfigurationCrusher.py')]);

        $process->setInput(json_encode($flags, JSON_UNESCAPED_SLASHES));

        $process->run();

        // If there's an error with collapsing...
        if (!$process->isSuccessful()) {
            $err = json_decode($process->getErrorOutput(), true) ?? $process->getErrorOutput();

            (new self)->log_error("Error collapsing goliath configuration for $_tag", $err);

            return;
        }

        // We're decoding then re-encoding to ensure whatever ends up in there is indeed valid JSON.
        $new_flags = json_encode(json_decode($process->getOutput(), true, flags: JSON_THROW_ON_ERROR));
        $new_chains = json_encode($chains, JSON_UNESCAPED_SLASHES);

        $chains_change = false;
        $flag_change = false;

        if(md5($current_flags) != md5($new_flags)) {
            $flag_change = $flag_label;
            Redis::set($flag_label, $new_flags);
        }

        if(md5($current_chains) != md5($new_chains)) {
            $chains_change = $chain_label;
            Redis::set($chain_label, $new_chains);
        }

        return [ $flag_change, $chains_change ];
    }

    public function handle()
    {

        // Build configurations
        $regions = $this->getRegions();

        // Store things in Redis that only really need to be queried once but end up exceeding the arg-max if passed through via concurrency.
        $mercury_lookup_key = "$this->signature:mercury-lookup";
        $dedicated_bgp_lookup_key = "$this->signature:dedicated-bgp-lookup";
        $origin_lookup_key = "$this->signature:origin-aware-heuristics";

        Redis::setex($mercury_lookup_key, 3600, json_encode(PrefixCommunity::where('community', '=', '7578:92')->select('prefix_id')->get()->keyBy('prefix_id')));
        Redis::setex($dedicated_bgp_lookup_key, 3600, json_encode(PrefixCommunity::where('community', '=', '7578:91')->select('prefix_id')->get()->keyBy('prefix_id')));
        Redis::setex($origin_lookup_key, 3600, json_encode(PrefixGoliathLoose::select('prefix_id', 'subregion_info')->where('loose', '=', 1)->whereNotNull('subregion_info')->get()->keyBy('prefix_id')));

        $flags_changed = $chains_changed = [];

        foreach(array_chunk($regions, 4) as $chunk) {

            $tasks = [];

            foreach($chunk as $region) {
                // Early exit if the .env specifies configuration neutering
                if(env('goliath_configuration_neuter', false) && env('goliath_configuration_neuter') !== $region['tag']) {
                    $this->log_event("Skipping flags due to configuration neutering.", [ $region['tag'] ]);

                    continue;
                }
                
                $tasks[] =  static fn () => self::getConfiguration($region['region_id'], $region['subregion_id'], $region['tag'], $dedicated_bgp_lookup_key, $mercury_lookup_key, $origin_lookup_key);
            }

            $results = Concurrency::run($tasks);

            foreach($results as $x) {
                if($x[0]) {
                    $flags_changed[] = $x[0];
                }

                if($x[1]) {
                    $chains_changed[] = $x[1];
                }
            }
        }

        if(sizeof($flags_changed) != 0) {
            $this->log_event('The flags have changed for the following regions.', $flags_changed);
        }

        if(sizeof($chains_changed) != 0) {
            $this->log_event('The chains have changed for the following regions.', $chains_changed);
        }    
    
        return 0;
    }
}