<?php

namespace App\Modules\Network\Commands;

use Log;

use App\Modules\Network\Models\Prefixes;
use App\Modules\Network\Models\Mitigation\PrefixMitigation;
use App\Modules\Network\Models\Mitigation\MitigationRulesets;
use App\Modules\Network\Models\Mitigation\MitigationSensitivities;
use App\Models\Organisation;
use App\Modules\Network\Helpers\Corero;

class MitigationSync extends \App\Console\Commands\BaseCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'mitigation:sync';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Checks whether prefixes are in sync with the mitigation system, and if so - Mark any sent ones as actioned. Otherwise throw up a notification.';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        try {            
            $corero = new Corero(env('CORERO_ENDPOINT'), env('CORERO_API_VERSION'), env('CORERO_USERNAME'), env('CORERO_PASSWORD'));

            // Create lookup objects for ease of computation down the road.

            $ruleset_lookup = $sensitivity_lookup = [];

            MitigationRulesets::chunk(env('CHUNK_SIZE'), function($ruleset_chunk) use($corero, &$ruleset_lookup) {
                foreach($ruleset_chunk as $ruleset) {
                    $raw_rulesets = json_decode($corero->get_address_group_list($ruleset->system_identifier)->body())->items;
    
                    foreach($raw_rulesets as $prefix) {
                        if(isset($ruleset_lookup[!str_contains($prefix->address, '/') ? $prefix->address . '/32' : $prefix->address])) {
                            $ruleset_lookup[!str_contains($prefix->address, '/') ? $prefix->address . '/32' : $prefix->address][] = $ruleset->id;
                        }
                        else {
                            $ruleset_lookup[!str_contains($prefix->address, '/') ? $prefix->address . '/32' : $prefix->address] = [ $ruleset->id ];
                        }
                        
                    }
                }
            });

            MitigationSensitivities::chunk(env('CHUNK_SIZE'), function($sensitivity_chunk) use($corero, &$sensitivity_lookup) {
                foreach($sensitivity_chunk as $sensitivity) {
                    $raw_sensitivities = json_decode($corero->get_address_group_list($sensitivity->system_identifier)->body())->items;
    
                    foreach($raw_sensitivities as $prefix) {
                        $sensitivity_lookup[!str_contains($prefix->address, '/') ? $prefix->address . '/32' : $prefix->address] = $sensitivity->id;
                    }
                }
            });

            // Determine what requires action and what does not.

            $requires_action = $in_sync_ids = [];

            $sensitivities = MitigationSensitivities::select('id', 'system_identifier', 'name')->get()->keyBy('id');
            $rulesets = MitigationRulesets::select('id', 'system_identifier', 'name')->get()->keyBy('id');
            $organisations = Organisation::select('id', 'name')->get()->keyBy('id');

            Prefixes::withTrashed()->leftJoin('prefix_mitigation', 'prefix_id', '=', 'prefixes.id')->chunk(env('CHUNK_SIZE'), function($networks) use($sensitivities, $rulesets, $organisations, $ruleset_lookup, $sensitivity_lookup, &$in_sync_ids, &$requires_action) {
                foreach($networks as $network) {
                    $lookup_key = $network->address . '/' . $network->mask;

                    $in_sync = true;

                    if($network->deleted_at != null) {
                        // Notify that a profile is in Corero but not Creatia.
                        if(isset($ruleset_lookup[$lookup_key])) {
                            $requires_action[$organisations[$network->organisation_id]->name][$lookup_key][] = '*Prefix deleted in Creatia but the ruleset is still in Corero.';
                            $in_sync = false;
                        }

                        if(isset($sensitivity_lookup[$lookup_key])) {
                            $requires_action[$organisations[$network->organisation_id]->name][$lookup_key][] = '*Prefix deleted in Creatia but the sensitivity still in Corero.';
                            $in_sync = false;
                        }
                    }
                    else {
                        // Need to account for the fact there can be multiple rulesets (REEEEEEE)

                        // Check whether there's any ruleset in the first place...
                        if(empty($network->ruleset_ids) && isset($ruleset_lookup[$lookup_key])) {
                            $requires_action[$organisations[$network->organisation_id]->name][$lookup_key][] = '*Ruleset out of sync, according to Creatia should consist of: NOTHING';

                            $in_sync = false;
                        }
                        else {
                            $network_rulesets = explode(',', $network->ruleset_ids); 

                            $mismatched_rulesets = false;

                            // We should check if the rulesets we hold matches what Corero holds...
                            if((isset($ruleset_lookup[$lookup_key]) && !(count($network_rulesets) == count($ruleset_lookup[$lookup_key]) && array_diff($network_rulesets, $ruleset_lookup[$lookup_key]) === array_diff($ruleset_lookup[$lookup_key], $network_rulesets))) || !empty($network->ruleset_id)) {
                                $mismatched_rulesets = true;
                            }

                            if($mismatched_rulesets) {
                                $requires_action[$organisations[$network->organisation_id]->name][$lookup_key][] = '*Ruleset out of sync, according to Creatia should consist of: ' . implode(', ', $rulesets->whereIn('id', $network_rulesets)->pluck('system_identifier')->toArray());
                                $in_sync = false;
                            }
                        }
                    
                        // Check whether sensitivity matches what has been requested.
                        if((isset($sensitivity_lookup[$lookup_key]) && $network->sensitivity_id != $sensitivity_lookup[$lookup_key]) || (!isset($sensitivity_lookup[$lookup_key]) && !empty($network->sensitivity_id)) ) {
                            $requires_action[$organisations[$network->organisation_id]->name][$lookup_key][] = '*Sensitivity out of sync, according to Creatia should be: '
                                . (empty($network->sensitivity_id) ? 'NOTHING' : $sensitivities[$network->sensitivity_id]->system_identifier);
                            $in_sync = false;
                        }
                    }

                    if($in_sync) {
                        $in_sync_ids[] = $network->id;
                    }
                }
            });

            // Explicit unsets since these guys can be huge. Probably will do nothing however.
            unset($ruleset_lookup);
            unset($sensitivity_lookup);
            unset($organisations);
            unset($sensitivities);
            unset($rulesets);

            PrefixMitigation::leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')->whereNull('prefixes.deleted_at')->whereIn('prefixes.id', $in_sync_ids)->update(['in_sync' => '1', 'sent' => '0']);

            $notification = "*===========================================*\n*CORERO FOLLOW-UP AND SYNCHRONICITY REPORT*\n*===========================================*";

            foreach($requires_action as $organisation_key => $organisation_value) {
                $notification .= "\n*Organisation: $organisation_key*";
                
                foreach($organisation_value as $prefix_key => $prefix_value) {
                    $notification .= "\n\n*$prefix_key:*\n";

                    foreach($prefix_value as $point) {
                        $notification .= "\n$point";
                    }
                }
            }

            if(count($requires_action) != 0) {
                $this::notify($notification);
            }
        }
        catch(Exception $ex) {

        }
        
        return 0;
    }
}