<?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;

use Symfony\Component\Process\Process;

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

// Mitigation
use App\Models\Asns;
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 AdminMitigationController extends NetworkController
{
    public function get_networks(Request $_r) {        
        $draw = $_r->input('draw');
        $start = $_r->input('start');
        $length = $_r->input('length');
        $search_term = $_r->input('search_string');

        // Build our query function
        $query_builder = function() {
            return Prefixes::select('prefixes.id', 'address', 'mask', 'prefixes.name', 'still_announced', 'in_sync', 'organisations.name as organisation', 'ruleset_ids', DB::raw('mitigation_sensitivities.name as sensitivity'), 'prefixes.organisation_id', 'asn')
                ->leftJoin('prefix_mitigation', 'prefix_id', '=', 'prefixes.id')
                ->leftJoin('organisations', 'prefixes.organisation_id', '=', 'organisations.id')
                ->leftJoin('asns', 'asn_id', '=', 'asns.id')
                ->leftJoin('mitigation_sensitivities', 'sensitivity_id', '=', 'mitigation_sensitivities.id');
        };

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

            if($global) {
                $prefix_query = $prefix_query->where(function($query) use ($global){
                    $query->orWhere(DB::raw("CONCAT(address, '/', mask)"), 'LIKE', "%$global%")
                        ->orWhere('mitigation_sensitivities.name', '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 'organisation': $col = "organisation"; 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 'organisation':
                    $prefix_query = $prefix_query->orderBy('organisation', $direction)->orderBy('mask');
                    break;
                default:
                    $prefix_query = $prefix_query->orderBy('address', $direction ?? 'asc')->orderBy('mask');
            }

            return $prefix_query;
        };

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

        // Build our transformation function
        $query_transform = function($data) {
            $data->transform(function($row) {
                return $row;
            });

            return $data;
        };

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

    public function get_mitigation($_network_id) {
        try {
            $relevant_network = PrefixMitigation::where('prefix_id', '=', $_network_id)->get()->first();

            if($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) {}

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

    public function put_mitigation(Request $_r, $_network_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::find($_network_id);
            $announced_supernet = Prefixes::getIsInAnnouncedSupernet($_network_id);

            if($network && $valid_settings && ($network->still_announced || $announced_supernet)) {

                $parent_sense_set = PrefixMitigation::parentSenseSet($_network_id);
                $child_sense_set = PrefixMitigation::childSenseSet($_network_id);

                Log::debug($child_sense_set);

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

                PrefixMitigation::where('prefix_id', '=', $_network_id)->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_metadata($_organisation_id) {        
        try {
            $count_large = PrefixMitigation::countSetTrueSupernets($_organisation_id);
            $count_small = PrefixMitigation::countSetTrueSubnets($_organisation_id);

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

            return response()->json([
                'allowed_supernets' => $attributes->allowed_supernets ?? 100,
                'allowed_subnets' => $attributes->allowed_subnets ?? 10,
                'ssp_id' => $attributes->ssp_id
            ], 200);
        }
        catch(Exception $ex) {}

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

    public function put_metadata(Request $_r, $_organisation_id) {
        try {
            $valid_request = !Validator::make([
                'supernets'     => $_r->input('supernets'), // < 24s
                'subnets'       => $_r->input('subnets'),   // 32s
                'organisation'  => $_organisation_id
                ], [
                'supernets'     => ['required', 'integer', 'min:0'],
                'subnets'       => ['required', 'integer', 'min:0'],
                'organisation'  => ['required', 'exists:organisations,id']
            ])->fails();
    
            if($valid_request) {
                $updated = MitigationOrganisationAttributes::where('organisation_id', '=', $_organisation_id)->update([
                    'organisation_id'   => $_organisation_id,
                    'allowed_supernets' => $_r->input('supernets'),
                    'allowed_subnets'   => $_r->input('subnets'),
                    'ssp_id'            => $_r->input('ssp_id')
                ]);

                if(!$updated) {
                    $attributes = new MitigationOrganisationAttributes([
                        'organisation_id'   => $_organisation_id,
                        'allowed_supernets' => $_r->input('supernets'),
                        'allowed_subnets'   => $_r->input('subnets'),
                        'ssp_id'            => $_r->input('ssp_id')
                    ]);

                    if($attributes->save()) {
                        return response()->json(['detail' => __('api.200_GENERIC')], 200);
                    }
                }
                else {
                    return response()->json(['detail' => __('api.200_GENERIC')], 200);
                }

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

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

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

    public function get_corero_csvs_zipped(Request $_r) {

        // Get profiles to slap in...
        $sensitivities = MitigationSensitivities::select('id', 'system_identifier')->get()->keyBy('id');
        $rulesets = MitigationRulesets::select('id', 'system_identifier')->get()->keyBy('id');
        $asns = Asns::select('id', 'asn')->get()->keyBy('id');

        $network_descriptors = [];
        $groupings = [];

        PrefixMitigation::leftJoin('prefixes', 'prefixes.id', '=', 'prefix_id')->whereRaw('(sensitivity_id is not null OR ruleset_ids is not null)')
            ->chunk(env('CHUNK_SIZE'), function($network_chunk) use($sensitivities, $rulesets, $asns, &$groupings, &$network_descriptors) {
                foreach($network_chunk as $network) {
                    $prefix_for_network = "$network->address/$network->mask";

                    // In theory there could be two, which is foul.
                    if(isset($network_descriptors[$prefix_for_network])) {
                        $network_descriptors[$network->address] .= ' & ' . $asns[$network->asn_id];
                    }
                    else {
                        $network_descriptors[$network->address] = $asns[$network->asn_id];
                    }

                    if($sensitivities[$network->sensitivity_id]->system_identifier ?? false) {
                        $groupings[$sensitivities[$network->sensitivity_id]->system_identifier][] = $prefix_for_network;
                    }

                    foreach(explode(',', $network->ruleset_ids) as $x) {
                        if($rulesets[$x]->system_identifier ?? false) {
                            $groupings[$rulesets[$x]->system_identifier][] = $prefix_for_network;
                        }    
                    }
                }
            }
        );

        // Begin making our zip file...
        $headers = ["Content-Type"=>"application/zip"];

        $zipfile_name = 'corero.zip'; // name of zip

        $zipfile_fh = new \ZipArchive();

        $zipfile_fh->open(storage_path($zipfile_name), \ZipArchive::CREATE | \ZipArchive::OVERWRITE);

        foreach($groupings as $category_key => $category_value) {
            // Begin making CSV
            $file_name = storage_path($category_key . '.csv');
            $fh = fopen($file_name, 'w');

            // Crush the category
            $crusher_process = new Process(['python3', '../app/Bin/SubnetCrusher.py']);
            $crusher_process->setInput(implode(' ', $category_value));
            $crusher_process->run();

            // Process and produce the CSV array.
            $file_contents = [];
                    
            if($crusher_process->isSuccessful()) {
                // Get and process the output into an array.
                $prefixes = [];
                preg_match_all('/((?:(?:(?:[A-Fa-f0-9]{1,4}:{1,2}){0,8})|(?:(?:\d{1,3}\.?)){4})\/\d{1,3})/', $crusher_process->getOutput(), $prefixes);
                    
                // Output the final crush list.
                foreach($prefixes[1] as $network) {
                    $file_contents[] = "\"$network\",\"".'AS'.($network_descriptors[explode('/', $network)[0]]->asn ?? '-MATCH-FAILURE')."\"";
                }
            }

            Log::debug($file_contents);
            fwrite($fh, implode(",\r\n",$file_contents));
            fclose($fh);

            $zipfile_fh->addFile($file_name, "$category_key.csv");
        }

        $zipfile_fh->close();

        return response()->download(storage_path($zipfile_name));
    }
}
