<?php

namespace App\Modules\Network\Controllers;

// Laravel Specifics
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Auth;
use Carbon\Carbon;

// Creatia Core
use App\Models\Asns;
use App\Models\Suborganisation;
use App\Helpers\Utility\RouteList;

// Module Specifics
use App\Modules\Network\Controllers\NetworkController;
use App\Modules\Network\Models\PrefixExclusions;
use App\Modules\Network\Models\PrefixInclusions;
use App\Modules\Network\Models\Prefixes;
use App\Modules\Network\Models\Supernets;
use App\Modules\Network\Models\PrefixSubtenants;

use App\Modules\Network\Helpers\ControllerCommons;
use App\Modules\Network\Models\CommunityList;
use Log;

class AdminNetworkController extends NetworkController
{
    public function get_inclusions(Request $_r, $_asn_id) {
        if(Asns::find($_asn_id)) {        
            return response()->json(PrefixInclusions::select(DB::raw('concat("address", \'/\', "mask") as cidr'))->where('asn_id', '=', $_asn_id)->pluck('cidr') ?? [], 200);
        }

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

    public function get_exclusions(Request $_r, $_asn_id) {
        if(Asns::find($_asn_id)) {        
            return response()->json(PrefixExclusions::select(DB::raw('concat("address", \'/\', "mask") as cidr'))->where('asn_id', '=', $_asn_id)->pluck('cidr') ?? [], 200);
        }

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

    public function put_inclusions(Request $_r, $_asn_id) {
        try {
            if(Asns::find($_asn_id)) {

                $to_insert = [];
    
                foreach($_r->input('addresses') as $addr) {
                    $to_insert[] = [
                        'asn_id' => $_asn_id,
                        'mask' => $addr['mask'],
                        'address' => $addr['address']
                    ];
                }
    
                DB::beginTransaction();
    
                PrefixInclusions::where('asn_id', '=', $_asn_id)->delete();
                PrefixInclusions::insert($to_insert);            
    
                DB::commit();
    
                return response()->json(['detail' => __('api.200_GENERIC')], 200);
            }
        }
        catch(\Exception $ex) {}

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

    public function put_exclusions(Request $_r, $_asn_id) {
        try {
            if(Asns::find($_asn_id)) {

                $to_insert = [];
    
                foreach($_r->input('addresses') as $addr) {
                    $to_insert[] = [
                        'asn_id' => $_asn_id,
                        'mask' => $addr['mask'],
                        'address' => $addr['address']
                    ];
                }
    
                DB::beginTransaction();
    
                PrefixExclusions::where('asn_id', '=', $_asn_id)->delete();
                PrefixExclusions::insert($to_insert);            
    
                DB::commit();
    
                return response()->json(['detail' => __('api.200_GENERIC')], 200);
            }
        }
        catch(\Exception $ex) {}

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

    public function get_prefix_metadata(Request $_r, $_prefix_id) {
        try {
            $network = Prefixes::find($_prefix_id);

            return response()->json([
                'name' => $network->name,
                'created_at' => $network->created_at,
                'address' => "$network->address/$network->mask"
            ], 200);
        }
        catch(\Exception $ex) {}

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

    public function patch_bulk_upsert(Request $_r) {
        /* Expected schema like:
            {
                organisation_id: number, (required)
                prefixes: [
                    {
                        prefix: string, (required)
                        subtenant_prefix_ownership: number[], (optional) (subtenant org id)
                        subtenant_subnet_ownership: number[], (optional) (subtenant org id)
                        subtenant_delete_previous_state: boolean, (optional, assumed no)
                        name: string,(optional)
                    }
                ]
            }
        */

        try {
            $outcome = DB::transaction(function () use( $_r) {
                $organisation_id = $_r->input('organisation_id');

                // Probable parents to reference later
                $potential_parents = Prefixes::select(DB::raw('concat(address, \'/\', mask) as xy'))->where('organisation_id', $organisation_id == 0 ? '!=' : '=', $organisation_id)->get()->pluck('xy');
    
                // Subtenants to insert
                $subtenants_to_insert = [];

                // Keep track of anything the end user should know
                $transaction_statement = [];

                // Go over each prefix...
                foreach($_r->input('prefixes', []) as $prefix) {
                    // First, attempt to locate the prefix - If it's not there, attempt to locate it's parent.
                    $address = $mask = null;

                    // We will need to remember the final ID
                    $final_id = null;

                    // We need to remember the organisation ID we find for the prefix in question for subtenanting.
                    $final_organisation_id = null;
    
                    if(str_contains($prefix['prefix'], '/')) {
                        [$address, $mask] = explode('/', $prefix['prefix']);
                    }
                    else {
                        $address = $prefix['prefix'];
                        $mask = 32;
                    }
    
                    $existing = Prefixes::where([
                        ['address', '=', $address],
                        ['mask', '=', $mask],
                        ['organisation_id', $organisation_id == 0 ? '!=' : '=', $organisation_id]
                    ])->get()->first();

                    $final_organisation_id = $existing->organisation_id ?? null;
    
                    // If it already exists, update that sucker
                    if($existing) {
                        
                        if($prefix['subtenant_delete_previous_state']) {
                            PrefixSubtenants::where('prefix_id', '=', $existing->id)->delete();   
                        }
    
                        if(isset($prefix['name'])) {
                            $existing->name = $prefix['name'];
                        }
    
                        $final_id = $existing->id;
    
                        $existing->save();

                        $transaction_statement["$address/$mask"] = [
                            'prefix' => 'Updated',
                            'invalid_subtenants' => []
                        ];
                    }
                    else { // Otherwise, create it
                        $has_parent = false;

                        foreach($potential_parents as $supernet) {
                            if(RouteList::ip_in_block("$address/$mask", $supernet)) {
                                $has_parent = explode('/', $supernet);
                            }
                        }

                        if($has_parent) {
                            // Fetch the actual parent
                            $parent = Prefixes::where([
                                ['address', '=', $has_parent[0]],
                                ['mask', '=', $has_parent[1]],
                                ['organisation_id', $organisation_id == 0 ? '!=' : '=', $organisation_id]
                            ])->get()->first();

                            $final_organisation_id = $parent->organisation_id;
    
                            $new = Prefixes::create([
                                'name' => $prefix['name'],
                                'address' => $address,
                                'mask' => $mask,
                                'created_source' => 'api',
                                'organisation_id' => $parent->organisation_id,
                                'asn_id' => $parent->asn_id,
                            ]);
    
                            $final_id = $new->id;

                            $transaction_statement["$address/$mask"] = [
                                'prefix' => 'Inserted',
                                'invalid_subtenants' => []
                            ];
                        }
                        else {
                            $transaction_statement["$address/$mask"] = [
                                'prefix' => 'Invalid',
                            ];
                        }
                    }

                    if($final_id != null) {

                        // Get suborganisations and cache ahead
                        $valid_suborganisations = Suborganisation::where('parent_id', '=', $final_organisation_id)->get()->pluck('chiid_id');

                        if(isset($prefix['subtenant_prefix_ownership'])) {
                            foreach($_r->input('subtenant_prefix_ownership', []) as $sub) {
                                if(array_search($sub, $valid_suborganisations)) {
                                    $subtenants_to_insert[] = [
                                        'created_at' => Carbon::now(),
                                        'updated_at' => Carbon::now(),
                                        'prefix_id' => $final_id,
                                        'subtenant_id' => $sub,
                                        'also_subnets' => 0,
                                    ];
                                }
                                else {
                                    $transaction_statement["$address/$mask"]['invalid_subtenants'][] = $sub;
                                }
                            }
                        }
        
                        if(isset($prefix['subtenant_subnet_ownership'])) {
                            foreach($_r->input('subtenant_subnet_ownership', []) as $sub) {
                                if(array_search($sub, $valid_suborganisations)) {
                                    $subtenants_to_insert[] = [
                                        'created_at' => Carbon::now(),
                                        'updated_at' => Carbon::now(),
                                        'prefix_id' => $final_id,
                                        'subtenant_id' => $sub,
                                        'also_subnets' => 1,
                                    ];                                    
                                }
                                else {
                                    $transaction_statement["$address/$mask"]['invalid_subtenants'][] = $sub;
                                }
                            }
                        }
                    }
                }
    
                PrefixSubtenants::insert($subtenants_to_insert);
                return [
                    'message' => 'Success',
                    'statement' => $transaction_statement
                ];
            });
        }
        catch(\Exception $ex) {
            return response()->json(['message' => 'System error'], 500);
        }

        return response()->json($outcome, 200);
    }

    public function get_prefix_options(Request $_r) {
        $prefixes = Prefixes::select(DB::raw("CONCAT(address, '/', mask) as prefix"))->whereNull('deleted_at')->get()->pluck('prefix');

        return response()->json([
            'prefixes' => $prefixes,
        ], 200);
    }

    public function rename_prefix(Request $_r, $_prefix_id) {

        $valid_request = !Validator::make( array_merge([
            'prefix_id' => $_prefix_id,
        ], $_r->all()), [
            'prefix_id'         => [ 'required', Rule::in(Prefixes::all()->pluck('id')) ],
            'name'              => [ 'string' ],
        ])->fails();

        if(!$valid_request) {
            return response()->json([], 400);
        }

        $edit_prefix = Prefixes::find($_prefix_id);

        $edit_prefix->name = $_r->input('name', null);

        $edit_prefix->save();

        return response()->json(['SUCCESS'], 200);
    }

    public function delete_prefix(Request $_r, $_prefix_id) {

        $valid_request = !Validator::make( array_merge([
            'prefix_id' => $_prefix_id,
        ], $_r->all()), [
            'prefix_id' => [
                'required',
                Rule::in(Prefixes::where('id', '=', $_prefix_id)->whereIn('created_source', ['goliath-attack', 'user'])->get()->pluck('id'))
            ]
        ])->fails();

        if(!$valid_request) {
            return response()->json([], 400);
        }

        // Delete prefix and supernet associations..
        Prefixes::where('id', '=', $_prefix_id)->delete();
        Supernets::where('supernet_id', '=', $_prefix_id)->orWhere('subnet_id', '=', $_prefix_id)->delete();

        return response()->json(['SUCCESS'], 200);
    }
}
   