<?php

namespace App\Http\Controllers\Organisation;

use App\Helpers\Utility\WhoIs;
use App\Http\Controllers\BaseController;
use App\Models\Asns;
use App\Models\AsSets;
use App\Models\CodeAsnClaim;
use App\Models\CodeAsSetClaim;
use App\Rules\AsnFormat;
use App\Rules\AsSetFormat;
use App\Rules\OwnedAsnIdentity;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Notification;
use App\Notifications\SlackNotification;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Throwable;

class Controller extends BaseController
{
    public function get_as_set(Request $_r) {
        return $this->paged_table($_r, function() {
            return AsSets::select('id', 'as_set', 'approved')->where('organisation_id', '=', Auth::user()->organisation_id);
        }, [ 
            'as_set' => [ 'db' => 'as_set', 'searchable' => true ]
        ], 'as_set', null);
    }

    public function post_as_set(Request $_r) {
        $response = response()->json(__('api.500_GENERIC'), 500);

        // Check whether the AS-SET is nested in a given ASN...
        $nested_asn = str_contains($_r->input('as_set'), ':') === true ? explode(':', $_r->input('as_set'))[0] : false;

        // Check whether we can find the nested ASN
        $valid_as_set = $nested_asn ? Asns::where([['organisation_id', '=', Auth::user()->organisation_id], ['asn', '=', ltrim($nested_asn, 'ASas')], ['approved', '=', 1]])->count() > 0 : false;

        $validated_rules = Validator::make($_r->all(), [
            'as_set' => ['required', AsSetFormat::make(true)],
        ]);

        Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
            "NEW AS-SET ADD ATTEMPT - DETAILS TO COME IF VALID PAYLOAD. INVALID PAYLOADS ARE LOGGED.\n" .
            'USER: ' . Auth::user()->email
        ));

        \Log::debug("\nAS-SET ADD ATTEMPT\n" .
            'User: ' . Auth::user()->email . "\n" .
            'Inputs: ' . json_encode($_r->input()) . "\n" .
            'Errors: ' . json_encode($validated_rules->errors()) . "\n" .
            'Nested ASN: ' . ($nested_asn ? $nested_asn : '-') . "\n" .
            'Valid AS SET: ' . ($valid_as_set ? 'true' : 'false') . "\n"
        );

        try {
            $as_set = $_r->input('as_set');

            if(!$validated_rules->fails() && $valid_as_set) {

                Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
                    "ONGOING AS-SET ADD ATTEMPT - VALID, VERIFYING\n" .
                    'USER: ' . Auth::user()->email . 
                    "\nAS-SET: $as_set"
                ));

                $outcome = DB::transaction(function () use($as_set) {
                    return AsSets::make(strtoupper($as_set), Auth::user()->organisation_id, false);
                });

                if($outcome) {
                    Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
                        "ONGOING AS-SET ADD ATTEMPT - COMPLETED\n" .
                        'USER: ' . Auth::user()->email . 
                        "\nAS-SET: $as_set"
                    ));

                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {
                    
                    Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
                        "ONGOING AS-SET ADD ATTEMPT - UNSUCCESSFUL\n" .
                        'USER: ' . Auth::user()->email . 
                        "\nAS-SET: $as_set"
                    ));

                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(\Throwable $e) {
            \Log::debug($e);
        }

        return $response;
    }

    public function approve_as_set(Request $_r) {
        $validation_rules = [
            'token' => ['required', 'string']
        ];

        $response = response()->json(__('api.500_GENERIC'), 500);

        try {
            $token = $_r->input('token');

            if(!Validator::make($_r->all(), $validation_rules)->fails()) {
                $outcome = DB::transaction(function () use($token) {

                    // Check whether there is a valid code for this organisation, and if so, begin reassignment...
                    $valid_code = CodeAsSetClaim::where([
                        ['token', '=', hash('sha256', $token)],
                        ['organisation_id', '=', Auth::user()->organisation_id],
                        ['expiry_at', '>', Carbon::now()]
                    ])->first() ?? false;

                    if($valid_code) {
                        // Get the ASN in question...
                        $as = AsSets::find($valid_code->as_set) ?? false;

                        $valid_code->delete();

                        if($as && $valid_code->save()) {
                            $as->approved = 1;
                            $as->organisation_id = $valid_code->organisation_id;

                            if($as->save()) {
                                return true;
                            }
                        }
                    }

                    return false;
                });

                if($outcome) {
                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {
                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(\Throwable $e) {}

        return $response;
    }

    public function get_asns(Request $_r) {
        return $this->paged_table($_r, function() {
            return Asns::select('id', 'asn', 'approved')->where('organisation_id', '=', Auth::user()->organisation_id);
        }, [ 
            'asn' => [ 'db' => 'asn', 'searchable' => true ]
        ], 'asn', null);
    }

    public function post_asn(Request $_r) {
        $response = response()->json(__('api.500_GENERIC'), 500);

        $validation_rules = [
            'email' => ['required', 'email:rfc,dns', 'not_regex:/@gmail\.com$/i'],
            'asn' => ['required', new AsnFormat],
        ];

        Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
            "NEW ASN ADD ATTEMPT - DETAILS TO COME IF VALID PAYLOAD. INVALID PAYLOADS ARE LOGGED.\n" .
            'USER: ' . Auth::user()->email
        ));

        \Log::debug("ASN ADD ATTEMPT");
        \Log::debug(Auth::user()->email);
        \Log::debug($_r->input());

        try {
            $email = $_r->input('email');
            $asn = $_r->input('asn');

            if(!Validator::make($_r->all(), $validation_rules)->fails()) {

                $asn_year = WhoIs::asn_registration_year('AS' . ltrim($asn, 'ASN'));

                if($asn_year === false || $asn_year <= 2000) {
                    Log::debug("The ASN is too old.");

                    return response()->json('Legacy ASN', 422);
                }
                
                Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
                    "ONGOING ASN ADD ATTEMPT - VALID, VERIFYING\n" .
                    'USER: ' . Auth::user()->email . 
                    "\nEMAIL: $email" . 
                    "\nASN: $asn"
                ));

                $outcome = DB::transaction(function () use($asn, $email) {
                    return Asns::make($asn, $email, null, Auth::user()->organisation_id, false);
                });

                if($outcome) {
                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {

                    Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
                        "ONGOING ASN ADD ATTEMPT - UNSUCCESSFUL\n" .
                        'USER: ' . Auth::user()->email . 
                        "\nEMAIL: $email" . 
                        "\nASN: $asn"
                    ));

                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(\Throwable $e) {}

        return $response;
    }

    public function approve_asn(Request $_r) {
        $validation_rules = [
            'token' => ['required', 'string']
        ];

        $response = response()->json(__('api.500_GENERIC'), 500);

        try {
            $token = $_r->input('token');

            if(!Validator::make($_r->all(), $validation_rules)->fails()) {
                $outcome = DB::transaction(function () use($token) {

                    // Check whether there is a valid code for this organisation, and if so, begin reassignment...
                    $valid_code = CodeAsnClaim::where([
                        ['token', '=', hash('sha256', $token)],
                        ['organisation_id', '=', Auth::user()->organisation_id],
                        ['expiry_at', '>', Carbon::now()]
                    ])->first() ?? false;


                    if($valid_code) {
                        // Get the ASN in question...
                        $asn = Asns::find($valid_code->asn_id) ?? false;

                        $valid_code->delete();

                        if($asn && $valid_code->save()) {

                            $should_clean = $asn->organisation_id != 1 && $asn->organisation_id != $valid_code->organisation_id;
                            
                            $asn->approved = 1;
                            $asn->organisation_id = $valid_code->organisation_id;

                            Notification::route('slack', config('creatia.webhook.standard'))->notify(new SlackNotification(
                                "VALID ASN APPROVAL CODE BEING USED.\n" .
                                'USER: ' . Auth::user()->email . "\n" .
                                "ASN: $asn->asn \n" . 
                                'A <-> B TRANSFER: ' . ($should_clean ? 'Yes' : 'No')
                            ));

                            if($should_clean) {
                                $asn->clean();
                            }

                            if($asn->save()) {
                                return true;
                            }

                        }
                    }

                    return false;
                });

                if($outcome) {
                    $response = response()->json(__('api.200_GENERIC'), 200);                    
                } else {
                    $response = response()->json(__('api.400_INVALID_PAYLOAD'), 400);
                }
            }
        } catch(Throwable $ex) {
            Log::error($ex);
        }

        return $response;
    }

    public function list_asn_emails(Request $_r) {

        try {
            $asn = $_r->input('asn');

            if(!Validator::make($_r->all(), [ 'asn' => ['required', new AsnFormat] ])->fails()) {

                $valid_emails = WhoIs::asn_emails('AS' . ltrim($asn, 'ASN')) ?? [];

                $asn_year = WhoIs::asn_registration_year('AS' . ltrim($asn, 'ASN'));

                if($asn_year !== false && $asn_year > 2000) {
                    return response()->json($valid_emails ? array_values($valid_emails) : [], 200);
                }
                else {
                    return response()->json(['Legacy ASN'], 422);
                }
            }

            return response()->json(__('api.400_INVALID_PAYLOAD'), 400);
        } catch(\Exception $ex) {            
            return response()->json(__('api.500_GENERIC'), 500);
        }
    }

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

        $validator = Validator::make([
            'asn' => $_asn_id
        ], [
            'asn' => ['required', OwnedAsnIdentity::make(true)],
        ]);

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

        $asn = Asns::find($_asn_id)->asn; 

        $peering = Http::withHeaders([
                'Content-Type' => 'application/json'
            ])
            ->get("https://www.peeringdb.com/api/net?asn=$asn");

        if($peering->status() === 200) {

            $data = $peering->json();
            
            if(sizeof($data['data'])) {
                return response()->json(['found' => true, 'v4_max' => $data['data'][0]['info_prefixes4'], 'v6_max' => $data['data'][0]['info_prefixes6']], 200);
            }
        }


        return response()->json(['found' => false, 'v4_max' => null, 'v6_max' => null], 200);
    }
}
