<?php

namespace App\Http\Controllers\Provisioning;

// Laravel
use App\Models\Asns;
use App\Models\AsSets;
use App\Models\Goliath\GoliathSubregion;
use App\Models\Organisation;
use App\Models\Prefixes;
use App\Rules\AdminAnyAsnIdentity;
use App\Rules\AdminAnyAsSetIdentity;
use App\Rules\ComputePeerIdentity;
use App\Rules\Fragments\PagedTable;
use App\Rules\PermittedIn;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Validator;

class AdminComputeController extends ComputeController
{

    public function get_options() {
        $all_prefixes = Prefixes::where('organisation_id', '=', 1)->select('address', 'mask', 'id')->get();

        return response()->json([
            'v4' => boolval($all_prefixes->filter(function ($item) { return Str::contains($item['address'], '.'); })->count()),
            'v6' => boolval($all_prefixes->filter(function ($item) { return Str::contains($item['address'], ':'); })->count()),
            'asns' => Asns::select('id', 'asn')->where([['approved', '=', 1]])->get(),
            'as_sets' => AsSets::select('id', 'as_set')->where([['approved', '=', 1]])->get()
        ]);
    }

    public function get_peers(Request $_r) 
    {

        $valid_request = !Validator::make($_r->all(), PagedTable::rules([]))->fails();

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

        // Unfortunately we need to iterate over the peers in batches of 200, where we will then attempt to find as many matches as possible for the end user, and then we'll do as needed. Match on ASN.
        $scan_complete = false;
        $rows = [];

        $known_asns = Asns::withTrashed()->select('asn')->where([['approved', '=', 1]])->get()->keyBy('asn');

        $current_offset = $_r->input('start');

        $peers = collect($this->helper_get_sessions())->groupBy('session_name');

        \Log::debug($peers);

        while(!$scan_complete) {
            $row_selection = $this->helper_get_peers($current_offset, $_r->input('length'));

            // Find matches in the selection.
            foreach($row_selection as $row) {

                if(isset($known_asns[$row['asn']])) {
                    
                    // Generate our safe key
                    $key = $this->helper_row_key($row['id'], $row['node_location'], $row['asn']);
                    
                    Redis::setex(('U:' . Auth::user()->id . ':COMPUTE:ROW:P:' . $key), 259200, $row['id']);

                    // Get sessions
                    $peers_4_key = 'as' . $row['asn'] . '_v4_' . str_replace('.', '_', $row['ipv4']);
                    $peers_6_key = 'as' . $row['asn'] . '_v6_' . str_replace(':', '_', $row['ipv6']);

                    $sessions_max = 2 + ($row['ipv6'] !== null ? 2 : 0);
                    $sessions_up = 0;

                    foreach([
                        ...( isset($peers[$peers_4_key]) ? $peers[$peers_4_key] : [] ),
                        ...( isset($peers[$peers_6_key]) ? $peers[$peers_6_key] : [] ),
                    ] as $peer) {

                        if($peer['state'] === 'UP') {
                            ++$sessions_up;
                        }

                    }

                    $rows[] = [ ...$row, 'id' => $key, 'sessions' => [ 'up' => $sessions_up, 'total' => $sessions_max ] ];
                }

                // Check if we're done so we can bail out
                if(sizeof($rows) >= $_r->input('length')) {
                    $scan_complete = true;
                    break;
                }
            }

            $current_offset += $_r->input('length');

            if(sizeof($row_selection) != $_r->input('length')) {
                $scan_complete = true;
                break;
            }
        }

        return response()->json([
            'draw' => (int)$_r->input('draw', 0),
            'recordsTotal' => sizeof($rows),
            'recordsFiltered' => sizeof($rows),
            'data' => $rows
        ], 200);

    }

    public function add_peer(Request $_r) {

        $valid_request = !Validator::make($_r->all(), [
            'asn' => ['required', AdminAnyAsnIdentity::make(true)],
            'filter_type' => ['required', Rule::in('asn', 'as-set')],
            'filter' => ['required', $_r->input('filter_type') === 'asn' ? AdminAnyAsnIdentity::make(true) : AdminAnyAsSetIdentity::make(true)],
            'server_v4' => ['required'],
            'server_v6' => ['nullable'],
            'table_type' => ['required', Rule::in('full', 'default')],
            'import_type' => ['required', new PermittedIn([
                'downstream_valid_unknown_transit'  => 'compute.bgp.tier3',
                'downstream_valid_only_transit'     => 'compute.bgp.tier2',
                'downstream_valid_unknown'          => 'compute.bgp.tier1',
                'downstream_valid_only'             => null,
                'HIGHRISK-NOFILTER'                 => 'compute.bgp.manage'
            ])],
            'allow_most_specific' => ['required', 'boolean']
        ])->fails();

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

        // We must now construct our payload. We need to obtain the following information: Node location, ASN, IPv4 and 6, table, import, and whether we allow long
        $community = Prefixes::leftJoin('prefix_community', 'prefix_id', '=', 'prefixes.id')
            ->whereNull('prefix_community.deleted_at')
            ->whereRaw('((community LIKE \'65500:%\' OR community LIKE \'7578:%\') AND community LIKE \'%99\' AND community != \'7578:99\')')
            ->where([
                [DB::raw("CONCAT(address, '/', mask)::cidr"), '>>', ($_r->input('server_v4') . '/32')],
            ])
            ->orderBy('mask')
            ->first()
            ->community ?? false;

        // Once we know we have a community to use to obtain a location...
        if($community !== false) {

            $community_location_lookup = json_decode(file_get_contents(app_path('Models/CommunityLocationLookup.json')), true);;
           
            $region = isset($community_location_lookup[$community]) ? GoliathSubregion::where('telemetry_identifier', '=', $community_location_lookup[$community])->first()->name : false;

            if($region !== false) {

                $asn = Asns::find($_r->input('asn'));
                $filter = $_r->input('filter_type') === 'as-set' ? AsSets::find($_r->input('filter')) : Asns::find($_r->input('filter'));
    
                $payload = [
                    'node_location'         => str_replace(' ', '', explode(', ', explode(' - ', $region)[1])[0]),
                    'asn'                   => $asn->asn,
                    'ipv4'                  => $_r->input('server_v4'),
                    'ipv6'                  => $_r->input('server_v6'),
                    'full_tables'           => $_r->input('table_type') === 'full' ? true : false,
                    'import_type'           => $_r->input('import_type'),
                    'allow_long_prefixes'   => $_r->input('allow_most_specific'),
                    'prefix_filter'         => $_r->input('filter_type') === 'asn' ? ("AS$filter->asn") : $filter->as_set,
                    'peer_name'             => Organisation::find($asn->organisation_id)->name
                ];

                $outcome = $this->helper_add_peer($payload);

                if($outcome->status() === 200) {
                    return response()->json(['detail' => 'Successfully provisioned session.'], 200);
                }

                \Log::error("We didn't get a 200");
                \Log::error($outcome->status());
                \Log::error($outcome->json());

            }
            else {
                \Log::error("Could not find a region string for community $community - Session information: " . json_encode($_r->input()));
            }
        }
        else {
            \Log::error("Could not find a community - Session information: " . json_encode($_r->input()));
        }

        return response()->json(['detail' => 'Issue provisioning session.'], 500);

    }

    public function delete_session(Request $_r, $_peer_id) {

        $validator = Validator::make([
            'peer_id' => $_peer_id
        ], [
            'peer_id' => ['required', new ComputePeerIdentity],
        ]);

        if($validator->fails()) {
            return response()->json(['detail' => 'Invalid identifier provided.'], 400);
        }

        $real_id = Redis::get('U:' . Auth::user()->id . ":COMPUTE:ROW:P:$_peer_id");

        if($real_id !== null) {

            $response = $this->helper_delete_peer($real_id);

            if($response->status() !== 200) {
                return response()->json(['detail' => 'There was an issue deleting the peer.'], 500);
            }
            else {
                return response()->json(['detail' => 'Peer deleted.'], 200);
            }
        }

        return response()->json(['detail' => 'An error has occurred.'], 500);
    }

}