<?php

namespace App\Http\Controllers\Provisioning;

// Laravel
use App\Http\Controllers\BaseController;
use App\Models\Asns;
use App\Models\OrganisationExternalIdentities;
use App\Rules\Fragments\PagedTable;
use App\Rules\OwnedAsnIdentity;
use App\Rules\OwnedAsSetIdentity;
use App\Rules\SidHash;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

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

use Illuminate\Support\Facades\RateLimiter;

class BgpController extends BaseController
{

    public function __construct()
    {

        if(config('app.demonstration')) {
            Http::fake([
                'http://localhost:56000/scan/cxid/*' => Http::response((function() {

                    $data = json_decode(file_get_contents(app_path('Demonstration/BgpPortsDemo.json')), true);

                    return json_encode($data['list']);

                })(), 200),
                
                'http://localhost:56000/live/details/Auckland-Router' => Http::response((function() {
                    return json_encode(json_decode(file_get_contents(app_path('Demonstration/BgpPortsDemo.json')), true)['ports']['Auckland-Router']);
                })(), 200),

                'http://localhost:56000/live/details/Ashburn-Router' => Http::response((function() {
                    return json_encode(json_decode(file_get_contents(app_path('Demonstration/BgpPortsDemo.json')), true)['ports']['Ashburn-Router']);
                })(), 200),

                'http://localhost:56000/live/details/Singapore-Router' => Http::response((function() {
                    return json_encode(json_decode(file_get_contents(app_path('Demonstration/BgpPortsDemo.json')), true)['ports']['Singapore-Router']);
                })(), 200),
                
                'http://localhost:56000/peeringdb/asn/137409*' => Http::response(
                    json_decode('{"network_name":"Testing w/ 137409","irr_as_set":null,"info_prefixes4":50,"info_prefixes6":100,"asn":137409,"ipv4_subnet":"10.16.0.2/31","ipv6_subnet":"2401:3cc0::4:502/127","ipv4_is_private":true,"suggested_ipv4_hash":"8fefb3e0aa49e98b163ca9c80beb318a97a48ff2ae76e58bf3423cc5deb566c0","suggested_ipv6_hash":"9abcd296f4fb4c8d5163a85a08c6319af83fd4bec95472c7a04db54136245b24"}', true),
                    200
                ),

                'http://localhost:56000/provision/add-bgp-session*' => Http::response(
                    json_decode('{"message": "Live mode: BGP session request accepted for SID"}', true),
                    200
                ),

                '*' => Http::response([ 'Fallback Error' ], 500) // Fallback
            ]);

        }
        
        parent::__construct();
    }

    protected function helper_scan_cxid($_cxid) {
        
        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->retry(3, 100)
        ->get(env('bgp_microservice') . "/scan/cxid/$_cxid")
        ->json();
    }

    protected function helper_get_details($_uuid) {
        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->retry(3, 100)
        ->get(env('bgp_microservice') . "/live/details/$_uuid")
        ->json();
    }

    protected function helper_get_asn($_asn, $_sid, $_uuid) {
        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->retry(3, 100)
        ->get(env('bgp_microservice') . "/peeringdb/asn/$_asn", [
            'sid' => $_sid,
            'temp_id' => $_uuid
        ])
        ->json();
    }

    protected function helper_add_session($_uuid, $_sid, $_ipv4_hash, $_ipv6_hash, $_asn, $_as_set, $_filter, $_vlan_id, $_max_prefix_4, $_max_prefix_6) {
        // Use Laravel's built-in rate limiter
        $limiterKey = 'bgp_session_rate_limit' . Auth::user()->id;

        $response = null;

        $executed = RateLimiter::attempt(
            $limiterKey,
            $perMinute = 1,
            function() use(&$response, $_uuid, $_sid, $_ipv4_hash, $_ipv6_hash, $_asn, $_as_set, $_filter, $_vlan_id, $_max_prefix_4, $_max_prefix_6) {
        
                $response = Http::withHeaders([
                        'Content-Type' => 'application/json'
                    ])
                    ->post(env('bgp_microservice') . "/provision/add-bgp-session", [
                        'temp_id'       => $_uuid,
                        'sid'           => $_sid,
                        'ipv4_hash'     => $_ipv4_hash,
                        'ipv6_hash'     => $_ipv6_hash,
                        'asn'           => $_asn,
                        'as_set'        => $_as_set,
                        'filter'        => $_filter,
                        'vlan_id'       => $_vlan_id,
                        'max_prefix_4'  => $_max_prefix_4,
                        'max_prefix_6'  => $_max_prefix_6
                    ])
                    ->json();

            }, 300
        );

        return $executed != true ? [
            'message' => 'rate-limit'
        ] : ( $response === null ? [
            'message' => 'failure'
        ] : $response );
    }

    protected function get_sid_hash($_id, $_name) {
        return hash('sha512', "$_id-$_name");
    }

    protected function get_sid_key($_hash) {
        return 'U:' . Auth::user()->id . ":P:$_hash";
    }

    public function get_sid_list(Request $_r) {

        // Validate input information.
        $valid_request = !Validator::make($_r->all(), PagedTable::rules(['community', 'description', 'type']))->fails();

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

        // Get the users WHMCS ID
        $ids = OrganisationExternalIdentities::where([['key', '=', 'gsl'], ['organisation_id', '=', Auth::user()->organisation_id]])->get() ?? [];

        if($ids === []) {
            return response()->json([
                'draw' => (int)$_r->input('draw', 0),
                'recordsTotal' => 0,
                'recordsFiltered' => 0,
                'data' => []
            ], 200);
        }

        // Use it to make calls to our provisioning system
        
        $raw_rows = [];

        foreach($ids as $id) {
            $raw_rows = [...$raw_rows, ...$this->helper_scan_cxid($id->identity)];
        }

        // Iterate over each batch interface to build up the true logical representation of the super-interfaces

        $processed_rows = [];

        foreach($raw_rows as $i => $superinterface) {

            if($superinterface['batch_interfaces'] !== null && sizeof($superinterface['batch_interfaces']) !== 0) {

                foreach($superinterface['batch_interfaces'] as $j => $subinterface) {

                    // We only care IF it is the parent interface
                    if($subinterface['parent_interface_name'] === null) {

                        // Cache the temporary identifiers against a distinct identifiable. We'll just use a session.
                        $redis_hash = $this->get_sid_hash("$i-$j", $superinterface['display_hint']);
                        $redis_string = $this->get_sid_key($redis_hash);

                        Redis::setex($redis_string, 600, $superinterface['temp_id']);

                        $processed_rows[] = [
                            'id'                    => $redis_hash,
                            'interface_type'        => $subinterface['interface_type'],
                            'interface_name'        => $subinterface['interface_name'],
                            'router_name'           => $superinterface['display_hint'],
                            'is_unsupported'        => $superinterface['is_unsupported']
                        ];

                    }
                }
            }
            else {
                if($superinterface['parent_interface_name'] === null) {
                    // Cache the temporary identifiers against a distinct identifiable. We'll just use a session.
                    $redis_hash = $this->get_sid_hash("$i", $superinterface['display_hint']);
                    $redis_string = $this->get_sid_key($redis_hash);

                    Redis::setex($redis_string, 600, $superinterface['temp_id']);

                    $processed_rows[] = [
                        'id'                    => $redis_hash,
                        'interface_type'        => $superinterface['interface_type'],
                        'interface_name'        => '-',
                        'router_name'           => $superinterface['display_hint'],
                        'is_unsupported'        => $superinterface['is_unsupported'],
                    ];
                }
            }
        }

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

    public function get_sid_details(Request $_r, $_id) {

        // Validate this request in the first place.
        $valid_request = !Validator::make( [ 'uuid' => $_id ], [
            'uuid'    => ['required', 'string', new SidHash]
        ])->fails();

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

        // Take a distinct identifiable, check the session for this information, and then use the pulled out UUID to get the SID details.
        if(($uuid = Redis::get($this->get_sid_key($_id))) && $uuid !== null) {

            // Use it to make calls to our provisioning system
            $raw_rows = $this->helper_get_details($uuid);

            $processed_parentals = [];
            $processed_children = [];
            $processed_sessions = [];

            foreach($raw_rows as $row) {

                unset($row['temp_id']);

                if($row['parent_interface_name'] === null) {
                    $processed_parentals[$row['interface_name']] = $row;

                    if(isset($row['details']) && isset($row['details']['bgp_details']) && $row['details']['bgp_details'] !== []) {
                        $processed_sessions[$row['interface_name']] = isset($processed_sessions[$row['interface_name']]) ? [
                            ...$processed_sessions[$row['interface_name']],
                            ...$row['details']['bgp_details']
                        ] : [
                            ...$row['details']['bgp_details']
                        ];
                    }
                }
                else {
                    $processed_children[$row['parent_interface_name']][$row['interface_name']] = $row;

                    if(isset($row['details']) && isset($row['details']['bgp_details']) && $row['details']['bgp_details'] !== []) {
                        $processed_sessions[$row['parent_interface_name']] = isset($processed_sessions[$row['parent_interface_name']]) ? [
                            ...$processed_sessions[$row['parent_interface_name']],
                            ...$row['details']['bgp_details']
                        ] : [
                            ...$row['details']['bgp_details']
                        ];
                    }
                }
            }
            
            return response()->json([
                'parent'    => $processed_parentals,
                'children'  => $processed_children,
                'bgp'       => $processed_sessions
            ], 200);
        }

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

    public function get_asn_details(Request $_r) {

        // Validate this request in the first place.
        $valid_request = !Validator::make( $_r->all(), [
            'asn_id'    => ['required', OwnedAsnIdentity::make(true)],
            'sid'       => ['required', 'string'],
            'identity'  => ['required', 'string', new SidHash],
        ])->fails();

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

        // Take a distinct identifiable, check the session for this information, and then use the pulled out UUID to get the SID details.
        if(($uuid = Redis::get($this->get_sid_key($_r->input('identity')))) && $uuid !== null) {

            // Use it to make calls to our provisioning system
            $information = $this->helper_get_asn(Asns::find($_r->input('asn_id'))->asn, $_r->input('sid'), $uuid );
            
            return response()->json([
                'info' => $information,
            ], 200);
        }

        return response()->json([], 400);
    }
    
    public function post_bgp_session(Request $_r) {

        // Validate this request in the first place.
        $valid_request = !Validator::make( $_r->all(), [
            'identity'          => ['required', 'string', new SidHash],
            'sid'               => ['required', 'string'],
            'v4_hash'           => ['required', 'string'],
            'v6_hash'           => ['required', 'string'],
            'asn_id'            => ['required', OwnedAsnIdentity::make(true)],
            'as_set_id'         => ['nullable', OwnedAsSetIdentity::make(true)],
            'filter'            => ['required', Rule::in(['full', 'full-nodefault', 'default', 'denyall'])],
            'vlan'              => ['nullable', 'integer', 'min:2', 'max:4094'],
            'v4_max'            => ['nullable', 'integer', 'min:0'],
            'v6_max'            => ['nullable', 'integer', 'min:0']
        ])->fails();

        \Log::warning("BGP session posted. Information logged.");
        \Log::warning($_r->all());

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

        // Take a distinct identifiable, check the session for this information, and then use the pulled out UUID to get the SID details.
        if(($uuid = Redis::get($this->get_sid_key($_r->input('identity')))) && $uuid !== null) {

            $asn = Asns::find($_r->input('asn_id'))->asn;

            $v4_max = $_r->input('v4_max', null) ?? 1;
            $v6_max = $_r->input('v6_max', null) ?? 1;

            \Log::info("Session call of: " . json_encode([$uuid, $_r->input('sid'), $_r->input('v4_hash'), $_r->input('v6_hash'), $asn, null, $_r->input('filter'), $_r->input('vlan'), ($v4_max == 0 ? 1 : $v4_max), ($v6_max == 0 ? 1 : $v6_max)]));
            $session_add = $this->helper_add_session($uuid, $_r->input('sid'), $_r->input('v4_hash'), $_r->input('v6_hash'), $asn, null, $_r->input('filter'), $_r->input('vlan'), ($v4_max == 0 ? 1 : $v4_max), ($v6_max == 0 ? 1 : $v6_max));

            \Log::debug($session_add);

            if(str_contains($session_add['message'], 'BGP session request accepted')) {
                return response()->json([], 200);                
            }

            if(str_contains($session_add['message'], 'rate-limit')) {
                return response()->json([], 429);                
            }

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

}