<?php

namespace App\Console\Commands\Network;

use App\Console\Commands\BaseCommand;
use App\Models\Goliath\GoliathAttack;
use App\Models\Goliath\GoliathAttackPrefix;
use Carbon\Carbon;
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

class GoliathMonitorAttack extends BaseCommand
{

    protected $signature = 'goliath:monitor-attacks {--thread-pool=} {--minutes=}';

    protected $description = 'Associates attacks streamed in from appliances against the prefixes that contain them.';

    private static function associate(int $_id_max, int $_id_mod, int $_minutes) {
        $inserted = 0;

        GoliathAttack::select(
            'id',
            DB::raw('(
                SELECT string_agg(id::text, \' \')
                FROM prefixes
                WHERE
                    prefixes.deleted_at is null
                    AND family(prefixes.address) = 4
                    AND prefixes.mask > 16
                    AND (
                        (prefixes.mask = 32 and goliath_attacks.destination = prefixes.address)
                        OR CONCAT(goliath_attacks.destination,\'/32\')::cidr << concat(prefixes.address,\'/\',prefixes.mask)::cidr
                    )
            ) AS prefix_ids')
        )
        ->where('created_at', '>', Carbon::now()->subMinutes($_minutes))
        ->where(DB::raw('id % ' . $_id_max), '=', $_id_mod)
        ->chunkById(5000, function($attacks) use(&$inserted) {

            $to_insert = [];

            $known_association_lookup = GoliathAttackPrefix::leftJoin('goliath_attacks', 'attack_id', '=', 'goliath_attacks.id')
                ->select(DB::raw('CONCAT(prefix_id, \'-\', attack_id) AS lookup_id'))
                ->whereIn('attack_id', $attacks->pluck('id'))
                ->orderBy('goliath_attack_prefixes.id', 'desc')
                ->get()
                ->keyBy('lookup_id');

            foreach($attacks as $attack) {
                if($attack->prefix_ids != null) {
                    foreach(explode(' ', $attack->prefix_ids) as $prefix_id) {

                        if(!isset($known_association_lookup["$prefix_id-$attack->id"])) {
                            $to_insert[] = [
                                'prefix_id' => $prefix_id,
                                'attack_id' => $attack->id
                            ];
                        }
                    }
                }
            }

            if(sizeof($to_insert) !== 0) {
                foreach(array_chunk($to_insert, config('creatia.constant.chunk_small')) as $chunk) {
                    DB::table('goliath_attack_prefixes')->insert($chunk);
                }
            }

            $inserted += sizeof($to_insert);
        });

        return $inserted;
    }

    public function handle()
    {
        $threads = (int) ($this->option('thread-pool') ?? 2);
        $minutes = (int) ($this->option('minutes') ?? 2);

        $tasks = [];

        for ($i = 0; $i < $threads; ++$i) {
            $tasks[] = static fn () => self::associate($threads, $i, $minutes);
        }

        try {
            $results = Concurrency::run($tasks);

            if(array_sum($results) !== 0) {
                $this->log_event('Attack prefix associations created.', [ 'total' => array_sum($results), 'per_thread' => $results ]);
            }
        }
        catch(\Exception $ex) {
            $this->log_error('Attack association monitoring failed.', [ 'error' => $ex->getMessage() ]);

            return 1;
        }

        return 0;
    }
}
