<?php

namespace Tests\Feature;

use App\Console\Commands\Report\AttackTrackCompletion;
use App\Console\Commands\Report\AttackTrackStart;
use App\Console\Commands\Report\ReportPendSingularThread;
use App\Helpers\Utility\SymEncrypt;
use App\Models\Goliath\ReportAwareness;
use App\Models\Organisation;
use App\Models\PendingReport;
use App\Models\PendingReportStatus;
use App\Models\PendingReportType;
use App\Models\Prefixes;
use App\Models\ReportOrganisationAttribute;
use App\Models\Webhook;
use App\Models\WebhookHookType;
use App\Models\WebhookMessage;
use App\Models\WebhookMessageStatus;
use App\Models\WebhookUsageType;
use App\Notifications\SlackNotification;
use Carbon\Carbon;
use Exception;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;

use function PHPUnit\Framework\assertTrue;

/**
 *      @@@ TEST CERTIFICATION @@@
 *      This set of tests has been confirmed to be functioning as intended.
 *      
 *      Jesse Cunningham, 2025-06-25 11:17 PM AEST
 */

class ReportNotificationTest extends TestCase
{

    use RefreshDatabase;

    private $organisation = null;
    private $attributes = null;
    private $webhooks = null;

    protected function setUp(): void
    {
        parent::setUp();

        /**
         * Make sure environment is safe
         */

        Http::fake([
            'http://localhost:56000/hook/*' => Http::response([], 200),
            '*' => Http::response([ 'Fallback Error' ], 500) // Fallback
        ]);

        Notification::fake();

        /**
         * Database setup and preamble
         */

        $this->organisation = [
            'a' => Organisation::create([ 'name' => 'a' ]),
            'b' => Organisation::create([ 'name' => 'b' ]),
            'c' => Organisation::create([ 'name' => 'c' ]),
            'd' => Organisation::create([ 'name' => 'd' ])
        ];

        $this->attributes = [
            // Sends everything when it's above 2 Gbps or 10 Mpps
            'a' => ReportOrganisationAttribute::create([
                'organisation_id'           => $this->organisation['a']->id,
                'send_start'                => true,
                'send_finish'               => true,
                'send_webhook'              => true,
                'send_individual'           => true,
                'send_carpet'               => true,
                'send_daily'                => true,
                'send_weekly'               => true,
                'send_monthly'              => true,
                'send_quarterly'            => true,
                'minimum_threshold_gbps'    => 2,
                'minimum_threshold_mpps'    => 10
            ]),
            // Do not send anything, things can pend however
            'b' => ReportOrganisationAttribute::create([
                'organisation_id'           => $this->organisation['b']->id,
                'send_start'                => true,
                'send_finish'               => true,
                'send_webhook'              => false,
                'send_individual'           => false,
                'send_carpet'               => false,
                'send_daily'                => false,
                'send_weekly'               => false,
                'send_monthly'              => false,
                'send_quarterly'            => false,
                'minimum_threshold_gbps'    => 1,
                'minimum_threshold_mpps'    => 1
            ]),
            // Send reports when > 10Gbps or 1 Mpps
            'c' => ReportOrganisationAttribute::create([
                'organisation_id'           => $this->organisation['c']->id,
                'send_start'                => false,
                'send_finish'               => false,
                'send_webhook'              => true,
                'send_individual'           => true,
                'send_carpet'               => false,
                'send_daily'                => false,
                'send_weekly'               => false,
                'send_monthly'              => false,
                'send_quarterly'            => false,
                'minimum_threshold_gbps'    => 10,
                'minimum_threshold_mpps'    => 1
            ]),
            // Send start, finish and individual when greater than 2 Gbps or 10 Mpps - Though no webhook
            'd' => ReportOrganisationAttribute::create([
                'organisation_id'           => $this->organisation['d']->id,
                'send_start'                => true,
                'send_finish'               => true,
                'send_webhook'              => true,
                'send_individual'           => false,
                'send_carpet'               => false,
                'send_daily'                => true,
                'send_weekly'               => false,
                'send_monthly'              => false,
                'send_quarterly'            => false,
                'minimum_threshold_gbps'    => 2,
                'minimum_threshold_mpps'    => 10
            ])
        ];

        $sym_encrypt = new SymEncrypt(config('creatia.reporting.encryption_key'));

        $this->webhooks = [
            'a' => [
                '1' => Webhook::create([
                    'name'              => 'Hook A 1',
                    'hook'              => $sym_encrypt->encrypt('http://localhost:56000/hook/a/1'),
                    'hook_type'         => WebhookHookType::Slack,
                    'usage_type'        => WebhookUsageType::Reporting,
                    'organisation_id'   => $this->organisation['a']->id
                ]),
                '2' => Webhook::create([
                    'name'              => 'Hook A 2',
                    'hook'              => $sym_encrypt->encrypt('http://localhost:56000/hook/a/2'),
                    'hook_type'         => WebhookHookType::Slack,
                    'usage_type'        => WebhookUsageType::Other1,
                    'organisation_id'   => $this->organisation['a']->id
                ]),
                '3' => Webhook::create([
                    'name'              => 'Hook A 3',
                    'hook'              => $sym_encrypt->encrypt('http://localhost:56000/hook/a/3'),
                    'hook_type'         => WebhookHookType::Slack,
                    'usage_type'        => WebhookUsageType::Reporting,
                    'organisation_id'   => $this->organisation['a']->id
                ])
            ],
            'b' => [
                '1' => Webhook::create([
                    'name'              => 'Hook B 1',
                    'hook'              => $sym_encrypt->encrypt('http://localhost:56000/hook/b/1'),
                    'hook_type'         => WebhookHookType::Slack,
                    'usage_type'        => WebhookUsageType::Reporting,
                    'organisation_id'   => $this->organisation['b']->id
                ]),
                '2' => Webhook::create([
                    'name'              => 'Hook B 2',
                    'hook'              => $sym_encrypt->encrypt('http://localhost:56000/hook/b/2'),
                    'hook_type'         => WebhookHookType::Slack,
                    'usage_type'        => WebhookUsageType::Other1,
                    'organisation_id'   => $this->organisation['b']->id
                ])
            ],
            'c' => [
                '1' => Webhook::create([
                    'name'              => 'Hook C 1',
                    'hook'              => $sym_encrypt->encrypt('http://localhost:56000/hook/c/1'),
                    'hook_type'         => WebhookHookType::Slack,
                    'usage_type'        => WebhookUsageType::Reporting,
                    'organisation_id'   => $this->organisation['c']->id
                ]),
                '2' => Webhook::create([
                    'name'              => 'Hook C 2',
                    'hook'              => $sym_encrypt->encrypt('http://localhost:56000/hook/c/2'),
                    'hook_type'         => WebhookHookType::Slack,
                    'usage_type'        => WebhookUsageType::Other1,
                    'organisation_id'   => $this->organisation['c']->id
                ])
            ],
            'd' => []
        ];

        // Create and assign ownership of prefixes
        DB::table('prefixes')->insert([
            [
                'address' => '103.1.213.13',
                'mask' => 32,
                'seen_at' => Carbon::now(),
                'still_announced' => 1,
                'created_source' => 'prefix-list-uncrushed',
                'organisation_id' => $this->organisation['a']->id,
                'asn_id' => null,
            ],
            [
                'address' => '103.1.213.14',
                'mask' => 32,
                'seen_at' => Carbon::now(),
                'still_announced' => 1,
                'created_source' => 'prefix-list-uncrushed',
                'organisation_id' => $this->organisation['b']->id,
                'asn_id' => null,
            ],
            [
                'address' => '103.1.213.15',
                'mask' => 32,
                'seen_at' => Carbon::now(),
                'still_announced' => 1,
                'created_source' => 'prefix-list-uncrushed',
                'organisation_id' => $this->organisation['c']->id,
                'asn_id' => null,
            ],
            [
                'address' => '103.1.213.16',
                'mask' => 32,
                'seen_at' => Carbon::now(),
                'still_announced' => 1,
                'created_source' => 'prefix-list-uncrushed',
                'organisation_id' => $this->organisation['d']->id,
                'asn_id' => null,
            ],
            [
                'address' => '100.2.2.0',
                'mask' => 24,
                'seen_at' => Carbon::now(),
                'still_announced' => 1,
                'created_source' => 'prefix-list-uncrushed',
                'organisation_id' => $this->organisation['d']->id,
                'asn_id' => null,
            ],
            [
                'address' => '100.2.2.1',
                'mask' => 24,
                'seen_at' => Carbon::now(),
                'still_announced' => 1,
                'created_source' => 'prefix-list-uncrushed',
                'organisation_id' => $this->organisation['d']->id,
                'asn_id' => null,
            ]
        ]);

    }

    public function testAttackTrackCompletion(): void
    {

        // Corresponding start notifications AND a finish notification to test prevention.
        ReportAwareness::insert([
            [ 'type' => 'start', 'status' => 'sent', 'report_id' => 1, 'organisation_id' => $this->organisation['a']['id'] ],
            [ 'type' => 'start', 'status' => 'sent', 'report_id' => 2, 'organisation_id' => $this->organisation['b']['id'] ],
            [ 'type' => 'start', 'status' => 'sent', 'report_id' => 3, 'organisation_id' => $this->organisation['c']['id'] ],
            [ 'type' => 'start', 'status' => 'sent', 'report_id' => 4, 'organisation_id' => $this->organisation['d']['id'] ],
            [ 'type' => 'start', 'status' => 'sent', 'report_id' => 5, 'organisation_id' => $this->organisation['d']['id'] ],
            [ 'type' => 'start', 'status' => 'sent', 'report_id' => 6, 'organisation_id' => $this->organisation['d']['id'] ],
            [ 'type' => 'finish', 'status' => 'sent', 'report_id' => 8, 'organisation_id' => $this->organisation['d']['id'] ]
        ]);

        $generate_completed = function($a) {

            $now = Carbon::now()->timestamp;

            return collect([
                [ // Should
                    'singular_id'       => 1,
                    'destination_addr'  => '103.1.213.13',
                    'organisation_id'   => $this->organisation['a']['id'],
                    'attack_start'      => $now - 1600 - 30 - 120,
                    'attack_finish'     => $now + 30,
                    'gbps'              => 12,
                    'mpps'              => 11

                ],
                [ // Should
                    'singular_id'       => 2,
                    'destination_addr'  => '103.1.213.14',
                    'organisation_id'   => $this->organisation['b']['id'],
                    'attack_start'      => $now - 60 - 120,
                    'attack_finish'     => $now + 60,
                    'gbps'              => 0.1,
                    'mpps'              => 0.5

                ],
                [ // Should
                    'singular_id'       => 3,
                    'destination_addr'  => '103.1.213.15',
                    'organisation_id'   => $this->organisation['c']['id'],
                    'attack_start'      => $now - 120 - 120,
                    'attack_finish'     => $now + 120,
                    'gbps'              => 12,
                    'mpps'              => 11
                ],
                [ // Should not - Missing start entry
                    'singular_id'       => 7,
                    'destination_addr'  => '100.2.2.11',
                    'organisation_id'   => $this->organisation['d']['id'],
                    'attack_start'      => $now - 33 - 120,
                    'attack_finish'     => $now + 33,
                    'gbps'              => 1,
                    'mpps'              => 11
                ],
                [ // Should not - Existing finish entry
                    'singular_id'       => 8,
                    'destination_addr'  => '100.2.2.11',
                    'organisation_id'   => $this->organisation['d']['id'],
                    'attack_start'      => $now,
                    'attack_finish'     => $now,
                    'gbps'              => 2,
                    'mpps'              => 6
                ]
            ]);
        };

        $store_completed = function($a, $reporting, $creatia, $summary) {
            ReportAwareness::insert($creatia);
        };

        $command = new AttackTrackCompletion;

        $command->iterateOverComplete(null, $generate_completed, $store_completed);

        $this->assertEquals(3, ReportAwareness::where([['type', '=', 'finish'], ['status', '=', 'pending']])->count(), "Only three awareness entries should be generated.");

        $this->assertEquals(3, ReportAwareness::where([['type', '=', 'finish'], ['status', '=', 'pending']])->selectRaw('count(DISTINCT organisation_id) as a')->get()->first()->a, 'Each awareness entry needs to be associated with a single organisation.');
    }

    public function testReportPendSingular(): void
    {
        /**
         * Create some potential reports. The following scenarios need to be covered:
         * 10G + Ready, 2G + Ready, 1G + Ready, 10G + Not Ready, 2G + Not Ready, 1G + Not Ready
         * Report outside of window
         * Report inside of window
         * 
         * Ideally do the first lot of pairings each against the inside and outside. 
         */

        $now = Carbon::now();
        $before = Carbon::now()->subHours(4);

        $generate = function ($_organisation_letter, $_organisation_id) use($now, $before) {

            $assign = [];

            foreach([1, 0] as $ready) { 
                foreach([10, 2, 1] as $speed) {
                    $assign[] = [
                        'report_id' => "$_organisation_id" . sizeof($assign),
                        'start' => $before,
                        'finish' => $now,
                        'destination' => Prefixes::where('organisation_id', '=', $_organisation_id)->get()->first()->address,
                        'organisation' => $_organisation_letter,
                        'ready' => $ready,
                        'gbps' => $speed,
                        'mpps' => 1
                    ];
                }
            }

            return $assign;
        };

        // There will be 6 potential reports for each organisation (3 * 2)
        $potential_reports = [
            'a' => [ 'data' => $generate('a', $this->organisation['a']->id) ],
            'b' => [ 'data' => $generate('b', $this->organisation['b']->id) ],
            'c' => [ 'data' => $generate('c', $this->organisation['c']->id) ]
        ];

        // Run two scenario batches - One where they all validate, and one where they all do not. This should produce 12 cases for each organisation.

        /**
         * A: All >= 2GB should have a pending report when ready assuming window is now - That's 2 
         * B: All should not pend - That's 0
         * C: All >= 10GB should have a pending report for ready assuming window is now - That's 1
         * 
         * All invalid potentials should not produce a pending or not for send.
         */

        $command = new ReportPendSingularThread;

        $command->iterateOverOrganisations(1, 0, null, function($rr, $org_id) use($potential_reports) {

            switch($org_id) {
                case $this->organisation['a']->id: return $potential_reports['a']; break;
                case $this->organisation['b']->id: return $potential_reports['b']; break;
                case $this->organisation['c']->id: return $potential_reports['c']; break;
            }

            throw new Exception("Insufficient Test Design");
        }, function($rr, $rep_id, $org_id) use($before, $now) {
            // We don't really care about this - We're just making sure the right amount of things insert with the right organisation identities.
            return [ 'valid' => true, 'destination' => Prefixes::where('organisation_id', '=', $org_id)->get()->first()->address, 'start' => $before, 'finish' => $now ];
        });

        // Now we can craft our asserts for this batch.
        $this->assertDatabaseCount('pending_reports', 3);

        // Check to make sure each organisations reports are right.
        $as = PendingReport::where([['organisation_id', '=', $this->organisation['a']->id]])->count();
        $bs = PendingReport::where([['organisation_id', '=', $this->organisation['b']->id]])->count();
        $cs = PendingReport::where([['organisation_id', '=', $this->organisation['c']->id]])->count();

        $this->assertEquals(2, $as);
        $this->assertEquals(0, $bs);
        $this->assertEquals(1, $cs);

        $command->iterateOverOrganisations(1, 0, null, function($rr, $org_id) use($potential_reports) {
            switch($org_id) {
                case $this->organisation['a']->id: return $potential_reports['a']; break;
                case $this->organisation['b']->id: return $potential_reports['b']; break;
                case $this->organisation['c']->id: return $potential_reports['c']; break;
            }

            throw new Exception("Insufficient Test Design");
        }, function($rr, $rep_id, $org_id) use($before, $now) {

            // We don't really care about this - We're just making sure the right amount of things insert with the right organisation identities.
            return [ 'valid' => false, 'destination' => Prefixes::where('organisation_id', '=', $org_id)->get()->first()->address, 'start' => $before, 'finish' => $now ];
        });

        // The values should remain the same as before.
        $this->assertDatabaseCount('pending_reports', 3);

        // The values must remain the same as before.
        $as = PendingReport::where([['organisation_id', '=', $this->organisation['a']->id]])->count();
        $bs = PendingReport::where([['organisation_id', '=', $this->organisation['b']->id]])->count();
        $cs = PendingReport::where([['organisation_id', '=', $this->organisation['c']->id]])->count();

        $this->assertEquals(2, $as);
        $this->assertEquals(0, $bs);
        $this->assertEquals(1, $cs);
    }

    public function testAttackQueueState(): void
    {
        /**
         * Org A - Will send start and finish on first hook
         * Org B - Will not send anything on either hook
         * Org C - Will not send anything on either hook
         * Org D - Will not send anything as there is no hook
         */

        DB::table('report_awareness')->insert([
            // Organisation A - Should only produce 4 webhook messages.
            [
                'created_at'        => Carbon::now(),
                'type'              => 'start',
                'status'            => 'pending',
                'report_id'         => 1,
                'organisation_id'   => $this->organisation['a']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.13'
            ],
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'pending',
                'report_id'         => 2,
                'organisation_id'   => $this->organisation['a']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.13'
            ],
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'sent',
                'report_id'         => 3,
                'organisation_id'   => $this->organisation['a']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.13'
            ],
            // Organisation B - Should produce none
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'pending',
                'report_id'         => 4,
                'organisation_id'   => $this->organisation['b']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.14'
            ],
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'sent',
                'report_id'         => 5,
                'organisation_id'   => $this->organisation['b']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.14'
            ],
            // Organisation C - Should produce none
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'pending',
                'report_id'         => 6,
                'organisation_id'   => $this->organisation['c']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.15'
            ],
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'sent',
                'report_id'         => 7,
                'organisation_id'   => $this->organisation['c']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.15'
            ],
            // Organisation D - Should produce none
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'pending',
                'report_id'         => 8,
                'organisation_id'   => $this->organisation['d']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.16'
            ],
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'sent',
                'report_id'         => 9,
                'organisation_id'   => $this->organisation['d']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.16'
            ],
            // Organisation A Mismatches - Should produce none
            [
                'created_at'        => Carbon::now(),
                'type'              => 'start',
                'status'            => 'pending',
                'report_id'         => 10,
                'organisation_id'   => $this->organisation['a']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.16'
            ],
            [
                'created_at'        => Carbon::now(),
                'type'              => 'finish',
                'status'            => 'pending',
                'report_id'         => 11,
                'organisation_id'   => $this->organisation['a']->id,
                'gbps'              => 1,
                'mpps'              => 1,
                'address'           => '103.1.213.16'
            ],
        ]);

        $this->artisan('attack-queue:state', []);

        $a_messages = WebhookMessage::where('organisation_id', '=', $this->organisation['a']->id)->count();

        $not_a_messages = WebhookMessage::where('organisation_id', '!=', $this->organisation['a']->id)->count();

        $this->assertEquals(4, $a_messages);
        $this->assertEquals(0, $not_a_messages);
    }

    public function testReportQueueSingular(): void
    {
        // Yippee - We'll have pending reports now!
        $this->testReportPendSingular();

        $this->artisan('report-queue:singular', []);

        /**
         * Verify the legitimacy of the actual generated messages and ensure the old reports are set to sent or deny
         */

        foreach([
            [ 'org_id' => $this->organisation['a']->id, 'total' => 4, 'hooks' => 2 ],
            [ 'org_id' => $this->organisation['c']->id, 'total' => 1, 'hooks' => 1 ]
        ] as $test) {
            assertTrue(WebhookMessage::where('organisation_id', '=', $test['org_id'])->count() === $test['total'], 'Organisation #' . $test['org_id'] . ' should have ' . $test['total'] . ' messages');
            assertTrue(WebhookMessage::where('organisation_id', '=', $test['org_id'])->select('webhook_id')->distinct()->get()->count() === $test['hooks'], 'Organisation #' . $test['org_id'] . ' should show ' . $test['hooks'] . ' distinct webhooks');
        }

        assertTrue(PendingReport::where('status', '=', PendingReportStatus::Sent)->get()->count() === 3, 'There should only be three sent reports - Remmeber, one 1:M report-notification relationship.');

        /**
         * We now need to test scenarios where things should absolutely fail.
         */

        DB::table('pending_reports')->insert([
            // Prefix that does not exist inside anything - Ergo never send
            [
                'created_at'        => Carbon::now(),
                'updated_at'        => Carbon::now(),
                'organisation_id'   => $this->organisation['a']->id,
                'destination'       => '201.1.2.1',
                'started_at'        => Carbon::now()->subHour(),
                'finished_at'       => Carbon::now(),
                'status'            => PendingReportStatus::Pending,
                'report_type'       => PendingReportType::Singular,
                'report_id'         => '123431'
            ],
            // Explicitly owned by someone else - Never send
            [
                'created_at'        => Carbon::now(),
                'updated_at'        => Carbon::now(),
                'organisation_id'   => $this->organisation['a']->id,
                'destination'       => '103.1.213.16',
                'started_at'        => Carbon::now()->subHour(),
                'finished_at'       => Carbon::now(),
                'status'            => PendingReportStatus::Pending,
                'report_type'       => PendingReportType::Singular,
                'report_id'         => '123432'
            ],
            // Implicitly owned by someone else - Never send
            [
                'created_at'        => Carbon::now(),
                'updated_at'        => Carbon::now(),
                'organisation_id'   => $this->organisation['c']->id,
                'destination'       => '100.2.2.10',
                'started_at'        => Carbon::now()->subHour(),
                'finished_at'       => Carbon::now(),
                'status'            => PendingReportStatus::Pending,
                'report_type'       => PendingReportType::Singular,
                'report_id'         => '123433'
            ],
            // Not for send - Never send
            [
                'created_at'        => Carbon::now(),
                'updated_at'        => Carbon::now(),
                'organisation_id'   => $this->organisation['c']->id,
                'destination'       => '100.2.2.10',
                'started_at'        => Carbon::now()->subHour(),
                'finished_at'       => Carbon::now(),
                'status'            => PendingReportStatus::NotForSend,
                'report_type'       => PendingReportType::Singular,
                'report_id'         => '123434'
            ],
        ]);

        $this->artisan('report-queue:singular', []);

        assertTrue(WebhookMessage::count() === 5, 'Webhook messages are being generated when they should not be. Dangerous.');

    }

    public function testHookReportSend(): void
    {

        // Use the notifications generated in the testReportQueueSingular function
        $this->testReportQueueSingular();

        $this->artisan('hook:report-send', []);

        Http::assertSent(function($x) {
            if(str_contains($x->data()['text'], '103.1.213.13') || str_contains($x->data()['text'], '103.1.213.15')) {
                if(str_contains($x->url(), 'a/1') || str_contains($x->url(), 'a/3') || str_contains($x->url(), 'c/1')) {
                    return true;
                }
                throw new Exception('An invalid webhook URL had a message sent to it.');
            }

            throw new Exception('An invalid IP address was mentioned in a message.');
        });

        // Check that they don't send again.
        Http::fake([
            'http://localhost:56000/hook/*' => Http::response([], 200),
            '*' => Http::response([ 'Fallback Error' ], 500) // Fallback
        ]);
        
        $this->artisan('hook:report-send', []);

        Http::assertNothingSent();

        // Now we need to queue up a bunch of notifications and ensure they only go to the right places.

        $notifications_full_test = [
            'http://localhost:56000/hook/a/1' => [
                'ids' => [ 'a1-1', 'a1-2', 'a1-3' ],
                'count_sent' => 0
            ],
            'http://localhost:56000/hook/a/2' => [
                'ids' => [ ],
                'count_sent' => 0
            ],
            'http://localhost:56000/hook/b/1' => [
                'ids' => [ 'b1-1', 'b1-2' ],
                'count_sent' => 0
            ],
            'http://localhost:56000/hook/b/2' => [
                'ids' => [ ],
                'count_sent' => 0
            ],
            'http://localhost:56000/hook/c/1' => [
                'ids' => [ 'c1-1' ],
                'count_sent' => 0
            ]
        ];

        // Hook A1 - These will all send except for the last one
        (new WebhookMessage([
            'key'               => 'a1-1',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['a1-1'],
            'webhook_id'        => $this->webhooks['a']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['a']['id']
        ]))->save();
        
        (new WebhookMessage([
            'key'               => 'a1-2',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['a1-2'],
            'webhook_id'        => $this->webhooks['a']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['a']['id']
        ]))->save();

        (new WebhookMessage([
            'key'               => 'a1-3',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['a1-3'],
            'webhook_id'        => $this->webhooks['a']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['a']['id']
        ]))->save();

        (new WebhookMessage([
            'key'               => 'a1-4',
            'message_type'      => WebhookUsageType::Other1->value,
            'payload'           => ['a1-4'],
            'webhook_id'        => $this->webhooks['a']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['a']['id']
        ]))->save();

        // Hook A2 - These will not send even if they are correctly queued up
        (new WebhookMessage([
            'key'               => 'a2-1',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['a2-1'],
            'webhook_id'        => $this->webhooks['a']['2']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['a']['id']
        ]))->save();
        
        (new WebhookMessage([
            'key'               => 'a2-2',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['a2-2'],
            'webhook_id'        => $this->webhooks['a']['2']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['a']['id']
        ]))->save();

        // Hook B1 - Both send
        (new WebhookMessage([
            'key'               => 'b1-1',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['b1-1'],
            'webhook_id'        => $this->webhooks['b']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['b']['id']
        ]))->save();
        
        (new WebhookMessage([
            'key'               => 'b1-2',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['b1-2'],
            'webhook_id'        => $this->webhooks['b']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['b']['id']
        ]))->save();

        // Hook B2 - Nothing should send
        (new WebhookMessage([
            'key'               => 'b2-1',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['b2-1'],
            'webhook_id'        => $this->webhooks['b']['2']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['b']['id']
        ]))->save();

        // Hook C1 - First will, second won't
        (new WebhookMessage([
            'key'               => 'c1-1',
            'message_type'      => WebhookUsageType::Reporting->value,
            'payload'           => ['c1-1'],
            'webhook_id'        => $this->webhooks['c']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['c']['id']
        ]))->save();
        
        (new WebhookMessage([
            'key'               => 'c1-2',
            'message_type'      => WebhookUsageType::Other2->value,
            'payload'           => ['c1-2'],
            'webhook_id'        => $this->webhooks['c']['1']['id'],
            'status'            => WebhookMessageStatus::Pending->value,
            'sent_at'           => null,
            'organisation_id'   => $this->organisation['c']['id']
        ]))->save();

        // Send
        $this->artisan('hook:report-send', []);

        Http::assertSent(function($x) use(&$notifications_full_test) {

            if(array_search($x->data()['text'], $notifications_full_test[$x->url()]['ids']) !== false) {
                $notifications_full_test[$x->url()]['count_sent'] += 1;
                return true;
            } else {
                throw new Exception('Something sent when it should not have.');
            }

        });

        foreach($notifications_full_test as $test) {
            $this->assertEquals(sizeof($test['ids']), $test['count_sent'], 'The amount of messages sent does not align with what is expected.');
        }


    }
}
