<?php

namespace App\Console\Commands\Network;

use App\Console\Commands\BaseCommand;
use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
use Log;

use App\Models\Prefixes;
use App\Models\PrefixInclusions;

use App\Helpers\Utility\UtilPrefix;

class PrefixImport extends BaseCommand
{

    protected $signature = 'prefix:import';

    protected $description = 'Import any prefixes that are not associated with known and approved ASNs';

    public function handle()
    {

        $all_prefixes = collect(
            array_merge(
                PrefixInclusions::select(DB::raw("CONCAT(address,'/',mask) as full"), 'address as ip', 'mask as width')->get()->toArray(),
                (UtilPrefix::get_detailed())->map(function($i) { return [ 'full' => $i->prefix ]; })->toArray(),
                (UtilPrefix::get_detailed_v6())->map(function($i) { return [ 'full' => $i->prefix ]; })->toArray()
            )
        )->pluck('full')->unique();

        // Reserve Carbon object...
        $now = Carbon::now();

        foreach($all_prefixes->chunk(config('creatia.constant.chunk_large')) as $prefix_chunk) {

            $to_insert = [];
            $cidr_insertion_list = [];
            $recently_seen = [];

            // Track prefixes to be aware of for this chunk
            $known_prefixes = Prefixes::select('id', DB::raw('concat("address", \'/\', "mask") as cidr'))
                ->whereIn(DB::raw('concat("address", \'/\', "mask")'), $prefix_chunk)
                ->get()
                ->keyBy('cidr');

            foreach($prefix_chunk as $prefix) {
                
                // Check whether we've seen this address before - We do not need to sanitise here as it is either in the list or it is not.
                if(isset($known_prefixes[$prefix])) {
                    $recently_seen[] = $known_prefixes[$prefix]->id;
                }
                else {

                    // Break up address for effective sanitisation.
                    [$address, $width] = explode('/', $prefix);

                    if(inet_pton($address) === false) {
                        \Log::warning("Invalid address detected - $address / $width");
                        continue;
                    }

                    if(str_contains($address, ':') && ($width < 1 || $width > 128)) {
                        \Log::warning("Invalid width detected - $address / $width");
                        continue;
                    }
                    elseif(!str_contains($address, ':') && ($width < 1 || $width > 32)) {
                        \Log::warning("Invalid width detected - $address / $width");
                        continue;
                    }

                    if($address == '0.0.0.0' || $address == '::') {
                        \Log::warning("Dangerous address detected - $address / $width");
                        continue;
                    }

                    $sanitised_address = inet_ntop(inet_pton($address)) . '/' . $width;

                    // Check whether this address will work in postgres as a final sanity measure.
                    try {
                        DB::beginTransaction();
                        DB::select("SELECT ?::cidr", [$sanitised_address]);
                        DB::commit();
                    }
                    catch (\Exception $e) {
                        DB::rollback();
                        \Log::warning("Bad address detected - $address / $width");
                        continue;
                    }

                    \Log::warning("New insert - $sanitised_address");

                    $to_insert[] = [
                        'created_at' => $now,
                        'updated_at' => $now,
                        'deleted_at' => null,
                        'name' => '',
                        'address' => $address,
                        'mask' => $width,
                        'seen_at' => $now,
                        'still_announced' => 1,
                        'created_source' => 'prefix-list-uncrushed',
                        'organisation_id' => 1,
                        'asn_id' => null,
                    ];

                    $cidr_insertion_list[] = $sanitised_address;
                }
            }

            // Add in networks that need to be inserted.
            if($to_insert !== []) {
                try {
                    foreach(array_chunk($to_insert, config('creatia.constant.chunk_small')) as $insertion_chunk) {
                        DB::table('prefixes')->insert($insertion_chunk);
                    }
                    
                    // Now we build and place in associations...
                    $new_insertions = Prefixes::whereIn(DB::raw('concat("address", \'/\', "mask")'), $cidr_insertion_list)->select('id')->pluck('id');

                    $associations_to_insert = [];

                    foreach($new_insertions as $id) {

                        $associations_to_insert[] = [
                            'created_at' => $now,
                            'updated_at' => $now,
                            'tenant_id' => 1,
                            'supernet_id' => null,
                            'subnet_id' => $id
                        ];
                    }


                    foreach(array_chunk($associations_to_insert, config('creatia.constant.chunk_small')) as $insertion_chunk) {
                        DB::table('supernets')->insert($insertion_chunk);
                    }
                }
                catch(\Exception $ex) {
                    \Log::debug($ex);
                }
            }

            // Mark prefixes as recently seen
            if($recently_seen != []) {
                Prefixes::whereIn('id', $recently_seen)->where('created_source', '!=', 'special')->update([
                    'seen_at' => $now,
                    'created_source' => 'prefix-list-uncrushed'
                ]);
            }

        };

        return 0;
    }
}
