<?php

namespace App\Modules\Network\Commands;

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

use App\Modules\Network\Models\Prefixes;
use App\Modules\Network\Models\PrefixInclusions;
use App\Modules\Network\Models\Supernets;

use App\Models\Asns;

use App\Helpers\Utility\RouteList;

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Associate known prefixes against known and approved ASNs';

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

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $insert_count = $supernet_count = 0;
        
        $known_prefixes = Prefixes::select(DB::raw('concat("address", \'/\', "mask") as cidr'), 'address', 'mask')
            ->whereNotNull('asn_id')
            ->get()
            ->keyBy('cidr');

        Asns::where('approved', '=', '1')->chunk(env('CHUNK_SIZE'), function($asns) use(&$insert_count, &$supernet_count, $known_prefixes) {

            // Iterate over every individual ASN - Keeps things lighter if we do things per ASN
            foreach($asns as $asn) {

                $crushed_prefixes = collect();

                try {
                    // Get crushed versions of all prefixes, then as they're crushed.
                    $crushed_prefixes = collect(RouteList::crush(
                        Prefixes::select(DB::raw('concat("address", \'/\', "mask") as cidr'))
                            ->where([['asn_id', '=', $asn->id], ['created_source', '=', 'prefix-list-uncrushed']])
                            ->get()
                            ->pluck('cidr')
                            ->toArray()
                    ))->pluck('full')->toArray();
                    
                }
                catch(\Exception $ex) {}

                // Only proceed if we've got anything...
                if($known_prefixes->count()) {

                    // Firstly, determine what crushed prefixes no longer seen - Then mark them as no longer seen.
                    Prefixes::where([
                        ['asn_id', '=', $asn->id],
                        ['created_source', '=', 'prefix-list-crushed']
                        ])
                        ->whereNotIn(DB::raw('concat("address", \'/\', "mask")'), $crushed_prefixes)
                        ->update([
                            'still_announced' => 0
                        ]);

                    // Mark prefixes as seen where they exist
                    Prefixes::where([
                        ['asn_id', '=', $asn->id],
                        ['created_source', '=', 'prefix-list-crushed']
                        ])
                        ->whereIn(DB::raw('concat("address", \'/\', "mask")'), $crushed_prefixes)
                        ->update([
                            'still_announced' => 1,
                            'seen_at' => Carbon::now()
                        ]);

                    $to_insert = [];
                    $insertable_cidr = [];

                    foreach($crushed_prefixes as $prefix) {
                        [$address, $width] = explode('/', $prefix);

                        if(!isset($known_prefixes[$prefix])) {
                            ++$insert_count;

                            $to_insert[] = [
                                'created_at' => Carbon::now(),
                                'updated_at' => Carbon::now(),
                                'deleted_at' => null,
                                'name' => '',
                                'address' => $address,
                                'mask' => $width,
                                'seen_at' => Carbon::now(),
                                'still_announced' => 1,
                                'created_source' => 'prefix-list-crushed',
                                'organisation_id' => $asn->organisation_id,
                                'asn_id' => $asn->id,
                            ];

                            $insertable_cidr[] = $prefix;
                        }
                    }

                    if($to_insert !== []) {
                        DB::table('prefixes')->insert($to_insert);
                        
                        // Now we build and place in associations...
                        $new_insertions = Prefixes::where('created_source', '=', 'prefix-list-crushed')
                            ->whereIn(DB::raw('concat("address", \'/\', "mask")'), $insertable_cidr)
                            ->pluck('id');
    
                        $associations_to_insert = [];
    
                        foreach($new_insertions as $id) {
                            ++$supernet_count;

                            $associations_to_insert[] = [
                                'created_at' => Carbon::now(),
                                'updated_at' => Carbon::now(),
                                'tenant_id' => $asn->organisation_id,
                                'supernet_id' => null,
                                'subnet_id' => $id
                            ];
                        }
    
                        DB::table('supernets')->insert($associations_to_insert);
                    }
                }
            }
        });

        echo memory_get_peak_usage()/1024/1024;
        echo "\nWe've inserted: $insert_count prefixes\nAmount of supernet associations: $supernet_count\n";
        
        return 0;
    }
}
