<?php

namespace App\Http\Controllers\Goliath;

// Laravel
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

use Illuminate\Support\Facades\Validator;

use Illuminate\Support\Facades\Notification;

// Creatia Default
use App\Helpers\Security;
use App\Notifications\SlackNotification;
use App\Http\Controllers\Network\NetworkController;
use App\Rules\ApplianceIdentity;

// Mitigation
use App\Models\Goliath\GoliathAppliance;
use App\Models\Goliath\GoliathMetro;
use App\Models\Goliath\GoliathRegion;
use App\Models\Goliath\GoliathSubregion;
use App\Models\Goliath\GoliathAttack;
use App\Models\Goliath\MitigationEventQueue;
use Illuminate\Support\Facades\Redis;

class AgentGoliathController extends NetworkController
{

    // This is used for the latest generation of mitigation whereby regional configurations can be distributed.
    public function fetch_update_v4(Request $_r) {

        $region = $_r->input('region', null);
        $subregion = $_r->input('subregion', null);
        $datacentre = $_r->input('datacentre', null);

        $appliance_target_hash = $_r->input('target_hash', null);
        $appliance_chain_hash = $_r->input('chain_hash', null);

        $unparsed_targets = Redis::get("goliath-flags.$subregion") ?? Redis::get('goliath-flags');
        $unparsed_chains = Redis::get("goliath-chains.$subregion") ?? Redis::get('goliath-chains');

        // In the unfortunate scenario this happens we MUST fail
        if($unparsed_targets === null || $unparsed_chains === null) {
            return response()->json([], 500);
        }

        $target_hash = md5($unparsed_targets);
        $chain_hash = md5($unparsed_chains);

        if($appliance_target_hash !== $target_hash || $appliance_chain_hash !== $chain_hash) {
            
            return response()->json([
                'update' => true,
                'chain_hash' => $chain_hash,
                'target_hash' => $target_hash,
                'targets' => $unparsed_targets,
                'chains' => $unparsed_chains,
                'prefix_count' => sizeof(json_decode($unparsed_targets, true)),
            ], 200);
        }

        return response()->json([
            'update' => false,
            'chain_hash' => $chain_hash,
            'target_hash' => $target_hash,
            'prefix_count' => sizeof(json_decode($unparsed_targets, true))
        ], 200);
    }

    public function register_appliance(Request $_r) {

        // Validate this request in the first place.
        $valid_request = !Validator::make( $_r->all(), [
            'name'                      => ['required', 'string'],
            
            'region_tag'                => ['nullable', 'exists:goliath_regions,telemetry_identifier'],
            'subregion_tag'             => ['nullable', 'exists:goliath_subregions,telemetry_identifier'],
            'facility_tag'              => ['nullable', 'exists:goliath_metro,telemetry_identifier'],

            'appliance_id'              => [new ApplianceIdentity]
            ])->fails();

        if($valid_request) {
            $appliance_id = $_r->input('appliance_id', false);

            $metro = $_r->input('facility_tag') == null ? '-' : GoliathMetro::where('telemetry_identifier','=',$_r->input('facility_tag'))->first();
            $subregion = $_r->input('subregion_tag') == null ? '-' : GoliathSubregion::where('telemetry_identifier','=',$_r->input('subregion_tag'))->first();
            $region =  $_r->input('region_tag') == null ? '-' : GoliathRegion::where('telemetry_identifier','=',$_r->input('region_tag'))->first();
            
            $appliance = $appliance_id ? GoliathAppliance::find($appliance_id) : new GoliathAppliance([
                'name' => $_r->input('name'),
                'organisation_id' => Security::bearer_token_organisation_id($_r),
                'registered_at' => Carbon::now()
            ]);

            $appliance->region_id = $region->id ?? null;
            $appliance->subregion_id = $subregion->id ?? null;
            $appliance->metro_id = $metro->id ?? null;

            $appliance->address = Security::real_ip();

            if($appliance_id) {
                $update = false;

                // Check if the region, subregion and metro have changed...
                if($appliance_id && ($appliance->region_id != $region->id || $appliance->subregion_id != $subregion->id || $appliance->metro_id != $metro->id)) {
                    Notification::route('slack', config('creatia.webhook.appliance'))->notify(new SlackNotification("An appliance named: $appliance->name of ID: $appliance_id is re-registering with new location information."));
                    $update = true;
                }

                if($appliance->address != Security::real_ip()) {
                    Notification::route('slack', config('creatia.webhook.appliance'))->notify(new SlackNotification("An appliance named: $appliance->name of ID: $appliance_id is re-registering with a new address: " . Security::real_ip()));
                    $update = true;
                }

                if($appliance->name != $_r->input('name')) {
                    Notification::route('slack', config('creatia.webhook.appliance'))->notify(new SlackNotification("An appliance named: $appliance->name of ID: $appliance_id is re-registering with a new name: " . $_r->input('name')));
                    $appliance->name = $_r->input('name');
                    $update = true;
                }

                if($update) {
                    if($appliance->save()) {
                        Notification::route('slack', config('creatia.webhook.appliance'))->notify(new SlackNotification(
                            "An appliance named: $appliance->name of ID: $appliance->id has successfully reregistered at facility: $metro->name, subregion: $subregion->name, region: $region->name. Uses: " . Security::real_ip() . ". Approval required to resume function."
                        ));
                    };
                }
                else {
                    // Save it anyway so that way we can keep track of reporting via the updated_at field.
                    $appliance->save();
                }
            }
            else {
                
                $appliance->authorised = 0;

                if($appliance->save()) {
                    Notification::route('slack', config('creatia.webhook.appliance'))->notify(new SlackNotification(
                        "An appliance named: $appliance->name of ID: $appliance->id has successfully registered at metro: $metro->name, subregion: $subregion->name, region: $region->name. Uses: " . Security::real_ip() . ". Approval required to begin function."
                    ));
                }
            }

            return response()->json([
                'message' => 'success',
                'appliance_id' => $appliance->id
            ], 200);
        }

        return response()->json([
            'message' => 'failure'
        ], 400);
    }

    public function update_hash(Request $_r) {
        // Validate this request in the first place.
        $valid_request = !Validator::make( $_r->all(), [
            'hash'                  => ['required', 'string'],
            'goliath_commit'        => ['string'],
            'agent_commit'          => ['string'],
            'appliance_id'          => ['required', new ApplianceIdentity]
            ])->fails();

        if($valid_request) {
            $appliance = GoliathAppliance::find($_r->input('appliance_id', false));

            if($appliance->update([
                'hash' => $_r->input('hash'),
                'goliath_commit' => $_r->input('goliath_commit', 'None Provided'),
                'agent_commit' => $_r->input('agent_commit', 'None Provided')
            ])) {
                return response()->json([
                    'message' => 'success',
                ], 200);   
            }
        }

        Notification::route('slack', config('creatia.webhook.appliance'))->notify(new SlackNotification(
            "An appliance supplied an invalid hash update attempt. IP Address: " . Security::real_ip()
        ));

        return response()->json([
            'message' => 'failure'
        ], 400);
    }

    public function add_attack(Request $_r) {
        try {
            // Validate this request in the first place.
            $valid_request = !Validator::make( $_r->all(), [
                'appliance_id'                      => ['required', new ApplianceIdentity],
                'attacks.*.description'             => ['required', 'string'],
                'attacks.*.type'                    => ['required', 'string'],
                'attacks.*.destination'             => ['required', 'ip'],
                'attacks.*.rounded_start_timestamp' => ['required', 'numeric'],
                'attacks.*.whole_start_timestamp'   => ['required', 'numeric'],
                'attacks.*.rounded_last_timestamp'  => ['required', 'numeric'],
                'attacks.*.whole_last_timestamp'    => ['required', 'numeric']
                ])->fails();

            if($valid_request) {
                $appliance = GoliathAppliance::find($_r->input('appliance_id', false));

                $to_insert = [];
                $to_queue = [];

                foreach($_r->input('attacks') as $attack) {

                    $lookup = GoliathAttack::where([
                            ['appliance_id', '=', $_r->input('appliance_id')],
                            ['description', '=', $attack['description']],
                            ['destination', '=', $attack['destination']],
                            ['updated_at', '>=', Carbon::now()->subMinutes(5)]
                        ])
                        ->select(DB::raw("concat(appliance_id, description, destination) as x"))
                        ->first() ?? false;

                    if(!$lookup) {
                        $to_insert[] = [
                            'created_at'            => Carbon::now(),
                            'updated_at'            => Carbon::now(),
                            'started_at'            => Carbon::createFromTimestamp($attack['whole_start_timestamp']),
                            'last_at'               => Carbon::createFromTimestamp($attack['whole_last_timestamp']),
                            'type'                  => $attack['type'],
                            'description'           => $attack['description'],
                            'destination'           => $attack['destination'],
                            'region_id'             => $appliance['region_id'],
                            'subregion_id'          => $appliance['subregion_id'],
                            'metro_id'              => $appliance['metro_id'],
                            'appliance_id'          => $_r->input('appliance_id')
                        ];
                    }
                    else {
                        GoliathAttack::where([
                                ['appliance_id', '=', $_r->input('appliance_id')],
                                ['description', '=', $attack['description']],
                                ['destination', '=', $attack['destination']],
                                ['started_at', '=', Carbon::createFromTimestamp($attack['whole_start_timestamp'])]
                            ])->update([
                                'updated_at'            => Carbon::now(),
                                'last_at'               => Carbon::createFromTimestamp($attack['whole_last_timestamp'])
                            ]);
                    }

                    $queue_lookup = MitigationEventQueue::where([
                            ['appliance_id', '=', $_r->input('appliance_id')],
                            ['description', '=', $attack['description']],
                            ['destination', '=', $attack['destination']],
                            ['updated_at', '>=', Carbon::now()->subMinutes(5)]
                        ])
                        ->select(DB::raw("concat(appliance_id, description, destination) as x"))
                        ->first() ?? false;

                    if(!$queue_lookup) {
                        $to_queue[] = [
                            'created_at'            => Carbon::now(),
                            'updated_at'            => Carbon::now(),
                            
                            'started_at'            => Carbon::createFromTimestamp($attack['whole_start_timestamp']),
                            'finished_at'           => Carbon::createFromTimestamp($attack['whole_last_timestamp']),
                            
                            'type'                  => $attack['type'],
                            'description'           => $attack['description'],
                            'destination'           => $attack['destination'],
                            
                            'appliance_id'          => $_r->input('appliance_id')
                        ];
                    }
                    else {
                        MitigationEventQueue::where([
                                ['appliance_id', '=', $_r->input('appliance_id')],
                                ['description', '=', $attack['description']],
                                ['destination', '=', $attack['destination']],
                                ['started_at', '=', Carbon::createFromTimestamp($attack['whole_start_timestamp'])]
                            ])->update([
                                'updated_at'            => Carbon::now(),
                                'finished_at'           => Carbon::createFromTimestamp($attack['whole_last_timestamp']),

                                // We need to send this event over to Oculi again if it's changed
                                'processed_at'          => null
                            ]);
                    }
                }

                DB::table('goliath_attacks')->insert($to_insert);
                DB::table('mitigation_event_queue')->insert($to_queue);

                if($appliance->save()) {
                    return response()->json([
                        'message' => 'success',
                    ], 200);   
                }
            }
        }
        catch(\Throwable $e) {

            Notification::route('slack', env('SLACK_HOOK'))->notify(new SlackNotification(
                "An appliance tried to notify of an attack but an error occured in Creatia. Investigation required"
            ));

            return response()->json([
                'message' => 'failure'
            ], 500);
        }

        Notification::route('slack', env('SLACK_HOOK'))->notify(new SlackNotification(
            "An appliance tried to notify of an attack but the payload was malformed. Device IP: " . Security::real_ip()
        ));

        return response()->json([
            'message' => 'failure'
        ], 400);
    }

}
