<?php

namespace App\Modules\Network\Controllers;

// Laravel
use Carbon\Carbon;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

use Symfony\Component\Process\Process;

// Creatia Default
use App\Helpers\Utility\GoliathBgp;
use App\Modules\Network\Controllers\NetworkController;
use App\Models\Asns;
use App\Models\SecurityEvent;

// Mitigation
use App\Modules\Network\Models\Prefixes;
use App\Modules\Network\Models\PrefixCommunity;
use App\Modules\Network\Models\Supernets;

// Mitigation
use App\Modules\Network\Models\Goliath\GoliathAppliance;
use App\Modules\Network\Models\Goliath\GoliathAttack;
use App\Modules\Network\Models\Goliath\Chain;
use App\Modules\Network\Models\Goliath\ChainElement;
use App\Modules\Network\Models\Goliath\ChainElementAction;
use App\Modules\Network\Models\Goliath\ChainElementOutcome;

use App\Modules\Network\Models\Goliath\ChainElementMatch;
use App\Modules\Network\Models\Goliath\GoliathActionType;
use App\Modules\Network\Models\Goliath\GoliathMetro;
use App\Modules\Network\Models\Goliath\GoliathRegion;
use App\Modules\Network\Models\Goliath\GoliathSubregion;
use App\Modules\Network\Models\Goliath\PrefixGoliathFirewall;
use App\Modules\Network\Models\Goliath\PrefixGoliathFlag;
use App\Modules\Network\Models\Goliath\PrefixGoliathInline;
use App\Modules\Network\Models\Goliath\GoliathApplianceCapture;
use App\Modules\Network\Models\Goliath\GoliathAsnAttributes;

use App\Helpers\ChartHandler;
use App\Helpers\ClickhouseHandler;
use App\Helpers\Utility\GoliathAttacks;
use Log;

class GoliathController extends NetworkController
{
    public function get_prefix_list(Request $_r, $_network_id = null) {
        return static::helper_prefix_list($_r, $_network_id, null);
    }

    public function get_chain_prefix_list(Request $_r, $_chain_id = null) {
        return static::helper_prefix_list($_r, null, $_chain_id);
    }

    // Called by the entrypoint functions get_prefixes and get_chain_prefixes since this reusable function can be called with either $_network_id XOR $_chain_id
    public function helper_prefix_list(Request $_r, $_network_id = null, $_chain_id = null) {

        if($_network_id == null || Prefixes::getOrganisationHasPrefix(Auth::user()->organisation_id, $_network_id)) {
            // Build our query function
            $query_builder = function() use($_network_id, $_chain_id) {
                $prefixes_relevant = Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id');

                $query = Prefixes::select(
                        'prefixes.organisation_id',
                        'prefixes.id',
                        'asn',
                        'name',
                        DB::raw("CONCAT(address, '/', mask) as network"),
                        DB::raw("(SELECT COUNT(*) FROM prefix_goliath_firewall WHERE prefix_id = prefixes.id AND chain_id IS NOT NULL AND prefix_goliath_firewall.deleted_at IS NULL) as ruleset_count"),
                        DB::raw('(SELECT COUNT(*) FROM supernets WHERE supernet_id = prefixes.id) AS "subnets"'),
                        DB::raw('(SELECT string_agg(community, \',\') from prefix_community where prefix_id = prefixes.id and deleted_at is null) as "community"'),
                        'created_source',
                        'still_announced'

                    )
                    ->leftJoin('asns', 'asn_id', '=', 'asns.id')
                    ->whereNull('prefixes.deleted_at')
                    ->whereRaw('family(address) = 4')
                    ->whereIn('prefixes.id', $prefixes_relevant);
                
                if($_chain_id !== null) {
                    $query = $query->leftJoin('prefix_goliath_firewall', 'prefix_goliath_firewall.prefix_id', '=', 'prefixes.id')
                        ->whereNull('prefix_goliath_firewall.deleted_at')
                        ->where('prefix_goliath_firewall.chain_id', '=', $_chain_id);
                }

                if($_network_id !== null) {
                    $query = $query->leftJoin('supernets', 'prefixes.id', '=', 'supernets.subnet_id')->where('supernets.supernet_id', '=', $_network_id);
                }

                return $query;
            };

            // Build our search function
            $query_search = function($prefix_query, $global, $filters) {

                if($global) {
                    $prefix_query = $prefix_query->where(function($query) use ($global){
                        $query->orWhere(DB::raw("CONCAT(address, '/', mask)"), 'LIKE', "%$global%")
                            ->orWhere('asn', 'LIKE', "%$global%")
                            ->orWhere('name', 'LIKE', "%$global%");
                    });
                }

                if($filters) {
                    foreach($filters as $filter) {
                        $col = '';
                        switch($filter['id']) {
                            case 'network': $col = DB::raw("CONCAT(address, '/', mask)"); break;
                            case 'asn': $col = 'asn'; break;
                            case 'name': $col = 'name'; break;
                        }

                        if($col != '') {
                            $prefix_query = $prefix_query->where($col, 'LIKE', '%' . $filter['value'] . '%');
                        }
                    }
                }

                return $prefix_query;
            };

            // Build our sorting function
            $query_sort = function($prefix_query, $column, $direction) {
                switch($column) {
                    case 'network':
                        $prefix_query = $prefix_query->orderBy('address', $direction)->orderBy('mask');
                        break;
                    case 'name':
                        $prefix_query = $prefix_query->orderBy('name', $direction);
                        break;
                    case 'asn':
                        $prefix_query = $prefix_query->orderBy('asn', $direction);
                        break;
                    default:
                        $prefix_query = $prefix_query->orderBy('address', $direction ?? 'asc')->orderBy('mask');
                }

                return $prefix_query;
            };

            // Create valid sorting keys
            $sorting_keys = ['network', 'name', 'asn'];

            // Build our transformation function
            $query_transform = function($data) {
                $flag_lookup = [];
                $parent_inline_lookup = [];

                $unfiltered_flags = PrefixGoliathFlag::leftJoin('goliath_metro', 'goliath_metro.id', '=', 'prefix_goliath_flags.metro_id')
                    ->leftJoin('goliath_regions', 'goliath_regions.id', '=', 'prefix_goliath_flags.region_id')
                    ->leftJoin('goliath_subregions', 'goliath_subregions.id', '=', 'prefix_goliath_flags.subregion_id')
                    ->leftJoin('prefixes', 'prefixes.id', '=', 'prefix_goliath_flags.prefix_id')
                    ->whereIn('prefix_id', $data->pluck('id'))
                    ->whereNull('prefixes.deleted_at')
                    ->select(
                        'prefix_id',
                        'flag_synproxy',
                        'flag_heuristics',
                        'flag_heuristics_level',
                        'flag_fivem',
                        'goliath_regions.telemetry_identifier as region',
                        'goliath_subregions.telemetry_identifier as subregion',
                        'goliath_metro.telemetry_identifier as datacentre'
                    )
                    ->get();

                $inline_lookup = PrefixGoliathInline::leftJoin('prefixes', 'prefixes.id', '=', 'prefix_goliath_inline.prefix_id')
                    ->whereIn('prefix_id', $data->pluck('id'))
                    ->whereNull('prefixes.deleted_at')
                    ->select('prefix_id', 'forced')
                    ->get()
                    ->keyBy('prefix_id');

                $parent_inlines = PrefixGoliathInline::leftJoin('supernets', 'supernet_id', '=', 'prefix_goliath_inline.prefix_id')
                    ->leftJoin('prefix_goliath_flags', 'prefix_goliath_flags.prefix_id', '=', 'prefix_goliath_inline.prefix_id')
                    ->leftJoin('prefixes', 'supernet_id', '=', 'prefixes.id')
                    ->whereIn('subnet_id', $data->pluck('id'))
                    ->where(function($x) {
                        $x->where('forced', '=', 1)
                            ->orWhere('flag_synproxy', '=', 1)
                            ->orWhere(DB::raw("(SELECT COUNT(*) FROM prefix_goliath_firewall WHERE prefix_goliath_firewall.prefix_id = supernet_id AND chain_id IS NOT NULL AND prefix_goliath_firewall.deleted_at IS NULL)"), '>', 0);
                    })
                    ->select('subnet_id', DB::raw("CONCAT(address, '/', mask) as network"))
                    ->distinct()
                    ->get();

                foreach($unfiltered_flags as $x) {
                    if(!isset($flag_lookup[$x->prefix_id])) {
                        $flag_lookup["$x->prefix_id"] = [[
                            'region' => $x->region,
                            'subregion' => $x->subregion,
                            'datacentre' => $x->datacentre,
                            'synproxy' => $x->flag_synproxy,
                            'fivem' => $x->flag_fivem,
                            'heuristics' => $x->flag_heuristics == 0 ? 0 : $x->flag_heuristics_level 
                        ]];
                    }
                    else {
                        $flag_lookup["$x->prefix_id"][] = [
                            'region' => $x->region,
                            'subregion' => $x->subregion,
                            'datacentre' => $x->datacentre,
                            'synproxy' => $x->flag_synproxy,
                            'fivem' => $x->flag_fivem,
                            'heuristics' => $x->flag_heuristics == 0 ? 0 : $x->flag_heuristics_level 
                        ];
                    }
                }

                foreach($parent_inlines as $x) {
                    if(!isset($parent_inline_lookup[$x->subnet_id])) {
                        $parent_inline_lookup["$x->subnet_id"] = [[ $x->network ]];
                    }
                    else {
                        $parent_inline_lookup["$x->subnet_id"][] = $x->network;
                    }
                }

                $data->transform(function($row) use($flag_lookup, $inline_lookup, $parent_inline_lookup) {

                    $row['flags'] = $flag_lookup[$row->id] ?? [];
                    $row['inline'] = isset($inline_lookup[$row->id]) ? $inline_lookup[$row->id]->forced : 0;
                    $row['community'] = array_reverse(explode(',', $row['community']));
                    $row['parent_inline'] = isset($parent_inline_lookup[$row->id]) ? $parent_inline_lookup[$row->id] : [];

                    switch($row['created_source']) {
                        case 'goliath-attack':
                            $row['source'] = 'attack';
                            break;
                        case 'user':
                            $row['source'] = 'user';
                            break;
                        default:
                            $row['source'] = 'import';
                    }

                    unset($row['created_source']);

                    if($row['community'][0] === "") {
                        $row['community'] = [];
                    }

                    return $row;
                });

                return $data;
            };

            return NetworkController::pagination_table_request($_r, $query_builder, $query_search, $query_sort, $query_transform, $sorting_keys);
        }
        
        return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
    }

    public function get_chains(Request $_r, $_prefix_id = null) {

        if($_prefix_id && !Prefixes::getOrganisationHasPrefix(Auth::user()->organisation_id, $_prefix_id)) {
            return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
        }

        // Build our query function
        $query_builder = function() use($_prefix_id) {
            $to_select = [
                'chains.uuid',
                'pps_limit as default_pps_limit',
                'bitrate as default_bitrate',
                'src_mask as default_src_mask',
                'dst_mask as default_dst_mask',
                'system_name as default_action',
                'pass_chain_id as default_goto',
                'chains.id',
                'chains.name',
            ];

            $query = Chain::leftJoin('chain_element_outcomes', 'chain_element_outcomes.chain_id', '=', 'chains.id')
            ->leftJoin('chain_element_actions', 'pass_action_id', '=', 'chain_element_actions.id')
            ->leftJoin('goliath_action_types', 'action_type_id', '=', 'goliath_action_types.id')
            ->whereNull('chain_element_outcomes.deleted_at')
            ->whereNull('chain_element_actions.deleted_at')
            ->whereNull('chain_element_outcomes.element_id')
            ->where(function ($q) {
                $q->orWhere('chains.organisation_id', '=', Auth::user()->organisation_id)
                    ->orWhere('chains.chain_type_id', '=', 1)
                    ->orWhere('chains.chain_type_id', '=', 4);
            });

            if($_prefix_id !== null) {
                $query = $query->leftJoin('prefix_goliath_firewall', 'chains.id', '=', 'prefix_goliath_firewall.chain_id')
                    ->leftJoin('prefixes', 'prefix_goliath_firewall.prefix_id', '=', 'prefixes.id')
                    ->leftJoin('goliath_metro', 'goliath_metro.id', '=', 'metro_id')
                    ->leftJoin('goliath_regions', 'goliath_regions.id', '=', 'region_id')
                    ->leftJoin('goliath_subregions', 'goliath_subregions.id', '=', 'prefix_goliath_firewall.subregion_id')
                    ->whereNull('prefix_goliath_firewall.deleted_at')
                    ->whereNull('prefixes.deleted_at')
                    ->where('prefix_id', '=', $_prefix_id);

                $to_select = array_merge($to_select, [
                    'goliath_regions.name as region',
                    'goliath_subregions.name as subregion',
                    'goliath_metro.name as metro',
                    DB::raw("CONCAT(prefixes.address, '/', prefixes.mask) as prefix")
                ]);
            }

            return $query->select($to_select);
        };

        // Build our search function
        $query_search = function($prefix_query, $global, $filters) {

            if($global) {
                $prefix_query = $prefix_query->where(function($query) use ($global){
                    $query->orWhere(DB::raw("CONCAT(address, '/', mask)"), 'LIKE', "%$global%")
                        ->orWhere('goliath_region.name', 'like', "%$global%")
                        ->orWhere('goliath_subregion.name', 'like', "%$global%")
                        ->orWhere('goliath_metro.name', 'like', "%$global%");
                });
            }

            if($filters) {
                foreach($filters as $filter) {
                    $col = '';
                    switch($filter['id']) {
                        case 'network': $col = DB::raw("CONCAT(address, '/', mask)"); break;
                        case 'goliath_region.name': $col = 'goliath_region.name'; break;
                        case 'goliath_subregion.name': $col = "goliath_subregion.name"; break;
                        case 'goliath_metro.name': $col = "goliath_metro.name"; break;
                    }

                    if($col != '') {
                        $prefix_query = $prefix_query->where($col, 'LIKE', '%' . $filter['value'] . '%');
                    }
                }
            }

            return $prefix_query;
        };

        // Build our sorting function
        $query_sort = function($prefix_query, $column, $direction) use($_prefix_id) {
            switch($column) {
                case 'network':
                    $prefix_query = $prefix_query->orderBy('address', $direction)->orderBy('mask');
                    break;
                case 'name':
                    $prefix_query = $prefix_query->orderBy('chains.name', $direction);
                    break;
                case 'region':
                    $prefix_query = $prefix_query->orderBy('goliath_region.name', $direction);
                    break;
                case 'subregion':
                    $prefix_query = $prefix_query->orderBy('goliath_subregion.name', $direction);
                    break;
                case 'metro':
                    $prefix_query = $prefix_query->orderBy('goliath_metro.name', $direction);
                    break;
                default:
                    $prefix_query = $prefix_query->orderBy($_prefix_id !== null ? 'prefix_goliath_firewall.id' : 'chains.created_at', $direction ?? 'asc');
            }

            return $prefix_query;
        };

        // Create valid sorting keys
        $sorting_keys = ['network', 'name', 'region', 'subregion', 'metro'];

        // Build our transformation function
        $query_transform = function($data) {
            // Chain elements in their totality
            $elements = ChainElement::leftJoin('chain_element_matches', 'chain_element_matches.id', '=', 'chain_elements.match_id')
                ->leftJoin('chain_element_outcomes', 'chain_element_outcomes.element_id', '=', 'chain_elements.id')
                ->leftJoin('chains as next_chain', 'pass_chain_id', '=', 'next_chain.id')
                ->leftJoin('chains as origin_chain', 'origin_chain.id', '=', 'chain_elements.chain_id')
                ->leftJoin('chain_element_actions', 'pass_action_id', '=', 'chain_element_actions.id')
                ->leftJoin('goliath_action_types', 'action_type_id', '=', 'goliath_action_types.id')
                ->select(
                    'origin_chain.uuid as origin_uuid',
                    'next_chain.uuid as next_uuid',
                    'pps_limit',
                    'bitrate',
                    'src_mask',
                    'dst_mask',
                    'pass_chain_id',
                    'system_name as action',
                    'position',
                    'bpf'
                )
                ->whereIn('chain_elements.chain_id', $data->pluck('id'))
                ->get();

            $rows = $data->toArray();

            $counts = PrefixGoliathFirewall::leftJoin('prefixes', 'prefixes.id', '=', 'prefix_goliath_firewall.prefix_id')
                    ->whereIn('prefix_goliath_firewall.chain_id', $data->pluck('id'))
                    ->where('prefixes.organisation_id', '=', Auth::user()->organisation_id)
                    ->whereNull('prefix_goliath_firewall.deleted_at')
                    ->select('prefix_goliath_firewall.chain_id', DB::raw('COUNT(*) as prefix_count'))
                    ->groupBy('prefix_goliath_firewall.chain_id')
                    ->get()
                    ->keyBy('chain_id');

            // Link up chain elements to their corresponding chain
            foreach($rows as $key => $row) {
                $rows[$key]['elements'] = array_values($elements->where('origin_uuid', '=', $row['uuid'])->toArray());
                $rows[$key]['prefix_count'] = isset($counts[$row['id']]) ? $counts[$row['id']]->prefix_count : 0;
            }

            return $rows;
        };

        return NetworkController::pagination_table_request($_r, $query_builder, $query_search, $query_sort, $query_transform, $sorting_keys);
    }

    public function get_chain(Request $_r, $_chain_id) {
        $valid_request = !Validator::make( $_r->all(), [ 'prefix_id' => [ Rule::in(Prefixes::where('organisation_id', '=', Auth::user()->organisation_id)->get()->pluck('id')) ] ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }
        
        // Build our query function
        $chain_query =  Chain::leftJoin('chain_element_outcomes', 'chain_element_outcomes.chain_id', '=', 'chains.id')
            ->leftJoin('chain_element_actions', 'pass_action_id', '=', 'chain_element_actions.id')
            ->leftJoin('goliath_action_types', 'action_type_id', '=', 'goliath_action_types.id')
            ->leftJoin('goliath_chain_types', 'chain_type_id', '=', 'goliath_chain_types.id')
            ->whereNull('chains.deleted_at')
            ->whereNull('chain_element_outcomes.element_id')
            ->whereNull('chain_element_outcomes.deleted_at')
            ->whereNull('chain_element_actions.deleted_at')
            ->where(function ($q) {
                $q->orWhere('chains.organisation_id', '=', Auth::user()->organisation_id)
                    ->orWhere('chains.chain_type_id', '=', 1)
                    ->orWhere('chains.chain_type_id', '=', 4);
            })
            ->where('chains.id', '=', $_chain_id)
            ->select(
                'pps_limit as default_pps',
                'bitrate as default_bps',
                'src_mask as default_src_mask',
                'dst_mask as default_dst_mask',
                'system_name as default_action',
                'pass_chain_id as default_goto',
                'chains.id',
                'chains.name',
                'default_x',
                'default_y',
                'goliath_chain_types.name as chain_type',
                'chains.organisation_id'
            );

            if($_r->input('prefix_id')) {
                $chain_query->leftJoin('prefix_goliath_firewall', 'chains.id', '=', 'prefix_goliath_firewall.chain_id')
                    ->whereNull('prefix_goliath_firewall.deleted_at')
                    ->where('prefix_id', '=', $_r->input('prefix_id'))
                    ->select(
                        'pps_limit as default_pps',
                        'bitrate as default_bps',
                        'system_name as default_action',
                        'pass_chain_id as default_goto',
                        'src_mask as default_src_mask',
                        'dst_mask as default_dst_mask',
                        'chains.id',
                        'chains.name',
                        'default_x',
                        'default_y',
                        'region_id',
                        'subregion_id',
                        'metro_id',
                        'goliath_chain_types.name as chain_type',
                        'chains.organisation_id'
                    );
            }

            $chain = $chain_query->get()
                ->first()
                ->toArray();

        // If there's no chain, end this...
        if(count($chain) == 0) {
            return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
        }

        // Chain elements in their totality
        $elements = ChainElement::leftJoin('chain_element_matches', 'chain_element_matches.id', '=', 'chain_elements.match_id')
            ->leftJoin('chain_element_outcomes', 'chain_element_outcomes.element_id', '=', 'chain_elements.id')
            ->leftJoin('chains as next_chain', 'pass_chain_id', '=', 'next_chain.id')
            ->leftJoin('chains as origin_chain', 'origin_chain.id', '=', 'chain_elements.chain_id')
            ->leftJoin('chain_element_actions', 'pass_action_id', '=', 'chain_element_actions.id')
            ->leftJoin('goliath_action_types', 'action_type_id', '=', 'goliath_action_types.id')
            ->select(
                'next_chain.id as goto_chain_id',
                'pps_limit as pps',
                'bitrate as bps',
                'src_mask',
                'dst_mask',
                'system_name as action',
                'position',
                'bpf',
                'x',
                'y'
            )
            ->where('chain_elements.chain_id', '=', $_chain_id)
            ->orderBy('position')
            ->get()
            ->toArray();

        $chain['elements'] = $elements;

        $chain['can_edit'] = $chain['organisation_id'] == Auth::user()->organisation_id;
        unset($chain['organisation_id']);

        return response()->json($chain, 200);
    }

    public function get_chain_options(Request $_r) {
        $chains = Chain::leftJoin('chain_element_outcomes', 'chain_element_outcomes.chain_id', '=', 'chains.id')
            ->leftJoin('chain_element_actions', 'pass_action_id', '=', 'chain_element_actions.id')
            ->where(function ($q) {
                $q->orWhere('chains.organisation_id', '=', Auth::user()->organisation_id)
                    ->orWhere('chains.chain_type_id', '=', 1)
                    ->orWhere('chains.chain_type_id', '=', 4);
            })
            ->whereNull('chains.deleted_at')
            ->whereNull('chain_element_outcomes.element_id')
            ->whereNull('chain_element_outcomes.pass_chain_id')
            ->whereNull('chain_element_outcomes.deleted_at')
            ->whereNull('chain_element_actions.deleted_at')
            ->whereNull('chain_element_outcomes.element_id')
            ->select(
                'chains.id',
                'chains.name',
            )
            ->get();

        if($chains) {
            // Chain elements in their totality
            $elements = ChainElement::leftJoin('chain_element_matches', 'chain_element_matches.id', '=', 'chain_elements.match_id')
                ->leftJoin('chain_element_outcomes', 'chain_element_outcomes.element_id', '=', 'chain_elements.id')
                ->select('chain_elements.chain_id')
                ->where('chain_element_outcomes.pass_chain_id')
                ->whereIn('chain_elements.chain_id', $chains->pluck('id'))
                ->get();

            foreach($chains as $key => $chain) {            
                if(!$elements->where('chain_id', '=', $chain->id)->count()) {
                    $chains->forget($key);
                }
            }
        }

        $appliances = GoliathAppliance::all();
        $metro_ids = $appliances->pluck('metro_id');
        $region_ids = $appliances->pluck('region_id');
        $subregion_ids = $appliances->pluck('subregion_id');

        return response()->json([ 
            'chains' => array_values($chains->toArray()),
            'metros' => GoliathMetro::whereIn('id', $metro_ids)->get()->toArray(),
            'regions' => GoliathRegion::whereIn('id', $region_ids)->get()->toArray(),
            'subregions' => GoliathSubregion::whereIn('id', $subregion_ids)->get()->toArray()
        ], 200);
    }

    public function get_chain_prefixes(Request $_r, $_chain_id) {
        try {
            $valid_chain = Chain::where([['organisation_id', '=', Auth::user()->organisation_id], ['id', '=', $_chain_id]])->get()->count();

            if($valid_chain) {
                $prefixes = Chain::leftJoin('prefix_goliath_firewall', 'chains.id', '=', 'prefix_goliath_firewall.chain_id')
                ->leftJoin('prefixes', 'prefix_goliath_firewall.prefix_id', '=', 'prefixes.id')
                ->where('prefixes.organisation_id', '=', Auth::user()->organisation_id)
                ->where('chains.id', '=', $_chain_id)
                ->whereNull('prefix_goliath_firewall.deleted_at')
                ->select(
                    'prefixes.id',
                    DB::raw("CONCAT(prefixes.address, '/', prefixes.mask) as prefix")
                )
                ->get()
                ->toArray();

                return response()->json($prefixes ? $prefixes : [], 200);
            }

            return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
        }
        catch(\Exception $ex) {}

        return response()->json([], 500);
    }

    public function put_chain(Request $_r, $_chain_id = null) {
        // Make the goto rule
        $goto_rule = Chain::leftJoin('chain_element_outcomes', 'chain_element_outcomes.chain_id', '=', 'chains.id')
            ->leftJoin('chain_element_actions', 'pass_action_id', '=', 'chain_element_actions.id')
            ->where(function ($q) {
                $q->orWhere('chains.organisation_id', '=', Auth::user()->organisation_id)
                    ->orWhere('chains.chain_type_id', '=', 1)
                    ->orWhere('chains.chain_type_id', '=', 4);
            })
            ->whereNull('chain_element_outcomes.element_id')
            ->whereNull('chain_element_outcomes.pass_chain_id')
            ->pluck('chains.id');

        // Validate this request in the first place.
        $valid_request = !Validator::make( array_merge([
            'chain_id' => $_chain_id
        ], $_r->all()), [

            'prefix_id'                 => [ Rule::in(Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id')) ],
            'chain_id'                  => [ 'nullable', Rule::in(Chain::where(function ($q) {
                $q->orWhere('chains.organisation_id', '=', Auth::user()->organisation_id)
                    ->orWhere('chains.chain_type_id', '=', 1)
                    ->orWhere('chains.chain_type_id', '=', 4);
            })->pluck('id'))
            ],
            
            'name'                      => ['required', 'string'],

            'default_action'            => ['required', Rule::in(['accept', 'drop', 'police', 'goto'])],
            'default_pps'               => ['required_if:default_action,police', 'numeric', 'min:0'],
            'default_bps'               => ['required_if:default_action,police', 'numeric', 'min:0'],
            'default_goto'              => ['required_if:default_action,goto', 'numeric', 'min:0', Rule::in($goto_rule)],

            'default_src_mask'       => ['nullable', 'numeric', 'min:0', 'max:32'],
            'default_dst_mask'       => ['nullable', 'numeric', 'min:0', 'max:32'],

            'default_x'                 => ['required', 'numeric'],
            'default_y'                 => ['required', 'numeric'],

            'elements.*.bpf'            => ['required', 'string'],
            'elements.*.action'         => ['required', Rule::in(['accept', 'drop', 'police', 'goto'])],
            'elements.*.pps'            => ['required_if:elements.*.action,police', 'numeric', 'min:0'],
            'elements.*.bps'            => ['required_if:elements.*.action,police', 'numeric', 'min:0'],

            'elements.*.src_mask'       => ['nullable', 'numeric', 'min:0', 'max:32'],
            'elements.*.dst_mask'       => ['nullable', 'numeric', 'min:0', 'max:32'],
            
            'elements.*.goto_chain_id'  => ['required_if:elements.*.action,goto', 'numeric', 'min:0', Rule::in($goto_rule)],
            'elements.*.x'              => ['required', 'numeric'],
            'elements.*.y'              => ['required', 'numeric']

        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }

        $outcome = DB::transaction(function () use($_r, $_chain_id) {
            try {
                $USER_ORGANISATION_ID = Auth::user()->organisation_id;
                $ACTION_LOOKUP = GoliathActionType::select('system_name', 'id')->get()->keyBy('system_name');

                // Get some values that'll be helpful...
                [$is_universal, $is_marketable_template, $is_marketable_specific] = (function() use($_chain_id) {

                    if($_chain_id) {
                        $chain = Chain::find($_chain_id);

                        return [
                            $chain->chain_type_id === 1,
                            $chain->chain_type_id === 4,
                            $chain->chain_type_id === 5
                        ];
                    }

                    return [0, 0, 0];
                })();

                // If a chain id is provided, erase old non chain values to populate them in - Only do this however IF the chain is not universal. If someone is editing a non-specific chain, treat it as new.
                if($_chain_id && !$is_universal && !$is_marketable_template) {

                    // Get all actions correspondent to the chain
                    $chain_action_ids = ChainElementOutcome::select('pass_action_id')->where('chain_id', '=', $_chain_id)->get()->pluck('pass_action_id')->toArray();

                    // Get corresponding matches
                    $chain_match_ids = ChainElement::select('match_id')->where('chain_id', '=', $_chain_id)->get()->pluck('match_id')->toArray();

                    // Delete outcomes, elements, actions, matches.
                    ChainElementOutcome::where('chain_id', '=', $_chain_id)->delete();
                    ChainElement::where('chain_id', '=', $_chain_id)->delete();
                    ChainElementAction::whereIn('id', $chain_action_ids)->delete();
                    ChainElementMatch::where('private', '!=', '1')->whereIn('id', $chain_match_ids)->delete();

                    // Put in matches, actions, elements, outcomes
                    foreach($_r->input('elements') as $i => $element) {
                        $new_match = new ChainElementMatch([
                            'author_id' => $USER_ORGANISATION_ID,
                            'match_type_id' => 1,
                            'bpf' => $element['bpf'],
                            'private' => 1
                        ]);

                        $new_match->save();

                        $new_action = new ChainElementAction([
                            'organisation_id' => $USER_ORGANISATION_ID,
                            'action_type_id' => $ACTION_LOOKUP[$element['action']]->id,
                            'pps_limit' => $element['pps'] ?? null,
                            'bitrate' => $element['bps'] ?? null,
                            'src_mask' => $element['src_mask'] ?? null
                        ]);

                        $new_action->save();

                        $new_element = new ChainElement([
                            'position' => $i + 1,
                            'chain_id' => $_chain_id,
                            'match_id' => $new_match->id,
                            'x' => $element['x'],
                            'y' => $element['y']
                        ]);

                        $new_element->save();

                        $new_outcome = new ChainElementOutcome([
                            'chain_id' => $_chain_id,
                            'element_id' => $new_element->id,
                            'pass_chain_id' => $element['goto_chain_id'] ?? null,
                            'pass_action_id' => $new_action->id,
                            'goto_next_element' => 1
                        ]);

                        $new_outcome->save();
                    }

                    $default_action = new ChainElementAction([
                        'organisation_id' => $USER_ORGANISATION_ID,
                        'action_type_id' => $ACTION_LOOKUP[$_r->input('default_action')]->id,
                        'pps_limit' => $_r->input('default_pps', null),
                        'bitrate' => $_r->input('default_bps', null),
                        'src_mask' => $_r->input('default_src_mask', null)
                    ]);
    
                    $default_action->save();

                    $default_outcome = new ChainElementOutcome([
                        'chain_id' => $_chain_id,
                        'element_id' => null,
                        'pass_chain_id' => $_r->input('default_goto', null),
                        'pass_action_id' => $default_action !== null ? $default_action->id : null,
                        'goto_next_element' => 0
                    ]);

                    $default_outcome->save();

                    // Point chain at new default
                    Chain::where('id', '=', $_chain_id)->update([
                        'organisation_id' => $USER_ORGANISATION_ID,
                        'chain_type_id' => ($is_marketable_specific || $is_marketable_template) ? 5 : ($_r->input('prefix_id', false) ? 2 : 3),
                        'name' => $_r->input('name', null),
                        'default_x' => $_r->input('default_x'),
                        'default_y' => $_r->input('default_y')
                    ]);
                }
                else {
                    // Create chain firstly.
                    $new_chain = new Chain([
                        'uuid' => (string) Str::orderedUuid(),
                        'organisation_id' => $USER_ORGANISATION_ID,
                        'chain_type_id' => ($is_marketable_specific || $is_marketable_template) ? 5 : ($_r->input('prefix_id', false) ? 2 : 3),
                        'name' => $_r->input('name', null),
                        'default_x' => $_r->input('default_x'),
                        'default_y' => $_r->input('default_y')
                    ]);

                    $new_chain->save();

                    $_chain_id = $new_chain->id;
                    
                    // Put in matches, actions, elements, outcomes
                    foreach($_r->input('elements') as $i => $element) {
                        $new_match = new ChainElementMatch([
                            'author_id' => $USER_ORGANISATION_ID,
                            'match_type_id' => 1,
                            'bpf' => $element['bpf'],
                            'private' => (isset($element['share']) && $element['share']) ? 1 : 0
                        ]);

                        $new_match->save();

                        $new_action = new ChainElementAction([
                            'organisation_id' => $USER_ORGANISATION_ID,
                            'action_type_id' => $ACTION_LOOKUP[$element['action']]->id,
                            'pps_limit' => $element['pps'] ?? null,
                            'bitrate' => $element['bps'] ?? null,
                            'src_mask' => $element['src_mask'] ?? null
                        ]);

                        $new_action->save();

                        $new_element = new ChainElement([
                            'position' => $i + 1,
                            'chain_id' => $new_chain->id,
                            'match_id' => $new_match->id,
                            'x' => $element['x'],
                            'y' => $element['y']
                        ]);

                        $new_element->save();

                        $new_outcome = new ChainElementOutcome([
                            'chain_id' => $_chain_id,
                            'element_id' => $new_element->id,
                            'pass_chain_id' => $element['goto_chain_id'] ?? null,
                            'pass_action_id' => $new_action->id,
                            'goto_next_element' => 1
                        ]);

                        $new_outcome->save();

                    }

                    // Create default action
                    $default_action = null;

                    $default_action = new ChainElementAction([
                        'organisation_id' => $USER_ORGANISATION_ID,
                        'action_type_id' => $ACTION_LOOKUP[$_r->input('default_action')]->id,
                        'pps_limit' => $_r->input('default_pps', null),
                        'bitrate' => $_r->input('default_bps', null),
                        'src_mask' => $_r->input('default_src_mask', null)
                    ]);

                    $default_action->save();

                    $default_outcome = new ChainElementOutcome([
                        'chain_id' => $_chain_id,
                        'element_id' => null,
                        'pass_chain_id' => $_r->input('default_goto', null),
                        'pass_action_id' => $default_action !== null ? $default_action->id : null,
                        'goto_next_element' => 0
                    ]);

                    $default_outcome->save();
                }

                if($_r->input('prefix_id', false)) {

                    // Get the existing prefix firewall association for this metro, region and subregion and if it doesn't exist, ADD ONE.
                    $prefix_association = PrefixGoliathFirewall::where([
                        ['prefix_id', '=', $_r->input('prefix_id')],
                        ['chain_id', '=', $_chain_id]
                    ])->get()->first() ?? PrefixGoliathFirewall::where([
                        ['prefix_id', '=', $_r->input('prefix_id')],
                        ['region_id', '=', null],
                        ['subregion_id', '=', null],
                        ['metro_id', '=', null]
                    ])->get()->first() ?? new PrefixGoliathFirewall([
                        'prefix_id' => $_r->input('prefix_id'),
                        'region_id' => null,
                        'subregion_id' => null,
                        'metro_id' => null
                    ]);

                    $prefix_association->chain_id = $_chain_id;
                    $prefix_association->region_id = null;
                    $prefix_association->subregion_id = null;
                    $prefix_association->metro_id = null;

                    $prefix_association->save();

                    $flags = PrefixGoliathFlag::firstOrNew([
                        'prefix_id'     => $_r->input('prefix_id'),
                        'region_id'     => null,
                        'subregion_id'  => null,
                        'metro_id'      => null
                    ]);
            
                    $flags->flag_heuristics         = 1;
                    $flags->flag_heuristics_level   = 3;
                    $flags->save();

                    GoliathBgp::emit_delta([$_r->input('prefix_id')]);
                    GoliathBgp::emit_acl();
                }

                return response()->json([
                    'chain_id' => $_chain_id
                ], 200);
            }
            catch(\Exception $ex) {
                Log::error($ex);
                Log::error("Failed to create chain...");
            }

            return response()->json([], 500);
        });

        return $outcome;
    }

    public function delete_chain(Request $_r, $_chain_id) {

        // This will be rather simple, simply delete the chain if it belongs to the logged in organisation. Should handle re-assignment later.

        $valid_request = !Validator::make( array_merge([
            'chain_id' => $_chain_id
        ], $_r->all()), [
            'chain_id' => [ Rule::in(Chain::where('organisation_id', Auth::user()->organisation_id)->whereNotIn('chain_type_id', [1, 4])->pluck('id')) ],
            'reassign_chain_id' => [ 'nullable', Rule::in(Chain::where(function ($q) {
                $q->orWhere('chains.organisation_id', '=', Auth::user()->organisation_id)
                    ->orWhereIn('chain_type_id', [1, 4, 5]);
            })->pluck('id')) ]
        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
        }

        $to_emit = [];

        if($_r->input('reassign_chain_id') == null) {
            $to_emit = PrefixGoliathFirewall::where('chain_id', '=', $_chain_id)->get()->pluck('prefix_id');
        }

        PrefixGoliathFirewall::where('chain_id', '=', $_chain_id)->update([ 'chain_id' => $_r->input('reassign_chain_id') ]);

        Chain::where('id', '=', $_chain_id)->delete();

        // If we don't reassign, we need to recalculate and send deltas for a given prefix.
        if($_r->input('reassign_chain_id') == null) {
            GoliathBgp::emit_delta($to_emit);
            GoliathBgp::emit_acl();
        }

        return response()->json(['detail' => __('api.200_GENERIC')], 200);
    }

    public function delete_prefix_chain(Request $_r, $_prefix_id) {

        // Validate this request in the first place.
        $valid_request = !Validator::make( array_merge([
            'prefix_id' => $_prefix_id
        ], $_r->all()), [
            'prefix_id' => [ Rule::in(Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id')) ],
            'chain_id' => [ Rule::in(PrefixGoliathFirewall::where('prefix_id', '=', $_prefix_id)->pluck('chain_id')) ]
        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
        }

        PrefixGoliathFirewall::where([['prefix_id', '=', $_prefix_id],['chain_id', '=', $_r->input('chain_id')]])->delete();

        GoliathBgp::emit_delta([$_prefix_id]);
        GoliathBgp::emit_acl();

        return response()->json(['detail' => __('api.200_GENERIC')], 200);
    }

    public function put_prefix_chain(Request $_r, $_prefix_id) {
        
        // Validate this request in the first place.
        $valid_request = !Validator::make( array_merge([
            'prefix_id' => $_prefix_id
        ], $_r->all()), [

            'prefix_id'                 => [ 'required', Rule::in(Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id')) ],
            'chain_id'                  => [ 'required', Rule::in(Chain::where(function ($q) {
                    $q->orWhere('chains.organisation_id', '=', Auth::user()->organisation_id)
                        ->orWhereIn('chain_type_id', [1, 4, 5]);
                })->pluck('id')) ],
            
            'make_duplicate'            => ['required', 'boolean'],

        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }
        
        $_chain_id = $_r->input('chain_id');
        
        try {
            $USER_ORGANISATION_ID = Auth::user()->organisation_id;
            $ACTION_LOOKUP = GoliathActionType::select('system_name', 'id')->get()->keyBy('system_name');

            if($_r->input('make_duplicate', false)) {
                // Get the chain ...
                $old_chain = json_decode(self::get_chain($_r, $_chain_id)->content(), true);

                // Remake the chain ...
                $new_chain = new Chain([
                    'uuid' => (string) Str::orderedUuid(),
                    'organisation_id' => $USER_ORGANISATION_ID,
                    'chain_type_id' => 2,
                    'name' => $old_chain['name'] . ' (DUPLICATE)',
                    'default_x' => $old_chain['default_x'],
                    'default_y' => $old_chain['default_y'],
                ]);

                $new_chain->save();

                // Get old default outcome for later cloning...
                $old_default_outcome = ChainElementOutcome::where('chain_id', '=', $_chain_id)->whereNull('element_id')->get()->first();

                $_chain_id = $new_chain->id;
                
                // Put in matches, actions, elements, outcomes
                foreach($old_chain['elements'] as $i => $element) {
                    $new_match = new ChainElementMatch([
                        'author_id' => $USER_ORGANISATION_ID,
                        'match_type_id' => 1,
                        'bpf' => $element['bpf'],
                        'private' => (isset($element['share']) && $element['share']) ? 1 : 0
                    ]);

                    $new_match->save();

                    $new_action = new ChainElementAction([
                        'organisation_id' => $USER_ORGANISATION_ID,
                        'action_type_id' => $ACTION_LOOKUP[$element['action']]->id,
                        'pps_limit' => $element['pps'] ?? null,
                        'bitrate' => $element['bps'] ?? null,
                        'src_mask' => $element['src_mask'] ?? null
                    ]);

                    $new_action->save();

                    $new_element = new ChainElement([
                        'position' => $i + 1,
                        'chain_id' => $new_chain->id,
                        'match_id' => $new_match->id,
                        'x'         => $element['x'],
                        'y'         => $element['y']
                    ]);

                    $new_element->save();

                    $new_outcome = new ChainElementOutcome([
                        'chain_id' => $_chain_id,
                        'element_id' => $new_element->id,
                        'pass_chain_id' => $element['goto_chain_id'] ?? null,
                        'pass_action_id' => $new_action->id,
                        'goto_next_element' => 1
                    ]);

                    $new_outcome->save();
                }

                // Create default action
                $default_action = null;

                if($_r->input('default_action') !== 'goto') {
                    $default_action = new ChainElementAction([
                        'organisation_id' => $USER_ORGANISATION_ID,
                        'action_type_id' => $ACTION_LOOKUP[$old_chain['default_action']]->id,
                        'pps_limit' => isset($old_chain['default_pps']) ? $old_chain['default_pps'] : null,
                        'bitrate' => isset($old_chain['default_bps']) ? $old_chain['default_bps'] : null,
                        'src_mask' => isset($old_chain['default_src_mask']) ? $old_chain['default_src_mask'] : null
                    ]);

                    $default_action->save();
                }

                $default_outcome = new ChainElementOutcome([
                    'chain_id' => $_chain_id,
                    'element_id' => null,
                    'pass_chain_id' => $old_default_outcome->pass_chain_id,
                    'pass_action_id' => $old_default_outcome->pass_action_id,
                    'goto_next_element' => 0
                ]);

                $default_outcome->save();

                $new_chain->save();
            }

            // Associate chain with prefix
            $prefix_association = PrefixGoliathFirewall::where([
                ['region_id', '=', null],
                ['subregion_id', '=', null],
                ['metro_id', '=', null],
                ['prefix_id', '=', $_prefix_id]
            ])->get()->first() ?? new PrefixGoliathFirewall([
                'region_id' => null,
                'subregion_id' => null,
                'metro_id' => null,
                'prefix_id' => $_prefix_id
            ]);

            $prefix_association->chain_id = $_chain_id;

            $prefix_association->save();

            $flags = PrefixGoliathFlag::firstOrNew([
                'prefix_id'     => $_prefix_id,
                'region_id'     => null,
                'subregion_id'  => null,
                'metro_id'      => null
            ]);
    
            $flags->flag_heuristics         = 1;
            $flags->flag_heuristics_level   = 3;
            $flags->save();

            GoliathBgp::emit_delta([$_prefix_id]);
            GoliathBgp::emit_acl();

            return response()->json(['detail' => __('api.200_GENERIC')], 200);
        }
        catch(\Exception $ex) {
            \Log::debug($ex);
        }
    }

    public function get_would_replace(Request $_r, $_prefix_id) {

        // Validate this request in the first place.
        $valid_request = !Validator::make( array_merge([
            'prefix_id' => $_prefix_id
        ], $_r->all()), [
            'prefix_id'                 => [ 'required', Rule::in(Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id')) ],
            'region_id'                 => ['exists:goliath_regions,id'],
            'subregion_id'              => ['exists:goliath_subregions,id', 'exclude_without:region_id'],
            'metro_id'                  => ['exists:goliath_metro,id', 'exclude_without:subregion_id'],
        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }

        $would_replace = PrefixGoliathFirewall::leftJoin('chains', 'chains.id', '=', 'chain_id')
            ->where([
                ['region_id', '=', $_r->input('region_id', null)],
                ['subregion_id', '=', $_r->input('subregion_id', null)],
                ['metro_id', '=', $_r->input('metro_id', null)],
                ['prefix_id', '=', $_prefix_id]
            ])->get()->first();

        return response()->json(['detail' => [
            'replace' => $would_replace ? $would_replace->name : false
        ]], 200);
    }

    public function get_flags(Request $_r, $_prefix_id) {
        // Validate this request in the first place.
        $valid_request = !Validator::make( array_merge([
            'prefix_id' => $_prefix_id,
        ], $_r->all()), [
            'prefix_id'     => [ 'required', Rule::in(Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id')) ],
            'region_id'     => ['exists:goliath_regions,id'],
            'subregion_id'  => ['exists:goliath_subregions,id', 'exclude_without:region_id'],
            'metro_id'      => ['exists:goliath_metro,id', 'exclude_without:subregion_id']
        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }

        $flags = PrefixGoliathFlag::where([
                ['prefix_id', '=', $_prefix_id],
                ['region_id', '=', $_r->input('region_id')],
                ['subregion_id', '=', $_r->input('subregion_id')],
                ['metro_id', '=', $_r->input('metro_id')]
            ])->get()->first();

        $inline = PrefixGoliathInline::where('prefix_id', '=', $_prefix_id)->get()->first();

        $parent_inline = PrefixGoliathInline::leftJoin('supernets', 'supernet_id', '=', 'prefix_goliath_inline.prefix_id')
            ->leftJoin('prefix_goliath_flags', 'prefix_goliath_flags.prefix_id', '=', 'prefix_goliath_inline.prefix_id')
            ->leftJoin('prefixes', 'supernet_id', '=', 'prefixes.id')
            ->where([['subnet_id', '=', $_prefix_id], ['flag_synproxy', '=', 1]])
            ->orWhere([['subnet_id', '=', $_prefix_id], ['forced', '=', 1]])
            ->get()
            ->count();

        $parent_level_2 = PrefixGoliathFirewall::leftJoin('supernets', 'supernet_id', '=', 'prefix_goliath_firewall.prefix_id')
            ->leftJoin('prefixes', 'supernet_id', '=', 'prefixes.id')
            ->whereNotNull('prefix_goliath_firewall.chain_id')
            ->where('subnet_id', '=', $_prefix_id)
            ->get()
            ->count();

        $parent_mercury = PrefixCommunity::leftJoin('supernets', 'supernet_id', '=', 'prefix_community.prefix_id')
            ->leftJoin('prefixes', 'supernet_id', '=', 'prefixes.id')
            ->where([['subnet_id', '=', $_prefix_id], ['community', '=', '7578:90']])
            ->get()
            ->count();

        return response()->json([
            'parent_inline' => $parent_inline === 0 ? false : true,
            'parent_level_2' => $parent_level_2 === 0 ? false : true,
            'parent_mercury' => $parent_mercury === 0 ? false : true,
            'level_2' => PrefixGoliathFirewall::where('prefix_id', '=', $_prefix_id)->whereNotNull('chain_id')->get()->count() === 0 ? false : true,
            'mercury' => PrefixCommunity::where([['prefix_id', '=', $_prefix_id], ['community', '=', '7578:90']])->get()->count() === 0 ? false : true,
            'inline' => boolval($inline->forced ?? false),
            'synproxy' => boolval($flags->flag_synproxy ?? false),
            'fivem' => boolval($flags->flag_fivem ?? false),
            'heuristics' => boolval($flags->flag_heuristics ?? false),
            'heuristics_level' => ($flags->flag_heuristics ?? false) ? $flags->flag_heuristics_level ?? 3 : null
        ], 200);
    }

    public function put_flags(Request $_r, $_prefix_id) {

        $valid_request = !Validator::make( array_merge([
            'prefix_id' => $_prefix_id,
        ], $_r->all()), [
            'prefix_id'         => [ 'required', Rule::in(Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id')) ],
            'inline'            => ['required', 'boolean'],
            'synproxy'          => ['required', 'boolean'],
            'fivem'             => ['required', 'boolean'],
            'heuristics_level'  => ['required', Rule::in([2, 3])],
            'region_id'         => ['exists:goliath_regions,id'],
            'subregion_id'      => ['exists:goliath_subregions,id', 'exclude_without:region_id'],
            'metro_id'          => ['exists:goliath_metro,id', 'exclude_without:subregion_id']
        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
        }

        // Get communities, specifically looking for Mercury
        $is_parent_mercury = DB::table('prefixes as p1')
            ->leftJoin('prefix_community', 'prefix_id', '=', 'p1.id')
            ->leftJoin('supernets', 'supernet_id', '=', 'p1.id')
            ->whereNull('p1.deleted_at')
            ->whereNull('prefix_community.deleted_at')
            ->where('supernets.subnet_id', '=', $_prefix_id)
            ->where('community', '=', '7578:90')
            ->where('is_large', '=', '0')
            ->select('subnet_id', DB::raw('string_agg(community::text, \' \') as communities'))
            ->groupBy('supernets.subnet_id')
            ->get()
            ->count();

        $is_mercury = PrefixCommunity::where([['prefix_id', '=', $_prefix_id], ['community', '=', '7578:90']])->get()->count();

        $flags = PrefixGoliathFlag::firstOrNew([
            'prefix_id' => $_prefix_id,
            'region_id' => $_r->input('region_id'),
            'subregion_id' => $_r->input('subregion_id'),
            'metro_id' => $_r->input('metro_id')
        ]);

        $flags->flag_fivem = $_r->input('fivem');

        if($flags->flag_fivem) {
            $flags->flag_synproxy = 1;
            $flags->flag_heuristics = 1;
            $flags->flag_heuristics_level = 3;
        }
        else {
            $flags->flag_synproxy = $_r->input('synproxy');
            $flags->flag_heuristics = 1;
            $flags->flag_heuristics_level = PrefixGoliathFirewall::where('prefix_id', '=', $_prefix_id)->whereNotNull('chain_id')->get()->count() === 0 ? $_r->input('heuristics_level') : 3;
        }

        if($is_mercury || $is_parent_mercury) {
            $flags->flag_heuristics_level = 3;
        }

        $flags->save();

        $inline = PrefixGoliathInline::firstOrNew([
            'prefix_id' => $_prefix_id
        ]);

        if($is_mercury || $is_parent_mercury) {
            $inline->forced = 1;
        }
        else {
            $inline->forced = $_r->input('inline', 0) ? 1 : 0;
        }

        $inline->save();

        // Autodiscovery handling
        Prefixes::where([['id', '=', $_prefix_id], ['mask', '=', 32], ['setup_required', '=', 2]])->update([
            'setup_required' => null
        ]);

        $__prefix = Prefixes::find($_prefix_id);
        $__heuristics = $flags->flag_heuristics ? "H$flags->flag_heuristics_level" : 'H-Off';
        $__synproxy = boolval($flags->flag_synproxy) ? 'On' : 'Off';
        $__inline = boolval($inline->forced) ? 'On' : 'Off';
        $__fivem = boolval($flags->flag_fivem) ? 'On' : 'Off';

        if($flags->flag_fivem) {
            $__heuristics = 'H3';
            $__synproxy = 'On';
            $__inline = 'On';
    
        }

        \Illuminate\Support\Facades\Notification::route('slack', env('GOLIATH_FLAG_HOOK'))->notify(new \App\Notifications\SlackNotification(
            "MITIGATION ATTRIBUTES CHANGED FOR PREFIX $__prefix->address/$__prefix->mask\nHeuristics: $__heuristics\nSynproxy: $__synproxy\nFiveM: $__fivem\nInline: $__inline\nResponsible: " . Auth::user()->name
        ));

        GoliathBgp::emit_delta([$_prefix_id]);
        GoliathBgp::emit_acl();

        return response()->json(['detail' => __('api.200_GENERIC')], 200);
    }

    public function post_subnet(Request $_r, $_network_id) {

        return NetworkController::post_prefix($_r, $_network_id, ['input' => [
            'synproxy' => $_r->input('synproxy'),
            'fivem' => $_r->input('fivem'),
            'heuristics' => $_r->input('heuristics'),
            'inline' => $_r->input('inline'),
            'heuristics_level' => $_r->input('heuristics_level')
        ], 'rule' => [
            'synproxy' => ['required', 'boolean'],
            'fivem' => ['required', 'boolean'],
            'inline' => ['required', 'boolean'],
            'heuristics_level'  => ['required', Rule::in([2, 3])],
        ] ] , function($new, $parent, $request) use($_network_id) {

            $fivem = $request->input('fivem', false);

            PrefixGoliathFlag::create([
                'prefix_id' => $new->id,
                'flag_synproxy' => $fivem ? 1 : $request->input('synproxy'),
                'flag_fivem' => $request->input('fivem'),
                'flag_heuristics' => 1,
                'flag_heuristics_level' => $fivem ? 3 : $request->input('heuristics_level', 3)
            ]);

            PrefixGoliathInline::create([
                'prefix_id' => $new->id,
                'forced' => $fivem ? 1 : ($request->input('inline', 0) ? 1 : 0)
            ]);

            $__heuristics = $request->input('heuristics') ? "H" . $request->input('heuristics_level', 3) : 'H-Off';
            $__synproxy = boolval($request->input('synproxy')) ? 'On' : 'Off';
            $__fivem = boolval($request->input('fivem')) ? 'On' : 'Off';
            $__inline = boolval($request->input('inline')) ? 'On' : 'Off';

            if($fivem) {
                $__heuristics = 'H3';
                $__synproxy = 'On';
                $__inline = 'On';
            }

            GoliathAttacks::calculate($new->id, "$new->address/$new->mask");
    
            \Illuminate\Support\Facades\Notification::route('slack', env('GOLIATH_FLAG_HOOK'))->notify(new \App\Notifications\SlackNotification(
                "MITIGATION ATTRIBUTES SET FOR NEWLY CREATED PREFIX $new->address/$new->mask\nHeuristics: $__heuristics\nSynproxy: $__synproxy\nFiveM: $__fivem\nInline: $__inline\nResponsible: " . Auth::user()->name
            ));

            GoliathBgp::emit_delta([$_network_id]);
            GoliathBgp::emit_acl();
        }, false);

    }

    public function get_captures(Request $_r) {
        $draw = $_r->input('draw');
        $start = $_r->input('start');
        $length = $_r->input('length');
        $search_term = $_r->input('search_string');

        // Build our query function
        $query_builder = function() {
            return GoliathApplianceCapture::leftJoin('goliath_appliances', 'goliath_appliances.id', '=', 'goliath_appliance_captures.appliance_id')
                ->leftJoin('goliath_subregions', 'goliath_subregions.id', '=', 'goliath_appliances.subregion_id')
                ->leftJoin('goliath_metro', 'goliath_metro.id', '=', 'goliath_appliances.metro_id')
                ->select(
                    'goliath_appliance_captures.created_at',
                    'goliath_appliance_captures.location',
                    'bpf',
                    'prescrubbed',
                    DB::raw("CONCAT(goliath_subregions.telemetry_identifier, '-', goliath_metro.telemetry_identifier, '-gol', ' - ', goliath_appliances.name) as name"),
                    'goliath_appliance_captures.id',
                )
                ->where('goliath_appliance_captures.organisation_id', '=', Auth::user()->organisation_id);
        };

        // Build our search function
        $query_search = function($prefix_query, $global, $filters) {

            if($global) {
                $prefix_query = $prefix_query->where(function($query) use ($global){
                    $query->orWhere('bpf', 'LIKE', "%$global%")
                        ->orWhere('created_at', 'LIKE', "%$global%")
                        ->orWhere("CONCAT(goliath_subregions.telemetry_identifier, '-', goliath_metro.telemetry_identifier, '-gol', ' - ', goliath_appliances.name)", 'LIKE', "%$global%");
                });
            }

            if($filters) {
                foreach($filters as $filter) {
                    $col = '';
                    switch($filter['id']) {
                        case 'created_at': $col = 'goliath_appliances.created_at'; break;
                        case 'bpf': $col = 'bpf'; break;
                        case 'name': $col = 'goliath_appliances.name'; break;
                    }

                    if($col != '') {
                        $prefix_query = $prefix_query->where($col, 'LIKE', '%' . $filter['value'] . '%');
                    }
                }
            }

            return $prefix_query;
        };

        // Build our sorting function
        $query_sort = function($prefix_query, $column, $direction) {
            switch($column) {
                default:
                    $prefix_query = $prefix_query->orderBy('goliath_appliances.created_at', $direction ?? 'desc');
            }

            return $prefix_query;
        };

        // Create valid sorting keys
        $sorting_keys = [];

        // Build our transformation function
        $query_transform = function($data) {
            $data->transform(function($row) {
                return $row;
            });

            return $data;
        };

        return NetworkController::pagination_table_request($_r, $query_builder, $query_search, $query_sort, $query_transform, $sorting_keys);
        
        return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
    }

    public function get_appliance_options(Request $_r) {
        return response()->json([
            'appliances' => GoliathAppliance::leftJoin('goliath_subregions', 'goliath_subregions.id', '=', 'goliath_appliances.subregion_id')
                ->leftJoin('goliath_metro', 'goliath_metro.id', '=', 'goliath_appliances.metro_id')
                ->select(
                    'goliath_appliances.id',
                    'goliath_appliances.name',
                    DB::raw("CONCAT(goliath_subregions.telemetry_identifier, '-', goliath_metro.telemetry_identifier, '-gol') as identifier")
                )
                ->get()
                ->toArray()
        ], 200);
    }

    public function post_captures(Request $_r, $_appliance_id) {

        // We only need to retrieve this once...
        $all_relevant_destinations = Prefixes::select(DB::raw("CONCAT(address, '/', mask) as prefix"))->whereIn('id', Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id'))->get()->pluck('prefix')->toArray();

        // Compress destinations for later usage...
        $crusher_process = new Process(['python3', '../app/Bin/SubnetCrusher.py']);
        $crusher_process->setInput(implode(' ', $all_relevant_destinations));
        $crusher_process->run();

        $crushed_prefixes = [];

        if($crusher_process->isSuccessful()) {
            $crushed_prefixes = explode("\n", rtrim($crusher_process->getOutput()));
        }

        if($crushed_prefixes !== []) {
            $ip_regex = "/^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\.)){3}(25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\/((3[0-2])|([1-2][0-9])|([8-9])))?|(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))(\/((1([0-1][0-9]|2[0-8]))|([1-9][0-9])|([8-9])))?$/i";

            $valid_request = !Validator::make( array_merge([
                'appliance_id' => $_appliance_id
            ], $_r->all()), [
                'appliance_id' => [ Rule::in(GoliathAppliance::all()->pluck('id')) ],
                'prescrubbed' => ['boolean', 'nullable'],
                'bpf' => ['string', 'nullable', 'not_regex:/(dst|src)/i'],
                'source' => ['nullable'],
                'source.*' => ["regex:$ip_regex"],
                'destination' => ['nullable'],
                'destination.*' => [Rule::in($all_relevant_destinations)]
            ])->fails();
    
            if($valid_request) {
                // We need to properly validate whether the IP provided exists inside ALL subnets...
                $valid_subnet = true;
    
                $bpf = $_r->input('bpf', null);
    
                // Construct source string
                $source_bpf = '';
    
                if($_r->input('source', false)) {
                    $new_sources = [];
                    
                    foreach($_r->input('source') as $src) {
                        $new_sources[] = str_contains($src, '/') === true ? "src net $src" : "src host $src";
                    }

                    $source_bpf = implode(' or ', $new_sources);
                }
    
                // Construct dest string
                $dest_bpf = '';
    
                if($_r->input('destination', false)) {
                    $dest_bpf = 'dst net ' . implode(' or dst net ', $_r->input('destination'));
                }
                else {
                    $dest_bpf = 'dst net ' . implode(' or dst net ', $crushed_prefixes);
                }
    
                if($bpf !== null) {
                    $bpf = $bpf . ($source_bpf !== '' ? " and ($source_bpf) and ($dest_bpf)" : " and ($dest_bpf)");
                }
                else {
                    $bpf = $source_bpf === '' ? $dest_bpf : "($source_bpf) and ($dest_bpf)";
                }
    
                if(!$valid_request || !$valid_subnet) {
                    return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
                }
    
                if(GoliathApplianceCapture::create([
                    'appliance_id' => $_appliance_id,
                    'prescrubbed' => $_r->input('prescrubbed', null),
                    'bpf' => $bpf,
                    'organisation_id' => Auth::user()->organisation_id
                ])->save()) {
                    return response()->json(['detail' => __('api.200_GENERIC')], 200);
                }
            }
        }

        return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
    }

    public function download_capture(Request $_r, $_capture_id) {
        $capture = GoliathApplianceCapture::where('organisation_id', '=', Auth::user()->organisation_id)->get();

        if($capture) {
            return Storage::download($capture[0]->location);
        }

        return response()->json(['detail' => __('api.409_NOT_PERMITTED')], 409);
    }

    public function get_defaults(Request $_r, $_asn_id) {
        $valid_request = !Validator::make( ['asn_id' => $_asn_id], ['asn_id' => ['required', Rule::in(Asns::where('organisation_id', '=', Auth::user()->organisation_id)->pluck('id'))]] )->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }
        
        $defaults = GoliathAsnAttributes::where('asn_id', '=', $_asn_id)->get()->first();
        
        return response()->json($defaults === null ? [
            'synproxy' => false,
            'heuristics' => 3,
            'inline' => false
        ] : [
            'synproxy' => $defaults->default_synproxy,
            'heuristics' => $defaults->default_heuristics,
            'inline' => $defaults->default_inline
        ], 200);
    }

    public function put_defaults(Request $_r, $_asn_id) {

        // Validate this request in the first place.
        $valid_request = !Validator::make( array_merge(['asn_id' => $_asn_id], $_r->all()), [
            'asn_id' => ['required', Rule::in(Asns::where('organisation_id', '=', Auth::user()->organisation_id)->pluck('id'))],
            'synproxy' => ['required', 'boolean'],
            'heuristics' => ['required', Rule::in([2, 3])],
            'inline' => ['required', 'boolean'],
            'override' => ['required', 'boolean']
        ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }

        $defaults =  GoliathAsnAttributes::firstOrNew(['asn_id' => $_asn_id]);

        $defaults->default_synproxy = $_r->input('synproxy') ? 1 : 0;
        $defaults->default_chain = null;
        $defaults->default_inline = $_r->input('inline') ? 1 : 0;
        $defaults->default_heuristics = $_r->input('heuristics');

        if($_r->input('override', false)) {
            $prefix_ids = Prefixes::select('id')->where('asn_id', '=', $_asn_id)->get()->pluck('id');

            // Find all of the level 2 prefixes - These must be handled differently.
            $level_2_prefixes = PrefixGoliathFirewall::leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')->where('asn_id', '=', $_asn_id)->whereNotNull('chain_id')->get()->pluck('prefix_id');
            $mercury_prefixes = PrefixCommunity::leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')->where([['asn_id', '=', $_asn_id], ['community', '=', '7578:90']])->get()->pluck('prefix_id');

            // Handle setting default for level 1 prefixes
            PrefixGoliathFlag::whereIn('prefix_id', $prefix_ids)
                ->whereNotIn('prefix_id', $level_2_prefixes->merge($mercury_prefixes))
                ->update([
                    'flag_synproxy' => $_r->input('synproxy'),
                    'flag_heuristics' => 1,
                    'flag_heuristics_level' => $_r->input('heuristics')
                ]);

            // Handle setting default for level 2 prefixes
            PrefixGoliathFlag::whereIn('prefix_id', $prefix_ids)
                ->whereIn('prefix_id', $level_2_prefixes->merge($mercury_prefixes))
                ->update([
                    'flag_synproxy' => $_r->input('synproxy')
                ]);

            PrefixGoliathInline::whereIn('prefix_id', $prefix_ids)
                ->whereNotIn('prefix_id', $mercury_prefixes)
                ->update([
                    'forced' => $_r->input('inline') ? 1 : 0
                ]);
            
            GoliathBgp::emit_delta($prefix_ids);
            GoliathBgp::emit_acl();
        }

        $__asn = Asns::find($_asn_id)->asn;
        $__heuristics = $_r->input('heuristics') ? "H" . $_r->input('heuristics') : 'H-Off';
        $__synproxy = boolval($_r->input('synproxy')) ? 'On' : 'Off';
        $__inline = boolval($_r->input('inline')) ? 'On' : 'Off';
        $__override = boolval($_r->input('override', false)) ? 'Yes' : 'No';

        \Illuminate\Support\Facades\Notification::route('slack', env('GOLIATH_FLAG_HOOK'))->notify(new \App\Notifications\SlackNotification(
            "MITIGATION DEFAULT SET FOR AS$__asn\nHeuristics: $__heuristics\nSynproxy: $__synproxy\nInline: $__inline\nOverride: $__override\nResponsible: " . Auth::user()->name
        ));

        if($defaults->save()) {
            return response()->json(['detail' => __('api.200_GENERIC')], 200);
        }

        return response()->json([], 500);
    }

    public function get_recent_changes(Request $_r) {

        $prefixes_relevant = Prefixes::getSubtenancyPrefixes(Auth::user()->organisation_id);

        // Build our query function
        $query_builder = function() use($prefixes_relevant) {
            // Build up our where any
            $any_payload_value = [];
            $any_route_value = [];
            
            foreach($prefixes_relevant as $x) {
                // Handle flag
                $any_route_value[] = "$x->id\/flag";

                // Handle subnet
                if($x->created_source === 'user') {
                    $any_payload_value[] = str_replace('.', '\.', $x->address) . "\/$x->mask";
                }
            }

            $query = SecurityEvent::select('security_events.id','route', 'payload', 'users.name as user_name', 'organisations.name as organisation_name', 'security_events.created_at as time')
                ->leftJoin('users', 'security_events.user_id', '=', 'users.id')
                ->leftJoin('organisations', 'users.organisation_id', '=', 'organisations.id')
                ->where('security_events.created_at', '>', Carbon::now()->subHours(3360))
                ->where('route', 'LIKE', '%/goliath/%')
                ->where(function($x) {
                    $x->where('route', 'LIKE', '%flag%')->orWhere('route', 'LIKE', '%subnet%')->orWhere('route', 'LIKE', '%default%');
                })
                ->where(function($x) use($any_payload_value, $any_route_value) { // Where organisationally relevant OR where the prefix is relevant to that organisation
                    if(sizeof($any_payload_value) || sizeof($any_route_value)) {
                        $x->where('organisation_id', '=', Auth::user()->organisation_id)
                        ->orWhere(function($y) use($any_payload_value, $any_route_value) {
                            if(sizeof($any_payload_value) && sizeof($any_route_value)) {
                                $y->where('route', 'SIMILAR TO', '%(' . implode('|', $any_route_value) . ')%')->orWhere(DB::raw('payload::text'), 'SIMILAR TO', '%(' . implode('|', $any_payload_value) . ')%');
                            }
                            elseif(sizeof($any_payload_value)) {
                                $y->where(DB::raw('payload::text'), 'SIMILAR TO', $any_payload_value);
                            }
                            elseif(sizeof($any_route_value)) {
                                $y->where('route', 'SIMILAR TO', '%(' . implode('|', $any_route_value) . ')%');
                            }
                        });
                    }
                    else {
                        $x->where('organisation_id', '=', Auth::user()->organisation_id);
                    }

                });

            return $query;
        };

        // Build our search function
        $query_search = function($prefix_query, $global, $filters) {
            return $prefix_query;
        };

        // Build our sorting function
        $query_sort = function($prefix_query, $column, $direction) {
            return $prefix_query->orderByDesc('security_events.created_at');
        };

        // Create valid sorting keys
        $sorting_keys = ['network', 'name', 'asn'];

        // Build our transformation function
        $query_transform = function($data) use($prefixes_relevant) {

            // Split things up and determine appropriate IDs for lookups...
            $prefixes_to_retrieve = [];

            foreach($data as $key => $datum) {
                $route = $datum->route;

                if(str_contains($route, 'flag')) {
                    $prefix_id = str_replace('/flag', '', explode('prefix/', $route)[1]);
                    $prefixes_to_retrieve[] = $prefix_id;

                    $data[$key]['prefix_id'] = $prefix_id;
                    $data[$key]['event_type'] = 'flag';
                }
                else if(str_contains($route, 'default')) {
                    $data[$key]['asn_id'] = explode('default/', $route)[1];
                    $data[$key]['event_type'] = 'default';
                }
                else if(str_contains($route, 'subnet')) {
                    $data[$key]['event_type'] = 'subnet';
                }
            }

            $asn_lookup = Asns::select('asn', 'id')->where('organisation_id', '=', Auth::user()->organisation_id)->get()->keyBy('id');
            $prefix_lookup = Prefixes::select('id', DB::raw("CONCAT(address, '/', mask) as network"))                
                ->where(function($x) use($prefixes_relevant) {
                    $x->where('organisation_id', '=', Auth::user()->organisation_id)
                        ->orWhereIn('id', $prefixes_relevant->pluck('id'));
                })
                ->whereIn('id', $prefixes_to_retrieve)->get()->keyBy('id');

            $data->transform(function($row) use($asn_lookup, $prefix_lookup) {

                $payload = json_decode($row->payload, true);

                $event = [
                    'heuristics' => isset($payload['heuristics']) && $payload['heuristics'] ? (isset($payload['heuristics_level']) ? $payload['heuristics_level'] : (is_bool($payload['heuristics']) ? 2 : $payload['heuristics'])) : 0,
                    'synproxy' => isset($payload['synproxy']) && $payload['synproxy'],
                    'inline' => isset($payload['inline']) && $payload['inline'],
                    'fivem' => isset($payload['fivem']) && $payload['fivem']
                ];

                switch($row['event_type']) {
                    case 'flag':
                        if(isset($prefix_lookup[$row['prefix_id']])) {
                            $event['type'] = 'flag';
                            $event['prefix'] = $prefix_lookup[$row['prefix_id']]->network;
                        }
                        else {
                            $row['c'] = true;
                        }
                        break;
                    case 'default':
                        if(isset($asn_lookup[$row['asn_id']])) {
                            $event['type'] = 'default';
                            $event['asn'] = $asn_lookup[$row['asn_id']]->asn;
                        }
                        else {
                            $row['c'] = true;
                        }
                        break;
                    case 'subnet':
                        $event['type'] = 'subnet';
                        $event['prefix'] = $payload['cidr'];
                        break;
                }

                $row['event'] = $event;

                unset($row['prefix_id'], $row['asn_id'], $row['event_type'], $row['route'], $row['payload']);

                return $row->toArray();
            });

            return array_values($data->filter(function($x) {
                return !isset($x['c']);
            })->toArray());
        };

        return NetworkController::pagination_table_request($_r, $query_builder, $query_search, $query_sort, $query_transform, $sorting_keys);
    }

    public function get_attack_summary(Request $_r) {
        // Get count of ongoing attacks
        $ongoing_count = GoliathAttack::where('goliath_attacks.updated_at', '>', Carbon::now()->subMinutes(2))
            ->where('organisation_id', '=', Auth::user()->organisation_id)
            ->leftJoin('goliath_attack_prefixes', 'attack_id', '=', 'goliath_attacks.id')
            ->leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')
            ->count();

        // Get count blocked last 24 hours
        $last_day_blocked = GoliathAttack::where('goliath_attacks.updated_at', '>', Carbon::now()->subHours(24))
            ->where('organisation_id', '=', Auth::user()->organisation_id)
            ->leftJoin('goliath_attack_prefixes', 'attack_id', '=', 'goliath_attacks.id')
            ->leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')
            ->count();

        // Get count blocked last week
        $last_week_blocked = GoliathAttack::where('goliath_attacks.updated_at', '>', Carbon::now()->subHours(168))
            ->where('organisation_id', '=', Auth::user()->organisation_id)
            ->leftJoin('goliath_attack_prefixes', 'attack_id', '=', 'goliath_attacks.id')
            ->leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')
            ->count();

        // Get global attacks
        $global_count = GoliathAttack::where('goliath_attacks.updated_at', '>', Carbon::now()->subMinutes(2))->count();

        return response()->json([
            'global' => $global_count,
            'ongoing' => $ongoing_count,
            'blocked_daily' => $last_day_blocked,
            'blocked_weekly' => $last_week_blocked
        ], 200);
    }
    
    public function get_attack_logs(Request $_r, $_prefix_id = null) {
        $flag_status = $_r->input('status', false);

        // Build our query function
        $query_builder = function() use($_prefix_id) {
        
            $q = GoliathAttack::leftJoin('goliath_metro', 'goliath_attacks.metro_id', '=', 'goliath_metro.id')
                ->leftJoin('goliath_subregions', 'goliath_attacks.subregion_id', '=', 'goliath_subregions.id')
                ->leftJoin('goliath_regions', 'goliath_attacks.region_id', '=', 'goliath_regions.id')
                ->leftJoin('goliath_attack_prefixes', 'attack_id', '=', 'goliath_attacks.id')
                ->leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')
                ->select(
                    'started_at as start',
                    'last_at as end',
                    'type',
                    'description', 
                    'destination',
                    'goliath_metro.telemetry_identifier as datacentre',
                    'goliath_subregions.telemetry_identifier as subregion',
                    'goliath_regions.telemetry_identifier as region'
                )
                ->whereIn('prefixes.id', Prefixes::getOrganisationPrefixes(Auth::user()->organisation_id)->pluck('id'))
                ->groupBy('goliath_attacks.id', 'goliath_metro.telemetry_identifier', 'goliath_subregions.telemetry_identifier', 'goliath_regions.telemetry_identifier');

                if($_prefix_id !== null) {
                    $q = $q->where('goliath_attack_prefixes.prefix_id', '=', $_prefix_id);
                }

                return $q;
        };

        // Build our search function
        $query_search = function($prefix_query, $global, $filters) use($flag_status) {
            $columns = [
                'start',
                'end',
                'type',
                'description',
                'destination',
                'datacentre',
                'subregion',
                'region',
                'prefixes'
            ];

            $columns_for_global = [
                'started_at',
                'last_at',
                'type',
                'description',
                'destination',
                'goliath_metro.telemetry_identifier',
                'goliath_subregions.telemetry_identifier',
                'goliath_regions.telemetry_identifier'
            ];

            if($global) {
                $prefix_query = $prefix_query->where(function($query) use ($global, $columns_for_global) {
                    foreach($columns_for_global as $column) {
                        if($column === 'last_at' && strtoupper($global) === 'ONGOING') {
                            $query->orWhere('goliath_attacks.updated_at', '>', Carbon::now()->subMinutes(2));
                        }

                        $query->orWhere($column, 'LIKE', "%$global%");
                    }
                });
            }

            if($filters) {
                foreach($filters as $filter) {
                    if( array_search($filter['id'], $columns) !== false ) {
                        switch($filter['id']) {
                            case 'start':
                                $prefix_query = $prefix_query->where('started_at', 'LIKE', '%' . $filter['value'] . '%');
                                break;
                            case 'end':
                                $prefix_query = $prefix_query->where('last_at', 'LIKE', '%' . $filter['value'] . '%');
                                break;
                            case 'datacentre':
                                $prefix_query = $prefix_query->where('goliath_metro.telemetry_identifier', 'LIKE', '%' . $filter['value'] . '%');
                                break;
                            case 'subregion':
                                $prefix_query = $prefix_query->where('goliath_subregions.telemetry_identifier', 'LIKE', '%' . $filter['value'] . '%');
                                break;
                            case 'region':
                                $prefix_query = $prefix_query->where('goliath_regions.telemetry_identifier', 'LIKE', '%' . $filter['value'] . '%');
                                break;
                            /*default:
                                $prefix_query = $prefix_query->where($filter['id'], 'LIKE', '%' . $filter['value'] . '%');*/
                        }
                    }
                }
            }

            if(strtoupper($flag_status) === 'ONGOING') {
                $prefix_query->where('goliath_attacks.updated_at', '>', Carbon::now()->subMinutes(2));
            }

            return $prefix_query;
        };

        // Build our sorting function
        $query_sort = function($prefix_query, $column, $direction) {
            return $prefix_query->orderByDesc('goliath_attacks.created_at');
        };

        // Build our transformation function
        $query_transform = function($data) {
            return $data;
        };

        return NetworkController::pagination_table_request($_r, $query_builder, $query_search, $query_sort, $query_transform, []);
    }

    public function get_usage_chart(Request $_r) {

        // For whoever reads this in the future - We're being careful here because we want to very strictly sanitise data that could end up being passed to clickhouse. While we sanitise, we've rolled our own sanity. We need to be cautious.
        $valid_request = !Validator::make($_r->only('time', 'type'), [
                'time' => ['required', Rule::in( 'five-minutes', 'quarter-hour', 'half-hour', '1-hour', '2-hour', '6-hour', '12-hour', '24-hour', '48-hour' )],
                'type' => ['required', Rule::in( 'pps', 'bps' )]
            ])->fails();

        if(!$valid_request) {
            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        }

        $start_from = 3600;

        switch($_r->input('time')) {
            case 'five-minutes': $start_from = 300; break;
            case 'quarter-hour': $start_from = 900; break;
            case 'half-hour': $start_from = 1800; break;
            case '1-hour': $start_from = 3600; break;
            case '2-hour': $start_from = 7200; break;
            case '6-hour': $start_from = 21600; break;
            case '12-hour': $start_from = 43200; break;
            case '24-hour': $start_from = 86400; break;
            case '48-hour': $start_from = 172800; break;
        }

        // Build our clickhouse handler
        $chh = new ClickhouseHandler(
            'default',
            null,
            null,
            null,
            null,
            'https://akvorado.gsl.tools/clickhouse/'
        );

        $asns = Asns::where('organisation_id', '=', Auth::user()->organisation_id)->get()->pluck('asn');

        if($_r->input('type') === 'pps') {
            return ChartHandler::goliathDropped($chh, Auth::user()->organisation_id === 1 ? [137409] : $asns, null, false, true, $start_from)[0];
        }
        else {
            return ChartHandler::goliathDropped($chh, Auth::user()->organisation_id === 1 ? [137409] : $asns, null, true, false, $start_from)[1];
        }

        return response()->json([], 500);
    }
}
