<?php

namespace App\Console\Commands\Network;

use App\Console\Commands\BaseCommand;
use App\Models\Goliath\GoliathSubregion;
use App\Models\Goliath\PrefixGoliathLoose;
use Illuminate\Support\Facades\DB;

use App\Models\Prefixes;
use App\Models\PrefixCommunity;
use Throwable;

class GoliathLocationSync extends BaseCommand
{

    protected $signature = 'goliath:location-sync';

    protected $description = 'Handles updating of the locations that a prefix is loosened in.';

    private $regionLookup = [];
    private $identityLookup = [];
    
    private function buildCommunityObject($communities) {

        $obj = [];

        foreach($communities as $x) {
            $obj[$x] = isset($this->regionLookup[$x]) ? $this->identityLookup[$this->regionLookup[$x]]->id : 0;
        }

        return $obj === [] ? null : $obj;
    }

    public function handle()
    {
        // We actually declare these here because the region lookup and such shouldn't happen in the constructor which builds all of these when the kernel starts up.
        $this->regionLookup = json_decode(file_get_contents(app_path('Models/CommunityLocationLookup.json')), true);
        $this->identityLookup = GoliathSubregion::select('telemetry_identifier', 'id')->get()->keyBy('telemetry_identifier');

        // Pre-fetch communities
        $all_parent_communities = DB::table('prefixes as p1')
            ->leftJoin('prefix_community', 'prefix_id', '=', 'p1.id')
            ->leftJoin('supernets', 'supernet_id', '=', 'p1.id')
            ->whereNull('p1.deleted_at')
            ->whereNull('prefix_community.deleted_at')
            ->where([['community', '!=', '7578:99'], ['community', 'LIKE', '%99']])
            ->where('is_large', '=', '0')
            ->select(DB::raw('subnet_id as prefix_id'), 'community')
            ->get()
            ->groupBy('prefix_id');

        $all_known_communities = PrefixCommunity::select('prefix_id', 'community')->where([['community', '!=', '7578:99'], ['community', 'LIKE', '%99'], ['is_large', '=', 0]])->get()->groupBy('prefix_id');
        $all_loose_communities = PrefixGoliathLoose::select('id', 'subregion_info', 'prefix_id')->get()->keyBy('prefix_id');

        $in_parent_mercury = DB::table('prefixes as p1')
            ->leftJoin('prefix_community', 'prefix_id', '=', 'p1.id')
            ->leftJoin('supernets', 'supernet_id', '=', 'p1.id')
            ->whereNull('p1.deleted_at')
            ->whereNull('prefix_community.deleted_at')
            ->where([['community', '=', '7578:92'], ['still_announced', '=', 1]])
            ->where('is_large', '=', '0')
            ->select(DB::raw('subnet_id as prefix_id'), 'community')
            ->get()
            ->groupBy('prefix_id');

        $in_mercury = PrefixCommunity::select('prefix_id')->where([['community', '=', '7578:92'], ['is_large', '=', 0]])->get()->groupBy('prefix_id');

        // Grab globs of prefixes

        Prefixes::join('prefix_goliath_loose', 'prefix_id', '=', 'prefixes.id')
            ->select('prefixes.id', DB::raw('CONCAT(address, \'/\', mask) as prefix'))
            ->where('loose', '=', 1)
            ->whereNull('prefix_goliath_loose.deleted_at')
            ->chunk(5000, function($prefixes) use($all_parent_communities, $all_known_communities, $all_loose_communities, $in_mercury, $in_parent_mercury) {

                try {

                    DB::beginTransaction();

                    foreach($prefixes as $prefix) {

                        if(!isset($all_known_communities[$prefix->id])) {
                            $all_known_communities[$prefix->id] = collect([]);
                        }

                        if(!isset($all_parent_communities[$prefix->id])) {
                            $all_parent_communities[$prefix->id] = collect([]);
                        }

                        $combined_parent_child_communities = array_unique([...$all_known_communities[$prefix->id]->pluck('community'), ...$all_parent_communities[$prefix->id]->pluck('community')]);

                        $known_communities = sizeof($combined_parent_child_communities) !== 0 ? $combined_parent_child_communities : [];
                        $loose_communities = isset($all_loose_communities[$prefix->id]) ? json_decode($all_loose_communities[$prefix->id]->subregion_info, true) : [];

                        // Early bailout for Mercury
                        if(isset($in_mercury[$prefix->id]) || isset($in_parent_mercury[$prefix->id])) {

                            echo "We've hit a bailout for Mercury: " . json_encode($prefix) . "\n";

                            if($loose_communities !== null && sizeof($loose_communities) !== 0) {
                                PrefixGoliathLoose::where('prefix_id', '=', $prefix->id)->update([
                                    'subregion_info' => null
                                ]);
                            }

                            continue;
                        }

                        // Check that all of the current loose communities still exist - Occurs for previously established setups.
                        $should_update = false;

                        if($loose_communities !== null && sizeof($loose_communities) !== 0) {
                            $count_loose = 0;

                            foreach(array_keys($loose_communities) as $community) {
                                if(array_search($community, $known_communities) !== false) {
                                    ++$count_loose;
                                }
                            }

                            // Not matching - Update

                            if($count_loose !== sizeof($loose_communities)) {

                                $should_update = true;
                            }
                            
                        }
                        else if(($loose_communities === null && sizeof($known_communities) > 0) || (sizeof($known_communities) === 0 && $loose_communities !== null)) { // When the first time setup occurs, or the prefix no longer has communities

                            $should_update = true;
                        }

                        if($should_update) {
                            PrefixGoliathLoose::where('prefix_id', '=', $prefix->id)->update([
                                'subregion_info' => $this->buildCommunityObject($known_communities)
                            ]);

                        }

                    }

                    DB::commit();

                }
                catch(Throwable $t) {
                    DB::rollBack();
                }

            });

        return 0;
    }
}
