
<?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\Models\Asns;

use App\Helpers\Utility\RouteList;

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

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Import prefixes that are associated with 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()
    {
        Asns::where('approved', '=', '1')->chunk(env('CHUNK_SIZE'), function($asns) {

            // Get all current prefixes, then create a lookup object
            $current_prefixes_collection = Prefixes::select('id', 'organisation_id', 'address', 'mask', DB::raw('concat("address", \'/\', "mask") as cidr'))->whereIn('organisation_id', $asns->pluck('organisation_id'))->get();
            $current_prefixes_lookup = $current_prefixes_collection->keyBy('cidr');

            // Iterate over every individual ASN - Keeps things lighter if we do things per ASN

            foreach($asns as $asn) {

                $uncrushed = collect();
                $crushed = collect();
                try {
                    // Get prefixes as they stand, then as they're crushed.
                    $uncrushed = collect(array_merge(RouteList::get_routes_url("http://100.127.242.223/$asn->asn.active.txt", false), RouteList::get_routes_url("http://100.127.242.223/$asn->asn.v6.active.txt", false)));
                    $crushed = collect(array_merge(RouteList::get_routes_url("http://100.127.242.223/$asn->asn.active.txt", true), RouteList::get_routes_url("http://100.127.242.223/$asn->asn.v6.active.txt", true)));
                }
                catch(\Exception $ex) {}

                // Inclusions
                $inclusions = PrefixInclusions::where('asn_id', '=', $asn->id)->select(DB::raw("CONCAT(address,'/',mask) as full"), 'address as ip', 'mask as width')->get();

                // Only proceed if we've got anything...
                if(!$uncrushed->isEmpty() || !$crushed->isEmpty() || !$inclusions->isEmpty()) {
                    // Remove duplicate values - A product of crushing. Then collect it.
                    $prefixes = $uncrushed->merge($crushed)->merge($inclusions)->unique();

			\Log::debug("INSERTION TIME");

                    // Create some lookups
                    $uncrushed_lookup = $uncrushed->merge($inclusions)->keyBy('full');

                    // Keep track of what we're going to want to insert.
                    $ssp_to_insert = [];

                    foreach($prefixes->chunk(env('CHUNK_SIZE')) as $prefix_chunk) {
                        $chunk_to_insert = [];
                        $chunk_recently_seen = [];

                        $insertable_cidrs = [];

                        foreach($prefix_chunk as $individual_prefix) {
                            // Sanitise the address so any weird notation is stripped away
                            $sanitised_address = inet_ntop(inet_pton($individual_prefix['ip'])) . '/' . $individual_prefix['width'];
                            // Add in any new prefixes...
                            if(!isset($current_prefixes_lookup[$sanitised_address])) {
                                $source = isset($uncrushed_lookup[$sanitised_address]) ? 'prefix-list-uncrushed' : 'prefix-list-crushed';

                                $chunk_to_insert[] = [
                                    'created_at' => Carbon::now(),
                                    'updated_at' => Carbon::now(),
                                    'deleted_at' => null,
                                    'name' => '',
                                    'address' => $individual_prefix['ip'],
                                    'mask' => $individual_prefix['width'],
                                    'seen_at' => Carbon::now(),
                                    'still_announced' => 1,
                                    'created_source' => $source,
                                    'organisation_id' => $asn->organisation_id,
                                    'asn_id' => $asn->id,
                                ];

                                $insertable_cidrs[] = $sanitised_address;

                            }
                            else {
                                $chunk_recently_seen[] = $current_prefixes_lookup[$sanitised_address]->id;
                            }
                        }

                        // Add in networks that need to be inserted.
                        if($chunk_to_insert != []) {
                            DB::table('prefixes')->insert($chunk_to_insert);
                            
                            // Now we build and place in associations...
                            $new_prefixes = Prefixes::whereIn(DB::raw('concat("address", \'/\', "mask")'), $insertable_cidrs)->pluck('id');

                            $chunk_prefix_associations = [];

                            foreach($new_prefixes as $id) {
                                $chunk_prefix_associations[] = [
                                    'created_at' => Carbon::now(),
                                    'updated_at' => Carbon::now(),
                                    'tenant_id' => $asn->organisation_id,
                                    'supernet_id' => null,
                                    'subnet_id' => $id
                                ];
                            }

                            DB::table('supernets')->insert($chunk_prefix_associations);
                        }

                        // Mark networks as recently seen if they have been.
                        if($chunk_recently_seen != []) {
                            Prefixes::whereIn('id', $chunk_recently_seen)->update([
                                'seen_at' => Carbon::now(),
                                'still_announced' => 1
                            ]);
                        }
                    }

                    // Determine what we're going to delete in the future - AKA Mark it for cleanup later on.
                    $to_delete = $current_prefixes_collection->whereNotIn('prefix', $prefixes->pluck('cidr'))->pluck('id');

                    Prefixes::leftJoin('supernets', 'subnet_id', '=', 'prefixes.id')->whereNull('supernet_id')->whereIn('prefixes.id', $to_delete)->where('asn_id', '=', $asn->id)->update(['still_announced' => 0]);
                }
            }
        });
        
        return 0;
    }
}
