<?php

namespace App\Http\Controllers\Provisioning;

// Laravel
use App\Http\Controllers\BaseController;
use App\Models\Asns;
use App\Models\AsSets;
use App\Models\OrganisationExternalIdentities;
use App\Rules\Cidr;
use App\Rules\Fragments\PagedTable;
use App\Rules\OwnedAsnIdentity;
use App\Rules\OwnedAsSetIdentity;
use App\Rules\OwnedTunnelIdentity;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Facades\Validator;

class TunnelController extends BaseController
{

    private $MERCURY_ENDPOINT_REGIONS = [
        "akl" => 'Auckland',
        "ams" => 'Amsterdam',
        "ash" => 'Ashburn',
        "atl" => 'Atlanta',
        "chi" => 'Chicago',
        "dal" => 'Dallas',
        "fra" => 'Frankfurt',
        "hel" => 'Helsinki',
        "hkg" => 'Hong Kong',
        "kr" => 'Korea',
        "lax" => 'Los Angeles',
        "lon" => 'London',
        "mad" => 'Madrid',
        "mct" => 'Muscat',
        "mia" => 'Miami',
        "mil" => 'Milan',
        "mrs" => 'Marseilles',
        "nyc" => 'New York',
        "par" => 'Paris',
        "phx" => 'Phoenix',
        "sea" => 'Seattle',
        "sin" => 'Singapore',
        "sof" => 'Sofia',
        "syd" => 'Sydney',
        "tpe" => 'Taipei',
        "tyo" => 'Tokyo',
        "vie" => 'Vienna',
        "war" => 'Warsaw'
    ];

    public function helper_get_tunnels($_cxid) {
        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
        ->get(env('tunnel_microservice_url') . "/api/v1/tunnels/by-cxid/$_cxid");
    }

    public function helper_get_tunnel($_endpoint) {
        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
        ->get(env('tunnel_microservice_url') . "/api/v1/tunnels/by-endpoint/$_endpoint");
    }

    public function helper_delete_tunnel($_endpoint) {
        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
        ->delete(env('tunnel_microservice_url') . "/api/v1/tunnels/by-endpoint/$_endpoint");
    }

    public function helper_add_tunnel($_cxid, $_endpoint, $_asn, $_as_set, $_max_prefix_length, $_bgp, $_overrides = []) {

        $bgp_address = $_bgp !== null ? $_bgp : $_endpoint;
        $bgp_address_key = str_contains($bgp_address, '.') ? 'bgp_ipv4' : 'bgp_ipv6';

        $payload = [
            "cxid"              => "$_cxid",
            "endpoint_ip"       => $_endpoint,
            "asn"               => (int)$_asn,
            "as_set"            => $_as_set,
            "max_prefix_len"    => $_max_prefix_length,
            $bgp_address_key    => $bgp_address
        ];

        if($_overrides !== []) {
            $payload['endpoint_overrides'] = $_overrides;
            $payload['is_advanced'] = true;
        }

        \Log::debug($payload);

        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
        ->post(env('tunnel_microservice_url') . "/api/v1/tunnels", $payload);
    }

    public function helper_edit_tunnel($_endpoint, $_overrides) {

        \Log::debug([
            "overrides" => $_overrides
        ]);

        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
        ->put(env('tunnel_microservice_url') . "/api/v1/tunnels/by-endpoint/$_endpoint/overrides", [
            "overrides" => $_overrides
        ]);

    }

    public function helper_update_as_set($_endpoint, $_as_set) {

        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
        ->put(env('tunnel_microservice_url') . "/api/v1/tunnels/by-endpoint/$_endpoint", [
            "as_set" => $_as_set,
        ]);

    }

    public function helper_update_prefix_length($_endpoint, $_max_prefix_length) {

        return Http::withHeaders([
            'Content-Type' => 'application/json'
        ])
        ->withHeaders([ 'X-API-Key' => env('tunnel_microservice_key') ])
        ->put(env('tunnel_microservice_url') . "/api/v1/tunnels/by-endpoint/$_endpoint", [
            "max_prefix_len" => $_max_prefix_length,
        ]);

    }

    public function get_options() {
        return response()->json([
            'asns' => Asns::select('id', 'asn')->where([['organisation_id', '=', Auth::user()->organisation_id], ['approved', '=', 1]])->get(),
            'as_sets' => AsSets::select('id', 'as_set')->where([['organisation_id', '=', Auth::user()->organisation_id], ['approved', '=', 1]])->get(),
            'regions' => $this->MERCURY_ENDPOINT_REGIONS
        ]);
    }

    public function get_tunnels(Request $_r) {

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

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

        // We can select literally all of the tunnels, and that's what we'll do. Then we'll iterate using values.
        $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);
        }

        $pulled_tunnels = collect([]);

        foreach($ids as $id) {
            
            $tunnel_response = $this->helper_get_tunnels($id->identity);

            if($tunnel_response->status() === 200) {
                $pulled_tunnels = $pulled_tunnels->merge(collect($tunnel_response->json()));
            }

        }

        // We grab the tunnels we're concerned with then iterate over them to cache a sensible tunnel ID and build sensible rows
        $pulled_tunnels = $pulled_tunnels->skip($_r->input('start'))->take($_r->input('length'));

        $rows = [];

        foreach($pulled_tunnels->toArray() as $x) {

            $row = $x;

            unset($row['cxid']);

            $row['asn_id'] = Asns::where([['asn', '=', $row['asn']], ['organisation_id', '=', Auth::user()->organisation_id], ['approved', '=', 1]])->first()->id ?? null;
            $row['as_set_id'] = AsSets::where([['as_set', '=', $row['as_set']], ['organisation_id', '=', Auth::user()->organisation_id]])->first()->id ?? null;

            $row['p2p_ipv4_cli'] = long2ip(ip2long(explode('/', $row['p2p_ipv4'])[0]) + 1) . '/30';
            $row['p2p_ipv6_cli'] = \DB::select("SELECT (?::inet + 1)::text as incremented", [$row['p2p_ipv6']])[0]->incremented;


            if(isset($row['endpoint_overrides'])) {
                $endpoint_labels = [];

                foreach($row['endpoint_overrides'] as $k => $v) {
                    $endpoint_labels[$this->MERCURY_ENDPOINT_REGIONS[$k]] = $v;
                }

                $row['endpoint_labels'] = $endpoint_labels;
            }

            if($row['asn_id'] === null) {
                continue;
            }

            $rows[] = $row;



            Redis::setex(('U:' . Auth::user()->organisation_id . ':TUNNEL:ROW:P:' . $x['id']), 604800, json_encode($x));
        }

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

    }

    public function add_tunnel(Request $_r) {

        \Log::debug($_r->input());

        // Standard validation
        $valid_request = !Validator::make($_r->all(), [
            'address'           => [ 'required', new Cidr ],
            'bgp_address'       => [ 'nullable', new Cidr ],
            'asn_id'            => [ 'required', OwnedAsnIdentity::make(true) ],
            'as_set_id'         => [ 'nullable', OwnedAsSetIdentity::make(true) ],
            'advanced.endpoint' => [ 'nullable' ],
            ...(function() {
                $regional_rules = [];

                foreach(array_keys($this->MERCURY_ENDPOINT_REGIONS) as $key) {
                    $regional_rules["advanced.endpoint.$key"] = [ new Cidr ];
                }

                return $regional_rules;
            })()
        ])->fails();

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

        $cxid = OrganisationExternalIdentities::where([['key', '=', 'gsl'], ['organisation_id', '=', Auth::user()->organisation_id]])->first()->identity ?? null;

        if($cxid === null) {
            return response()->json(['detail' => 'Precondition not met.'], 428);
        }

        $asn = Asns::find($_r->input('asn_id'))->asn;
        $as_set = $_r->input('as_set_id') === null ? ('AS' . Asns::find($_r->input('asn_id'))->asn) : AsSets::find($_r->input('as_set_id'))->as_set;

        // Quickly generate our regional overrides
        $regional_overrides = [];
        
        if( $_r->input('advanced.endpoint') !== null ) {

            $advanced_endpoint_input = $_r->input('advanced.endpoint');

            foreach(array_keys($this->MERCURY_ENDPOINT_REGIONS) as $key) {
                $regional_overrides[$key] = $advanced_endpoint_input[$key];
            }
        }

        $outcome = $this->helper_add_tunnel(
            $cxid,
            $_r->input('address'),
            $asn,
            $as_set,
            26,
            $_r->input('bgp_address'),
            $regional_overrides
        );

        switch($outcome->status()) {
            case 200:
                return response()->json(['detail' => 'Successfully provisioned tunnel.'], 200);
                break;
            case 409:
                return response()->json(['detail' => 'Tunnel already exists for this endpoint.'], 409);
                break;
            default:
                \Log::debug($outcome);
                return response()->json(['detail' => 'An error has occurred.'], 500);
        }

    }

    public function update_as_set(Request $_r, $_tunnel_id) {
        $validator = Validator::make([
            'tunnel_id' => $_tunnel_id,
            'as_set_id' => $_r->input('as_set_id')
        ], [
            'tunnel_id' => [ 'required', new OwnedTunnelIdentity ],
            'as_set_id' => [ 'nullable', OwnedAsSetIdentity::make(true) ]
        ]);

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

        $endpoint = json_decode(Redis::get('U:' . Auth::user()->organisation_id . ":TUNNEL:ROW:P:$_tunnel_id"), true);

        $as_set = $_r->input('as_set_id') === null ? ('AS' . $endpoint['asn']) : AsSets::find($_r->input('as_set_id'))->as_set;

        $outcome = $this->helper_update_as_set($endpoint['endpoint_ip'], $as_set);

        switch($outcome->status()) {
            case 200:
                return response()->json(['detail' => 'Successfully updated tunnel.'], 200);
                break;
            default:
                \Log::debug($outcome);
                return response()->json(['detail' => 'An error has occurred.'], 500);
        }

    }

    public function edit_tunnel(Request $_r, $_tunnel_id) {
        $validator = Validator::make([
            'tunnel_id' => $_tunnel_id,
            'endpoints' => $_r->input('endpoints')
        ], [
            'tunnel_id' => [ 'required', new OwnedTunnelIdentity ],
            ...(function() {
                $regional_rules = [];

                foreach(array_keys($this->MERCURY_ENDPOINT_REGIONS) as $key) {
                    $regional_rules["endpoint.$key"] = [ new Cidr ];
                }

                return $regional_rules;
            })()
        ]);

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

        $endpoint = json_decode(Redis::get('U:' . Auth::user()->organisation_id . ":TUNNEL:ROW:P:$_tunnel_id"), true);

        $outcome = $this->helper_edit_tunnel($endpoint['endpoint_ip'], $_r->input('endpoints'));

        switch($outcome->status()) {
            case 200:
                return response()->json(['detail' => 'Successfully updated tunnel.'], 200);
                break;
            default:
                \Log::debug($outcome);
                return response()->json(['detail' => 'An error has occurred.'], 500);
        }

    }

    public function delete_tunnel(Request $_r, $_tunnel_id) {
        $validator = Validator::make([
            'tunnel_id' => $_tunnel_id,
        ], [
            'tunnel_id' => [ 'required', new OwnedTunnelIdentity ],
        ]);

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

        $endpoint = json_decode(Redis::get('U:' . Auth::user()->organisation_id . ":TUNNEL:ROW:P:$_tunnel_id"), true)['endpoint_ip'];

        // Check that the tunnel is still infact associated with the user
        $tunnel_status = $this->helper_get_tunnel($endpoint);

        $still_has_cxid = OrganisationExternalIdentities::where([['key', '=', 'gsl'], ['organisation_id', '=', Auth::user()->organisation_id], ['identity', '=', $tunnel_status['cxid']]])->count();
        $has_asn_access = Asns::where([['asn', '=', $tunnel_status['asn']], ['organisation_id', '=', Auth::user()->organisation_id], ['approved', '=', 1]])->count();

        if($still_has_cxid && $has_asn_access) {
            $outcome = $this->helper_delete_tunnel($endpoint);

            \Log::debug($outcome);

            switch($outcome->status()) {
                case 200:
                    return response()->json(['detail' => 'Successfully deleted tunnel.'], 200);
                    break;
                default:
                    \Log::debug($outcome);
                    return response()->json(['detail' => 'An error has occurred.'], 500);
            }
        }

        return response()->json(['detail' => 'You do no have access to this tunnel.'], 400);
    }

}