<?php

namespace App\Modules\Network\Controllers;

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

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

// Creatia Default
use App\Models\Organisation;
use App\Models\Suborganisation;
use App\Helpers\Utility\RouteList;
use App\Modules\Network\Controllers\NetworkController;

// 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\Mitigation\MitigationSensitivities;
use App\Modules\Network\Models\Mitigation\MitigationRulesets;
use App\Modules\Network\Models\Mitigation\MitigationOrganisationAttributes;

use App\Modules\Network\Helpers\ControllerCommons;

use Log;

class MitigationController extends NetworkController
{
    public function get_networks(Request $_r, $_network_id = null) {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;
        
        $draw = $_r->input('draw');
        $start = $_r->input('start');
        $length = $_r->input('length');
        $search_term = $_r->input('search_string');
        $order_direction = $_r->input('order_direction');
        $order_column = $_r->input('order_column');
        
        // If user has access to anything.
        if($_network_id == null || Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_network_id)) {
            // Build our query function
            $query_builder = function() use($_network_id, $USER_ORGANISATION_ID) {
                // Get every single relevant prefix
                $prefixes_relevant = Prefixes::getOrganisationPrefixes($USER_ORGANISATION_ID)->pluck('id');

                // Construct our baseline query.
                $prefix_query = Prefixes::select('prefixes.id', 'address', 'mask', 'prefixes.name', 'still_announced', 'in_sync', 'ruleset_ids', DB::raw('mitigation_sensitivities.name as sensitivity'), 'prefixes.organisation_id', 'asn')
                    ->selectRaw('(SELECT COUNT(*) FROM supernets WHERE supernet_id = prefixes.id) AS "subnets"')
                    ->leftJoin('prefix_mitigation', 'prefix_id', '=', 'prefixes.id')
                    ->leftJoin('asns', 'asn_id', '=', 'asns.id')
                    ->leftJoin('mitigation_sensitivities', 'sensitivity_id', '=', 'mitigation_sensitivities.id')
                    //->leftJoin('mitigationRulesets', 'ruleset_id', '=', 'rulesets.id')
                    ->whereIn('prefixes.id', $prefixes_relevant);

                // Thin it out to relevant subnets only if applicable.
                if($_network_id !== null) {
                    $prefix_query = $prefix_query->leftJoin('supernets', 'prefixes.id', '=', 'supernets.subnet_id')->where('supernets.supernet_id', '=', $_network_id);
                }

                return $prefix_query;
            };

            // Build our search function
            $query_search = function($prefix_query, $global, $filters) {

                if($global) {
                    // Look over every row, though ruleset implementation required...
                    $prefix_query = $prefix_query->where(function($query) use($global) {
                        $query->orWhere(DB::raw("CONCAT(address, '/', mask)"), 'LIKE', "%$global%")
                            ->orWhere('asn', 'like', "%$global%")
                            ->orWhere('prefixes.name', 'like', "%$global%");
                     });
                }

                if($filters) {
                    foreach($filters as $filter) {
                        $col = '';
                        switch($filter['id']) {
                            case 'ip': $col = DB::raw("CONCAT(address, '/', mask)"); break;
                            case 'name': $col = "prefixes.name"; break;
                            case 'asn': $col = "asn"; break;
                        }

                        if($col != '') {
                            $prefix_query = $prefix_query->where($col, 'LIKE', '%' . $filter['value'] . '%');
                        }
                    }
                }

                return $prefix_query;
            };

            // Build our sorting function
            $query_sort = function($prefix_query, $column, $direction) {
                switch($column) {
                    case 'ip':
                        $prefix_query = $prefix_query->orderBy('address', $direction)->orderBy('mask');
                        break;
                    case 'name':
                        $prefix_query = $prefix_query->orderBy('name', $direction);
                        break;
                    case 'asn':
                        $prefix_query = $prefix_query->orderBy('asn', $direction);
                        break;
                    default:
                        $prefix_query = $prefix_query->orderBy('address', $direction ?? 'asc')->orderBy('mask');
                }

                return $prefix_query;
            };

            // Create valid sorting keys
            $sorting_keys = ['ip', 'name', 'updated_at', 'asn'];

            // Build our transformation function
            $query_transform = function($data) {
                $rulesets = MitigationRulesets::select('id', DB::raw('"name" as label'))->get()->keyBy('id');

                $data->transform(function($row) use($rulesets) {
                    $ruleset_string = [];

                    if($row->ruleset_ids != null) {
                        foreach(explode(',', $row->ruleset_ids) as $ruleset) {
                            $ruleset_string[] = $rulesets[$ruleset]->label;
                        }
                    }
                    else {
                        $ruleset_string = [ 'No Ruleset' ];
                    }

                    $row->ruleset = implode(', ', $ruleset_string);

                    return $row;
                });

                return $data;
            };

            return NetworkController::pagination_table_request($_r, $query_builder, $query_search, $query_sort, $query_transform, $sorting_keys);
        }

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

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

        try {
            $has_network = Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_network_id);
            $relevant_network = PrefixMitigation::where([['prefix_id', '=', $_network_id],['organisation_id', '=', $USER_ORGANISATION_ID]])->get()->first();

            if($has_network && $relevant_network) {
                $not_allowed_to_set_data = null;

                if($parent_sense = PrefixMitigation::parentSenseSet($_network_id)) {
                    $not_allowed_to_set_data = [[
                        'id' => 0,
                        'label' => 'Inherited from parent: ' . MitigationSensitivities::find($parent_sense)->name
                    ]];
                }
                else if(PrefixMitigation::childSenseSet($_network_id)) {
                    $not_allowed_to_set_data = [[
                        'id' => 0,
                        'label' => 'Subnet has sensitivity set'
                    ]];
                }

                $sensitivities = $not_allowed_to_set_data ?? MitigationSensitivities::select('id', DB::raw('"name" as label'))->get()->toArray();
                $rulesets = MitigationRulesets::select('id', DB::raw('"name" as label'))->get()->toArray();

                $selected_sensitivity = $relevant_network->sensitivity_id ?? 0;
                $selected_ruleset = $relevant_network->ruleset_ids ? array_map('intval', explode(',', $relevant_network->ruleset_ids)) : [];

                return response()->json([
                    'sense' => $sensitivities,
                    'ruleset' => $rulesets,
                    'selectedSense' => $selected_sensitivity,
                    'selectedRuleset' => $selected_ruleset
                ], 200);
            }
        } catch(Exception $ex) {
            Log::debug($ex);
        }

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

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

        try {
            $sense = $_r->input('sensitivity');
            $rules = $_r->input('ruleset');

            $valid_rulesets = (function($rules) {
                foreach($rules as $rule) {
                    if(!MitigationRulesets::find($rule))
                    {
                        return false;
                    }
                }

                return true;
            })($rules);

            $valid_settings = ($sense == '0' || MitigationSensitivities::find($sense)) && ($rules == [] || $valid_rulesets); 

            $network = Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_network_id);
            $announced_supernet = Prefixes::getIsInAnnouncedSupernet($_network_id);

            if($network && $valid_settings && ($network->still_announced || $announced_supernet)) {
                $attrs = MitigationOrganisationAttributes::where('organisation_id', '=', $network->organisation_id)->first() ?? new MitigationOrganisationAttributes([
                    'allowed_subnets' => 10,
                    'allowed_supernets' => 100
                ]);

                $mitigation_details = PrefixMitigation::where('prefix_id', '=', $_network_id)->first();

                $is_subnet = Prefixes::isTrueSubnet($_network_id);
                $parent_sense_set = PrefixMitigation::parentSenseSet($_network_id);
                $child_sense_set = PrefixMitigation::childSenseSet($_network_id);
                $subnet_slots_not_free = $attrs->allowed_subnets <= PrefixMitigation::countSetTrueSubnets($USER_ORGANISATION_ID);
                $supernet_slots_not_free = $attrs->allowed_supernets <= PrefixMitigation::countSetTrueSupernets($USER_ORGANISATION_ID);

                Log::debug($child_sense_set);

                if(($parent_sense_set || $child_sense_set) && $sense != 0) {
                    return response()->json(['detail' => __('api.500_GENERIC')], 500);
                }

                if($is_subnet && $subnet_slots_not_free && $child_sense_set && $sense != 0) {
                    return response()->json(['detail' => __('api.409_UNAVAILABLE_SLOTS')], 409);      
                }
                else if(!$is_subnet && $supernet_slots_not_free && $parent_sense_set && $sense != 0) {
                    return response()->json(['detail' => __('api.409_UNAVAILABLE_SLOTS')], 409);
                }
                else if(($subnet_slots_not_free || $supernet_slots_not_free) && (!(($sense == '0' && count($rules) == '0') || ($mitigation_details->sensitivity_id != null || $mitigation_details->ruleset_ids != null)))) {
                    // If the network they're trying to change already has a rule - Or they're changing it to nothing - If so, let them adjust it. Otherwise don't.
                    return response()->json(['detail' => __('api.409_UNAVAILABLE_SLOTS')], 409);
                }
                
                $mitigation_details->update([
                    'sensitivity_id' => $sense == '0' ? null : $sense,
                    'ruleset_ids' => count($rules) == '0' ? null : implode(',', $rules),
                    'last_source' => 'creatia',
                    'sent' => 0,
                    'in_sync' => 0
                ]);
    
                return response()->json(['detail' => __('api.200_GENERIC')], 200);
            }
        } catch(Exception $ex) {}

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

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

        try {
            $network = Prefixes::getOrganisationHasPrefix($USER_ORGANISATION_ID, $_network_id);

            if($network) {

                $count_small = PrefixMitigation::countSetTrueSubnets($USER_ORGANISATION_ID);
                $count_large = PrefixMitigation::countSetTrueSupernets($USER_ORGANISATION_ID);
    
                $attributes = MitigationOrganisationAttributes::where('organisation_id', '=', Auth::user()->organisation_id)->first() ?? [];
    
                return response()->json([
                    'prefix_name' => $network->name,
                    'prefix_address' => "$network->address/$network->mask",
                    'supernets_used' => $count_large ?? 0,
                    'subnets_used' => $count_small ?? 0,
                    'supernets_pool' => $attributes->allowed_supernets ?? 100,
                    'subnets_pool' => $attributes->allowed_subnets ?? 10,
                ], 200);
            }

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

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

    public function get_metadata() {
        $USER_ORGANISATION_ID = Auth::user()->organisation_id;
        
        try {
            $count_large = PrefixMitigation::countSetTrueSupernets($USER_ORGANISATION_ID);
            $count_small = PrefixMitigation::countSetTrueSubnets($USER_ORGANISATION_ID);

            $attributes = MitigationOrganisationAttributes::where('organisation_id', '=', Auth::user()->organisation_id)->first() ?? [];

            return response()->json([
                'supernets_used' => $count_large ?? 0,
                'subnets_used' => $count_small ?? 0,
                'supernets_pool' => $attributes->allowed_supernets ?? 100,
                'subnets_pool' => $attributes->allowed_subnets ?? 10,
            ], 200);
        }
        catch(Exception $ex) {}

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

    public function post_subnet(Request $_r, $_network_id) {

        return NetworkController::post_prefix($_r, $_network_id, ['input' => [], 'rule' => [] ] , function($new, $parent, $request) {
            PrefixMitigation::create([
                'prefix_id' => $new->id,
                'ruleset_ids' => null,
                'sensitivity_id' => null,
                'synced_at' => null,
                'origin' => null,
                'last_source' => null,
                'organisation_id' => $parent->organisation_id,
                'sent' => null,
                'in_sync' => 1
            ]);
        });

    }
}
