<?php

namespace App\Modules\Network\Commands;

use Log;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;

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

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Pulls rulesets from the mitigation system.';

    /**
     * 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'));

            // Worst case complexity of the below is ceiling(numOfSensitivities / chunkSize) * ceiling(numOfPrefixes / chunkSize)^2 - Not great but realistically laid out to avoid memory limits being exceeded.
            // We need to iterate over EVERY sensitivity stored in our database. Probably won't ever need to chunk, but never say never.
            $network_rulesets = [];

            MitigationRulesets::chunk(env('CHUNK_SIZE'), function($ruleset_chunk) use($corero, &$network_rulesets) {
                // Cache up rulesets for each network
                
                // Grab prefixes for this sensitivity.
                foreach($ruleset_chunk as $ruleset) {
                    $unchunked_prefixes = json_decode($corero->get_address_group_list($ruleset->system_identifier)->body())->items;

                    // Split the values into chunks so we don't end up with 500+ values being inserted into the database at any given time - Can handle higher but lets keep it reasonable.
                    $chunked_prefixes = array_chunk($unchunked_prefixes, env('CHUNK_SIZE'));
    
                    // Iterate over each chunk and it's corresponding prefixes.
                    foreach($chunked_prefixes as $prefixes) {
                        $networks_to_fetch = [];
    
                        foreach($prefixes as $prefix) {
                            $address = !str_contains($prefix->address, '/') ? "$prefix->address/32" : "$prefix->address";
                            
                            if(isset($network_rulesets[$address])) {
                                $network_rulesets[$address][] = $ruleset->id;
                            }
                            else {
                                $network_rulesets[$address] = [ $ruleset->id ];
                            }

                        }
                    }
                }
            });

            $prefix_mitigation_lookup = PrefixMitigation::leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')->select(DB::raw("CONCAT(address,'/',mask) as cidr"), 'ruleset_ids')->get()->keyBy('cidr');

            foreach($network_rulesets as $address => $rulesets) {
                // We only want to actually execute a query IF there's been changes. If we didn't, we'd be wastefully waiting for updates.
                if(!isset($prefix_mitigation_lookup[$address]) || (isset($prefix_mitigation_lookup[$address]) && $prefix_mitigation_lookup[$address] != $rulesets)) {
                    Log::debug($address . ": " . implode(',', $rulesets));
                    
                    PrefixMitigation::leftJoin('prefixes', 'prefix_id', '=', 'prefixes.id')->where(DB::raw("CONCAT(address,'/',mask)"), '=', $address)
                        ->whereNull('prefixes.deleted_at')
                        ->whereRaw('(in_sync = 1)')
                        ->whereRaw('(synced_at >= prefix_mitigation.updated_at OR synced_at is null)')
                        ->whereRaw('(sent is null OR sent = 1)')
                        ->update([
                            'ruleset_ids' => implode(',', $rulesets),
                            'sent' => null,
                            'synced_at' => Carbon::now()
                        ]);
                }
            }
        }
        catch(Exception $ex) {

        }
        
        return 0;
    }
}