<?php

namespace App\Modules\Network\Commands;

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

use App\Modules\Network\Models\Prefixes;

use App\Modules\Network\Models\PrefixCommunity;

class PrefixCommunitiesThread extends \App\Console\Commands\BaseCommand
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'prefix:communities-thread {--as-max=} {--as-mod=}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Thread for prefix-communitry association.';

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

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        // Build up community dictionary
        $prefix_community_dictionary = [];

        foreach(collect(Http::get("http://54.215.89.235/all.active.detailed.json")->json()) as $announced_prefix) {
            $prefix_community_dictionary[$announced_prefix['prefix']] = $announced_prefix['communities'];
        }

        if(sizeof($prefix_community_dictionary) === 0) {
            return 0;
        }

        $operation_time = Carbon::now();

        // We need to divide the chunk size by two to try to keep things efficient in this scenario, as it's feasible most prefixes will have more communities - We'll keep our size smaller this way.
        Prefixes::whereIn(DB::raw('concat("address", \'/\', "mask")'), array_keys($prefix_community_dictionary))
            ->where(DB::raw('asn_id % ' . $this->option('as-max')), '=', $this->option('as-mod'))
            ->chunk(env('CHUNK_SIZE'), function($prefixes) use($prefix_community_dictionary, $operation_time) {

            $known_community_dictionary = [];

            foreach(PrefixCommunity::select('id', 'prefix_id', 'community')->whereIn('prefix_id', $prefixes->pluck('id'))->get() as $x) {
                $known_community_dictionary[$x['prefix_id'] . ''][$x['community']] = $x;
            }

            $to_keep_communities = [];
            $to_insert = [];

            foreach($prefixes as $prefix) {
                if(isset($prefix_community_dictionary[$prefix['address'] . '/' . $prefix['mask']])) {
                    // If the communities do not match up, then we need to re-do them by deleting irrelevant ones and adding newly relevant ones.
                    $announced_prefix_communities = $prefix_community_dictionary[$prefix['address'] . '/' . $prefix['mask']];

                    foreach($announced_prefix_communities as $announced_community) {
                        
                        if(isset($to_keep_communities[$prefix->id])) {
                            $to_keep_communities[$prefix->id][] = $announced_community;
                        }
                        else {
                            $to_keep_communities[$prefix->id] = [ $announced_community ];
                        }

                        if(!isset($known_community_dictionary[$prefix->id][$announced_community])) {
                            $to_insert[] = [
                                'prefix_id' => $prefix->id,
                                'is_large' => substr_count($announced_community, ':') === 2 ? 1 : 0,
                                'community' => $announced_community,
                                'created_at' => $operation_time,
                                'updated_at' => $operation_time,
                                'deleted_at' => null
                            ];
                        }
                    }
                }
            }

            // Clear out old communities for speed
            PrefixCommunity::onlyTrashed()->where('deleted_at', '<', Carbon::now()->subDays(30))->forceDelete();

            // We're going to try something absolutely festy...
            foreach(array_chunk($to_keep_communities, 40, true) as $chunk_values) {
                PrefixCommunity::where(function($x) use($chunk_values) {
                    foreach($chunk_values as $id => $community) {
                        $x->orWhere(function($y) use($id, $community) {
                            $y->where('prefix_id', '=', $id)->whereNotIn('community', array_unique($community));
                        });
                    }
                })->delete();
            }

            // Insert new
            foreach(array_chunk($to_insert, env('CHUNK_SIZE')) as $insertable) {
                PrefixCommunity::insert($insertable);
            }
            
        });
        
        return 0;
    }
}
