<?php

namespace App\Models\Goliath;

use App\Models\Asns;
use App\Models\Organisation;
use App\Models\Prefixes;
use App\Notifications\SlackNotification;
use App\Rules\AdminGameFilters;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Notification;

class PrefixGoliathFlag extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'prefix_id',
        'flag_synproxy',
        'flag_heuristics',
        'flag_heuristics_level',
        'flag_block_udp',
        'flag_block_tcp',
        'flag_block_fragments',
        'flag_syn_cookies',
        'flag_drop_tcp_out_of_state',
        'flag_app_filter_strict',
        'flag_null_route',
        'flag_syn_cookie_data_promote',
        'flag_syn_cookie_os_gate',
        'flag_syn_challenge',
        'flag_syn_drop_first',
        'flag_src_limit_pps',
        'flag_src_limit_mbps',
        'flag_drop_unexpected_protocols',
        'flag_drop_non_core_protocols',
        'flag_drop_udp_to_tcp_ports',
        'flag_udp_established_lane',
        'flag_special',
        'region_id',
        'subregion_id',
        'metro_id',
        'flag_filters'
    ];

    private $changeLog = [];

    public function setFilters( $_filters )
    {
        if(!$_filters) {
            $this->flag_filters = null;

            return;
        }

        $filters = [];

        foreach(AdminGameFilters::VALID_FILTERS as $filter) {
            if(!isset($_filters[$filter]) || $_filters[$filter] === null) {
                continue;
            }

            $entries = $_filters[$filter];

            $normalised = [];

            foreach($entries as $entry) {
                if($entry === null || $entry === '') {
                    continue;
                }

                $normalised[] = (string) $entry;
            }

            if($normalised === []) {
                continue;
            }

            if($filter === 'icmp_anycast') {
                $normalised = [ "0-0" ];
            }

            $filters[$filter] = $normalised;
        }

        $this->flag_filters = $filters === [] ? null : json_encode($filters);
    }

    // The value a save leaves behind: getDirty() carries a field cleared to null, which `??` would read past.
    private static function after(array $_new, array $_old, string $_key)
    {
        return array_key_exists($_key, $_new) ? $_new[$_key] : ($_old[$_key] ?? null);
    }

    private static function srcLimitLabel($_pps, $_mbps): string
    {
        if($_pps) {
            return "$_pps pps";
        }

        if($_mbps) {
            return "$_mbps Mbps";
        }

        return 'Off';
    }

    protected static function booted()
    {

        $change = function ($_m) {

            try {
                $new = $_m->getDirty();
                $old = $_m->getOriginal();
    
                $addr = Prefixes::find($_m->prefix_id);
    
                $user = Auth::user();
                $organisation = Organisation::find(Prefixes::find($_m->prefix_id)->organisation_id);
    
                $_m->changeLog = [
                    'Prefix' => "$addr->address/$addr->mask",
                    'User' => $user->name ?? 'AUTOMATED',
                    'Organisation' => "$organisation->name",
                    'Heuristics' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_heuristics'] != 1 ? 'Off' : $old['flag_heuristics_level']),
                        'New' => $new === [] ? 'N/A' : (($new['flag_heuristics'] ?? $old['flag_heuristics'] ?? 0) != 1 ? 'Off' : ($new['flag_heuristics_level'] ?? $old['flag_heuristics_level']))
                    ],
                    'Synproxy' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_synproxy'] != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_synproxy'] ?? $old['flag_synproxy'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'Filters' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_filters'] == null ? 'Off' : $old['flag_filters']),
                        'New' => $new === [] ? 'N/A' : (($new['flag_filters'] ?? $old['flag_filters'] ?? null) == null ? 'Off' : $new['flag_filters'] ?? $old['flag_filters'] ?? 'Off')
                    ],
                    'BlockUDP' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_block_udp'] != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_block_udp'] ?? $old['flag_block_udp'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'BlockTCP' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_block_tcp'] != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_block_tcp'] ?? $old['flag_block_tcp'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'BlockFragments' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_block_fragments'] != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_block_fragments'] ?? $old['flag_block_fragments'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'SynCookies' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_syn_cookies'] != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_syn_cookies'] ?? $old['flag_syn_cookies'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'DropTCPOutOfState' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_drop_tcp_out_of_state'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_drop_tcp_out_of_state'] ?? $old['flag_drop_tcp_out_of_state'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'AppFilterStrict' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_app_filter_strict'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_app_filter_strict'] ?? $old['flag_app_filter_strict'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'SynCookieDataPromote' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_syn_cookie_data_promote'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_syn_cookie_data_promote'] ?? $old['flag_syn_cookie_data_promote'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'SynCookieOsGate' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_syn_cookie_os_gate'] ?? 'Off'),
                        'New' => $new === [] ? 'N/A' : ($new['flag_syn_cookie_os_gate'] ?? $old['flag_syn_cookie_os_gate'] ?? 'Off')
                    ],
                    'SynChallenge' => [
                        'Old' => $old === [] ? 'N/A' : ($old['flag_syn_challenge'] ?? 'Not Set'),
                        'New' => $new === [] ? 'N/A' : ($new['flag_syn_challenge'] ?? $old['flag_syn_challenge'] ?? 'Not Set')
                    ],
                    'SynDropFirst' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_syn_drop_first'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_syn_drop_first'] ?? $old['flag_syn_drop_first'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'SrcLimit' => [
                        'Old' => $old === [] ? 'N/A' : self::srcLimitLabel($old['flag_src_limit_pps'] ?? null, $old['flag_src_limit_mbps'] ?? null),
                        'New' => $new === [] ? 'N/A' : self::srcLimitLabel(self::after($new, $old, 'flag_src_limit_pps'), self::after($new, $old, 'flag_src_limit_mbps'))
                    ],
                    'DropUnexpectedProtocols' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_drop_unexpected_protocols'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (self::after($new, $old, 'flag_drop_unexpected_protocols') != 1 ? 'Off' : 'On')
                    ],
                    'DropNonCoreProtocols' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_drop_non_core_protocols'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (self::after($new, $old, 'flag_drop_non_core_protocols') != 1 ? 'Off' : 'On')
                    ],
                    'DropUDPToTCPPorts' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_drop_udp_to_tcp_ports'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (self::after($new, $old, 'flag_drop_udp_to_tcp_ports') != 1 ? 'Off' : 'On')
                    ],
                    'UdpEstablishedLane' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_udp_established_lane'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (self::after($new, $old, 'flag_udp_established_lane') != 1 ? 'Off' : 'On')
                    ],
                    'Special' => [
                        'Old' => $old === [] ? 'N/A' : (($old['flag_special'] ?? 0) != 1 ? 'Off' : 'On'),
                        'New' => $new === [] ? 'N/A' : (($new['flag_special'] ?? $old['flag_special'] ?? 0) != 1 ? 'Off' : 'On')
                    ],
                    'Asn' => Asns::find($addr->asn_id)->asn,
                    'Address' => "$addr->address"
                ];
            }
            catch(\Exception $ex) {
                \Log::error($ex);
            }

        };

        static::creating($change);
        static::updating($change);
        static::deleting($change);

        $done = function ($_m) {

            try {

                $notification = "Mitigation Flags Updated\n"
                    . 'Prefix: <https://bgp.tools/prefix/' . $_m->changeLog['Address'] . '|' . $_m->changeLog['Prefix'] . '> ( <https://bgp.tools/as/' . $_m->changeLog['Asn'] . '|AS' . $_m->changeLog['Asn'] . '>)' . "\n"
                    . 'User: ' . $_m->changeLog['User'] . "\n"
                    . 'Organisation: ' . $_m->changeLog['Organisation'] . "\n"
                    . 'Heuristics: ' . $_m->changeLog['Heuristics']['Old'] . ' -> ' . $_m->changeLog['Heuristics']['New'] . "\n"
                    . 'Synproxy: ' . $_m->changeLog['Synproxy']['Old'] . ' -> ' . $_m->changeLog['Synproxy']['New'] . "\n"
                    . 'Filters: ' . $_m->changeLog['Filters']['Old'] . ' -> ' . $_m->changeLog['Filters']['New'] . "\n"
                    . "Filter Flag Block UDP: " . $_m->changeLog['BlockUDP']['Old'] . ' -> ' . $_m->changeLog['BlockUDP']['New'] . "\n"
                    . "Filter Flag Block TCP: " . $_m->changeLog['BlockTCP']['Old'] . ' -> ' . $_m->changeLog['BlockTCP']['New'] . "\n"
                    . "Filter Flag Block Fragments: " . $_m->changeLog['BlockFragments']['Old'] . ' -> ' . $_m->changeLog['BlockFragments']['New'] . "\n"
                    . 'SYN Cookies: ' . $_m->changeLog['SynCookies']['Old'] . ' -> ' . $_m->changeLog['SynCookies']['New'] . "\n"
                    . 'Drop TCP Out-of-State: ' . $_m->changeLog['DropTCPOutOfState']['Old'] . ' -> ' . $_m->changeLog['DropTCPOutOfState']['New'] . "\n"
                    . 'Strict Application Filter: ' . $_m->changeLog['AppFilterStrict']['Old'] . ' -> ' . $_m->changeLog['AppFilterStrict']['New'] . "\n"
                    . 'SYN Cookie Data Promote: ' . $_m->changeLog['SynCookieDataPromote']['Old'] . ' -> ' . $_m->changeLog['SynCookieDataPromote']['New'] . "\n"
                    . 'SYN Cookie OS Gate: ' . $_m->changeLog['SynCookieOsGate']['Old'] . ' -> ' . $_m->changeLog['SynCookieOsGate']['New'] . "\n"
                    . 'SYN Challenge Mode: ' . $_m->changeLog['SynChallenge']['Old'] . ' -> ' . $_m->changeLog['SynChallenge']['New'] . "\n"
                    . 'Force SYN Retransmit: ' . $_m->changeLog['SynDropFirst']['Old'] . ' -> ' . $_m->changeLog['SynDropFirst']['New'] . "\n"
                    . 'Per-Source Rate Limit: ' . $_m->changeLog['SrcLimit']['Old'] . ' -> ' . $_m->changeLog['SrcLimit']['New'] . "\n"
                    . 'Drop Unexpected Protocols: ' . $_m->changeLog['DropUnexpectedProtocols']['Old'] . ' -> ' . $_m->changeLog['DropUnexpectedProtocols']['New'] . "\n"
                    . 'Drop Non-Core Protocols: ' . $_m->changeLog['DropNonCoreProtocols']['Old'] . ' -> ' . $_m->changeLog['DropNonCoreProtocols']['New'] . "\n"
                    . 'Drop UDP to TCP Ports: ' . $_m->changeLog['DropUDPToTCPPorts']['Old'] . ' -> ' . $_m->changeLog['DropUDPToTCPPorts']['New'] . "\n"
                    . 'Established UDP Lane: ' . $_m->changeLog['UdpEstablishedLane']['Old'] . ' -> ' . $_m->changeLog['UdpEstablishedLane']['New'] . "\n"
                    . 'Special: ' . $_m->changeLog['Special']['Old'] . ' -> ' . $_m->changeLog['Special']['New'] . "\n";


                $history = new GoliathHistory([
                    'type'              => GoliathHistoryType::Flags,
                    'history'           => json_encode($_m->changeLog),
                    'organisation_id'   => Prefixes::find($_m->prefix_id)->organisation_id,
                    'user_id'           => Auth::user()->id ?? null
                ]);
                
                $history->save();

                // Calculate who should see the history entries - People who are subtenanted specifically.
                $to_insert = [];

                foreach(Prefixes::find($_m->prefix_id)->getPrefixOrganisations() as $organisation) {
                    $to_insert[] = [ 'organisation_id' => $organisation, 'history_id' => $history->id ];
                }

                if($to_insert !== []) {
                    GoliathHistoryVisibility::insert($to_insert);
                }

                \Log::debug($notification);


                Notification::route('slack', config('creatia.webhook.goliath'))->notify(new SlackNotification($notification));
            }
            catch(\Exception $ex) {
                \Log::error($ex);

                try { \Log::info($notification); } catch (\Exception $i) {}
            }
        };

        static::created($done);
        static::updated($done);
        static::deleted($done);

    }
}
