<?php

namespace App\Modules\Network\Controllers;

// Laravel
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

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

use Carbon\Carbon;

// Creatia Default
use App\Models\Organisation;
use App\Models\Suborganisation;
use App\Http\Controllers\BaseController;
use App\Helpers\Utility\RouteList;

// Mitigation
use App\Modules\Network\Models\Prefixes;
use App\Modules\Network\Models\PrefixSubtenants;
use App\Modules\Network\Models\Mitigation\PrefixMitigation;
use App\Modules\Network\Models\GeoIp\PrefixGeoIp;
use App\Modules\Network\Models\Supernets;

use App\Modules\Network\Helpers\ControllerCommons;

use Log;

class NetworkController extends BaseController
{

    public function get_subtenants($_network_id) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;

        // Wrap the entire thing in a try-catch to gracefully die.
        try {
            if(Prefixes::validOwner($_network_id, $USER_ORGANISATION_ID)) {
                // Get organisational subtenants and cast to an array.
                $options = Organisation::select(DB::raw('organisations.id as value'), DB::raw('name as label'))->leftJoin('suborganisations', 'child_id', '=', 'organisations.id')->where('suborganisations.parent_id', '=', $USER_ORGANISATION_ID)->get();
                
                // Get every subtenant for the resource
                $prefixOnly = PrefixSubtenants::leftJoin('organisations', 'subtenant_id', '=', 'organisations.id')->select('organisations.name as label', 'organisations.id as value', 'also_subnets')->where([['prefix_id', '=', $_network_id], ['also_subnets', '=', 0]])->get();
                $childrenAllowed = PrefixSubtenants::leftJoin('organisations', 'subtenant_id', '=', 'organisations.id')->select('organisations.name as label', 'organisations.id as value', 'also_subnets')->where([['prefix_id', '=', $_network_id], ['also_subnets', '=', 1]])->get();
                
                // Return it.
                return response()->json([
                    'data' => $options,
                    'selectedPrefixOnly' => $prefixOnly->toArray(),
                    'selectedChildrenAllowed' => $childrenAllowed->toArray()
                ], 200);
            }

        } catch(Exception $ex) {}

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

    public function put_subtenant(Request $_r, $_network_id) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;

        // Wrap the entire thing in a try-catch to gracefully die.
        try {
            
            // Get child organisations as options then cast to an array.
            $options = Suborganisation::where('parent_id', '=', $USER_ORGANISATION_ID)->get()->pluck('child_id')->toArray();

            // Validate inputs
            $valid_request = !Validator::make([
                    'selectedPrefixOnly' => $_r->input('selectedPrefixOnly'),
                    'selectedChildrenAllowed' => $_r->input('selectedChildrenAllowed')
                ], [
                    'selectedPrefixOnly' => ['present', 'array'],
                    'selectedPrefixOnly.*' => [Rule::in(array_merge($options, [0]))],
                    'selectedChildrenAllowed' => ['present', 'array'],
                    'selectedChildrenAllowed.*' => [Rule::in(array_merge($options, [0]))]
            ])->fails();

            // If the request has a tenant that is valid, or is setting the tenant to zero.
            if($valid_request) {
                // Check if the requested network exists with the owner in mind, and whether the network is still announced OR it's just an inner.
                $requested_network = Prefixes::validOwner($_network_id, $USER_ORGANISATION_ID);
                $is_announced = $requested_network->still_announced == 1;
                $is_inner = Supernets::where('subnet_id', '=', $_network_id)->whereNotNull('supernet_id')->count();

                if($requested_network && ($is_announced || $is_inner)) {
                    // Begin transaction so we can keep this atomic
                    DB::beginTransaction();

                    // Erase subtenants so we can add in more
                    PrefixSubtenants::where('prefix_id', '=', $_network_id)->delete();

                    $new_subtenants = [];

                    foreach($_r->input('selectedPrefixOnly') as $x) {
                        $new_subtenants[] = [
                            'prefix_id' => $_network_id,
                            'subtenant_id' => $x,
                            'also_subnets' => 0,
                        ];
                    }

                    foreach($_r->input('selectedChildrenAllowed') as $x) {
                        $new_subtenants[] = [
                            'prefix_id' => $_network_id,
                            'subtenant_id' => $x,
                            'also_subnets' => 1,
                        ];
                    }

                    if($new_subtenants != []) {
                        DB::table('prefix_subtenants')->insert($new_subtenants);
                    }

                    // Commit transaction now that we're done.
                    DB::commit();

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

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

            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        } catch(Exception $ex) {
            DB::rollback();
        }

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

    public function get_inside_subnet(Request $_r, $_network_id) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;

        try {
            $valid_request = !Validator::make([ 'cidr' => $_r->input('cidr') ], ['cidr' => ['required', 'regex:/((?:(?:\d{1,3}\.?)){4}\/\d{1,2})/']])->fails();
            
            if($valid_request) {
                if($valid_network = Prefixes::validOwner($_network_id, $USER_ORGANISATION_ID)) {
                    $network_cidr = $valid_network->getCidr();
        
                    $valid_subnet = RouteList::valid_subnet($network_cidr, $_r->input('cidr'));
                    $inside_network_subnet = RouteList::ip_in_block($_r->input('cidr'), $network_cidr);

                    if($valid_subnet && $inside_network_subnet && !RouteList::ip_in_blocks($_r->input('cidr'), RouteList::get_excluded_blocks())) {
                        return response()->json(['detail' => __('api.200_GENERIC')], 200);
                    }

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

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

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

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

    public function post_prefix(Request $_r, $_network_id, $_validation_shim, $_creation_shim) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;
        
        try {
            $valid_request = !Validator::make(array_merge([
                'cidr' => $_r->input('cidr'),
                'name' => $_r->input('name'),
            ], $_validation_shim['input']), array_merge([
                'cidr' => ['required', 'regex:/((?:(?:\d{1,3}\.?)){4}\/\d{1,2})/'],
                'name' => ['string', 'nullable']
            ], $_validation_shim['rule']))->fails();
            
            if($valid_request) {
                $parent_network = Prefixes::find($_network_id);

                $is_rightful_editor = Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_network_id); 
                $is_parent_announced = Prefixes::getIsInAnnouncedSupernet($_network_id);

                Log::debug("$is_parent_announced");

                if($is_rightful_editor && $is_parent_announced) {
                    $network_cidr = $parent_network->getCidr();
        
                    $valid_subnet = RouteList::valid_subnet($network_cidr, $_r->input('cidr'));
                    $inside_network_subnet = RouteList::ip_in_block($_r->input('cidr'), $network_cidr);

                    // Create the network and attach it to the parent subnet.
                    if($valid_subnet && $inside_network_subnet && !RouteList::ip_in_blocks($_r->input('cidr'), RouteList::get_excluded_blocks())) {
                        [$prefix, $bits] = explode('/', $_r->input('cidr'));

                        DB::beginTransaction();

                        $new_network = Prefixes::create([
                            'name' => $_r->input('name'),
                            'address' => $prefix,
                            'mask' => $bits,
                            'created_source' => 'user',
                            'organisation_id' => $parent_network->organisation_id,
                            'asn_id' => $parent_network->asn_id,
                        ]);

                        if($_creation_shim != null) {
                            $_creation_shim($new_network, $parent_network, $_r);
                        }

                        DB::table('supernets')->insert([[
                                'created_at' => Carbon::now(),
                                'updated_at' => Carbon::now(),
                                'tenant_id' => $parent_network->organisation_id,
                                'supernet_id' => $_network_id,
                                'subnet_id' => $new_network->id
                            ], [
                                'created_at' => Carbon::now(),
                                'updated_at' => Carbon::now(),
                                'tenant_id' => $parent_network->organisation_id,
                                'supernet_id' => null,
                                'subnet_id' => $new_network->id
                            ]
                        ]);

                        DB::commit();

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

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

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

            return response()->json(['detail' => __('api.400_INVALID_PAYLOAD')], 400);
        } catch(Exception $ex) {
            DB::rollback();
        }

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

}
