<?php

namespace Tests\Feature;

use App\Mail\AsnApproval;
use App\Models\Asns;
use App\Models\AsSets;
use App\Models\CodeAsnClaim;
use App\Models\Group;
use App\Models\Organisation;
use App\Models\Prefixes;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;


class OrganisationModuleTest extends TestCase
{

    /**
     *      @@@ TEST CERTIFICATION @@@
     *      This set of tests has been confirmed to be functioning as intended - Minus the session deletions for ASNs.
     *       
     *      Jesse Cunningham, 2026-08-17 4:30 PM AEST
     */

    use RefreshDatabase;

    private $organisations = [];
    private $users = [];
    private $asns = [];
    private $prefixes = [];
    private $approvals = [];
    private $counter = 0;

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

        $this->approvals = [];

        Mail::fake();

        Http::fake([

            // We're only looking for emails with this set of functionality

            // Valid Email
            '*:52020/asn?asn=AS65536'   => Http::response(['result' => "RegDate: 2025-01-01 creatia@globalsecurelayer.com creatia@globalsecurelayer.com,creatia@globalsecurelayer.com creatia+1@globalsecurelayer.com"], 200),

            // No Email
            '*:52020/asn?asn=AS65537'   => Http::response(['result' => 'RegDate: 2025-01-01'], 200),

            // Owned by GSL
            '*:52020/asn?asn=AS65538'   => Http::response(['result' => 'RegDate: 2025-01-01 test@test.io'], 200),

            // Not owned by GSL
            '*:52020/asn?asn=AS65539'   => Http::response(['result' => 'RegDate: 2025-01-01 a@b.io'], 200),

            // For re-sending
            '*:52020/asn?asn=AS64515'   => Http::response(['result' => 'RegDate: 2025-01-01 a@b.io'], 200),

            // Forbidden ASNs
            '*:52020/asn?asn=AS137409'  => Http::response(['result' => 'RegDate: 2025-01-01 test@test.io'], 200),
            '*:52020/asn?asn=AS136510'  => Http::response(['result' => 'RegDate: 2025-01-01 test@test.io'], 200),
            '*:52020/asn?asn=AS7578'    => Http::response(['result' => 'RegDate: 2025-01-01 test@test.io'], 200),

            // ASN That's too old
            '*:52020/asn?asn=AS65540'   => Http::response(['result' => "RegDate: 1997-01-01 creatia@globalsecurelayer.com creatia@globalsecurelayer.com,creatia@globalsecurelayer.com creatia+1@globalsecurelayer.com"], 200),

            // ASN with banned email
            '*:52020/asn?asn=AS65541'   => Http::response(['result' => "RegDate: 2007-01-01 test@gmail.com creatia+1@globalsecurelayer.com"], 200),

            // AS-SETs
            '*/radb/search?query-string=AS-TEST&format=json' => Http::response([
                'objects' => [
                    'object' => [
                        [ 'attributes' => [ 'attribute' => true ] ]
                    ]
                ]
            ], 200),
            '*/radb/search?*query-string=AS65536%3AAS-TEST-1*' => Http::response([
                'objects' => [
                    'object' => [
                        [ 'attributes' => [ 'attribute' => true ] ]
                    ]
                ]
            ], 200),
            '*/radb/search?query-string=AS65536%3AAS-TEST-2&format=json' => Http::response([], 200),
            '*/radb/search?query-string=AS64515%3AAS-TEST-1&format=json' => Http::response([
                'objects' => [
                    'object' => [
                        [ 'attributes' => [ 'attribute' => true ] ]
                    ]
                ]
            ], 200),  

            env('compute_microservice_url') . '/api/peers*' => function($request) {

                if($request->method() === 'GET') {

                    return Http::response(
                        json_encode(array_slice(json_decode(file_get_contents(app_path('Models/ComputeListTest.json'))), $request->data()['skip'], $request->data()['limit'])),
                        200
                    );

                }
                else {
                    $this->counter++;

                    return Http::response([], 200);
                }
                
            },

            env('ENDPOINT_MERCURY_FORWARD') . '/tunnel*' => Http::response([], 200),

            env('ENDPOINT_MERCURY_FORWARD') . '/refresh*' => Http::response([], 200),

            '*' => Http::response([ 'Fallback Error' ], 500)
        ]);

        // Setup organisations
        $this->organisations = [
            'A' => Organisation::create([ 'id' => 1, 'name' => 'A' ]),
            'B' => Organisation::create([ 'id' => 2, 'name' => 'B' ]),
            'C' => Organisation::create([ 'id' => 3, 'name' => 'C' ]),
            'D' => Organisation::create([ 'id' => 4, 'name' => 'D' ])
        ];

        // Setup users
        $group = Group::create([ 'name' => 'Test' ]);

        $default_user = [
            'phone'             => null,
            'password'          => '',
            'group_id'          => $group->id,
        ];

        $this->users = [
            'A' => User::create([ ...$default_user, 'organisation_id' => $this->organisations['A']->id, 'name' => 'A', 'email' => 'creatia+1@globalsecurelayer.com' ]),
            'B' => User::create([ ...$default_user, 'organisation_id' => $this->organisations['B']->id, 'name' => 'B', 'email' => 'creatia+2@globalsecurelayer.com' ]),
            'C' => User::create([ ...$default_user, 'organisation_id' => $this->organisations['C']->id, 'name' => 'C', 'email' => 'creatia+3@globalsecurelayer.com' ]),
            'D' => User::create([ ...$default_user, 'organisation_id' => $this->organisations['D']->id, 'name' => 'D', 'email' => 'creatia+4@globalsecurelayer.com' ])
        ];

        // Setup ASNs
        $this->asns = [
            'A' => Asns::create([ 'asn' => 65538, 'organisation_id' => $this->organisations['A']->id, 'approved' => 1 ]),
            'B' => Asns::create([ 'asn' => 65539, 'organisation_id' => $this->organisations['B']->id, 'approved' => 1 ]),
            'C' => Asns::create([ 'asn' => 64514, 'organisation_id' => $this->organisations['C']->id, 'approved' => 1 ]),
            'D' => Asns::create([ 'asn' => 64515, 'organisation_id' => $this->organisations['D']->id, 'approved' => 0 ])
        ];

        Asns::create([ 'asn' => 137409, 'organisation_id' => $this->organisations['A']->id, 'approved' => 1 ]);
        Asns::create([ 'asn' => 136510, 'organisation_id' => $this->organisations['A']->id, 'approved' => 1 ]);
        Asns::create([ 'asn' => 7578, 'organisation_id' => $this->organisations['A']->id, 'approved' => 1 ]);

        $this->prefixes = [
            'A' => [
                1     => Prefixes::factory()->address('111.0.0.0', 20)->ownership($this->organisations['A'], $this->asns['A'])->inline(false)->flags(3, false)->firewall(null)->announced()->create(),
                2     => Prefixes::factory()->address('112.0.0.0', 20)->ownership($this->organisations['A'], $this->asns['A'])->inline(true)->flags(3, false)->firewall(null)->announced()->create()
            ],
            'B' => [
                1     => Prefixes::factory()->address('113.0.0.0', 20)->ownership($this->organisations['B'], $this->asns['B'])->inline(false)->flags(3, false)->firewall(null)->announced()->create(),
                2     => Prefixes::factory()->address('114.0.0.0', 20)->ownership($this->organisations['B'], $this->asns['B'])->inline(true)->flags(3, false)->firewall(null)->announced()->create()
            ],
            'C' => [
                1     => Prefixes::factory()->address('115.0.0.0', 20)->ownership($this->organisations['C'], $this->asns['C'])->inline(false)->flags(3, false)->firewall(null)->announced()->create(),
                2     => Prefixes::factory()->address('116.0.0.0', 20)->ownership($this->organisations['C'], $this->asns['C'])->inline(true)->flags(3, false)->firewall(null)->announced()->create()
            ],
            'D' => [
                1     => Prefixes::factory()->address('117.0.0.0', 20)->ownership($this->organisations['D'], $this->asns['D'])->inline(false)->flags(3, false)->firewall(null)->announced()->create(),
                2     => Prefixes::factory()->address('118.0.0.0', 20)->ownership($this->organisations['D'], $this->asns['D'])->inline(true)->flags(3, false)->firewall(null)->announced()->create()
            ]
        ];

    }

    public function testPostAsn() {

        /**
         * There are a variety of scenarios to test when posting an ASN.
         * 
         * 1. An email is found in the WHO-IS list
         *      A. The ASN does not yet exist
         *      B. The ASN exists and is owned by primary organisation.
         *          1. If in the never-touch list, nothing should happen.
         *      C. The ASN exists and is not owned by the primary organisation or the user. Transfer should be allowed, but ONLY through invitation.
         *      D. The ASN exists and is owned by the organisation of the user.
         * 2. An email is not
         * 3. An email is from a banned domain
         * 4. An email is too old
         */

        /** Scenario 1A **/

        // Check ASN does not yet exist.
        $this->assertTrue(Asns::where('asn', '=', '65536')->count() === 0, 'This ASN should not yet exist.');

        $this->actingAs($this->users['B'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'creatia@globalsecurelayer.com',
            'asn'       => 'AS65536'
        ])->json();

        // Check whether the ASN entry exists, the code claim exists, and the email was / would be sent.
        $asn = Asns::where('asn', '=', '65536')->get()->first();

        $this->assertTrue($asn !== null, 'The ASN needs to exist after this request.');
        $this->assertTrue($asn->approved == 0, 'The ASN should not be approved yet.');
        $this->assertTrue($asn->organisation_id == $this->users['B']->organisation_id, 'The ASN should be associated with the creating users organisation.');

        $code_claim = CodeAsnClaim::where('asn_id', '=', $asn->id)->get()->first();

        $this->assertTrue($code_claim !== null, 'There should be a code claim.');
        $this->assertTrue($code_claim->expiry_at !== null, 'There should be an unexpired code claim.');
        $this->assertTrue($code_claim->organisation_id == $this->users['B']->organisation_id, 'The organisation ID on the code claim must match the creating users organisation.');

        Mail::assertSent(AsnApproval::class, function ($mail) {
            $this->approvals['AS65536'] = $mail->code;
            return $mail->hasTo('creatia@globalsecurelayer.com');
        });

        Mail::fake();

        /** Scenario 1B **/
        $this->assertTrue(Asns::where('asn', '=', '65538')->count() === 1, 'This ASN should already exist.');

        $this->actingAs($this->users['B'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'test@test.io',
            'asn'       => 'AS65538'
        ])->json();

        // Check whether the ASN entry exists, the code claim exists, and the email was / would be sent.
        $this->assertTrue(Asns::where('asn', '=', '65538')->count() === 1, 'This ASN should only have one entry.');

        $asn = Asns::where('asn', '=', '65538')->get()->first();

        $this->assertTrue($asn !== null, 'The ASN needs to exist after this request.');
        $this->assertTrue($asn->approved == 1, 'The ASN should already be approved because it should not yet re-assign to the requesting user.');
        $this->assertTrue($asn->organisation_id == $this->users['A']->organisation_id, 'The ASN should be associated with the primary organisation until the code is used.');

        $code_claim = CodeAsnClaim::where('asn_id', '=', $asn->id)->get()->first();

        $this->assertTrue($code_claim !== null, 'There should be a code claim.');
        $this->assertTrue($code_claim->expiry_at !== null, 'There should be an unexpired code claim.');
        $this->assertTrue($code_claim->organisation_id == $this->users['B']->organisation_id, 'The organisation ID on the code claim must match the creating users organisation.');

        Mail::assertSent(AsnApproval::class, function ($mail) {
            $this->approvals['AS65538'] = $mail->code;
            return $mail->hasTo('test@test.io');
        });

        Mail::fake();

        /** Scenario 1B.1 **/

        foreach(['137409', '136510', '7578'] as $forbidden) {
            $this->assertTrue(Asns::where('asn', '=', $forbidden)->count() === 1, 'This ASN should already exist.');

            $this->actingAs($this->users['B'])->withoutMiddleware()->post('/api/organisation/asn', [
                'email'     => 'test@test.io',
                'asn'       => "AS$forbidden"
            ])->json();

            // Check whether the ASN entry exists, the code claim exists, and the email was / would be sent.
            $this->assertTrue(Asns::where('asn', '=', $forbidden)->count() === 1, 'This ASN should only have one entry.');

            $asn = Asns::where('asn', '=', $forbidden)->get()->first();

            $this->assertTrue($asn !== null, 'The ASN needs to exist after this request.');
            $this->assertTrue($asn->approved == 1, 'The ASNs approval status should not have changed.');
            $this->assertTrue($asn->organisation_id == $this->users['A']->organisation_id, 'The ASN should be associated with the primary organisation.');

            $code_claim = CodeAsnClaim::where('asn_id', '=', $asn->id)->get()->first();

            $this->assertTrue($code_claim === null, 'There should not be a code claim.');

            Mail::assertNotSent(AsnApproval::class, function ($mail) {
                return $mail->hasTo('test@test.io');
            });

            Mail::fake();
        }

        /** Scenario 1C **/
        $this->assertTrue(Asns::where('asn', '=', '65539')->count() === 1, 'This ASN should already exist.');

        $this->actingAs($this->users['C'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'a@b.io',
            'asn'       => 'AS65539'
        ])->json();

        // Check whether the ASN entry exists, the code claim exists, and the email was / would be sent.
        $this->assertTrue(Asns::where('asn', '=', '65539')->count() === 1, 'This ASN should only have one entry.');

        $asn = Asns::where('asn', '=', '65539')->get()->first();

        $this->assertTrue($asn !== null, 'The ASN needs to exist after this request.');
        $this->assertTrue($asn->approved == 1, 'The ASN should already be approved because it should not yet re-assign to the requesting user.');
        $this->assertTrue($asn->organisation_id == $this->users['B']->organisation_id, 'The ASN should be associated with the owning organisation.');

        $code_claim = CodeAsnClaim::where('asn_id', '=', $asn->id)->get()->first();

        $this->assertTrue($code_claim !== null, 'There should be a code claim - We do allow ORG <-> ORG transfers now.');

        Mail::assertSent(AsnApproval::class, function ($mail) {
            return $mail->hasTo('a@b.io');
        });

        Mail::fake();

        /** Scenario 1D **/
        $this->assertTrue(Asns::where('asn', '=', '64515')->count() === 1, 'This ASN should already exist.');

        $this->actingAs($this->users['D'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'a@b.io',
            'asn'       => 'AS64515'
        ])->json();

        // Check whether the ASN entry exists, the code claim exists, and the email was / would be sent.
        $asn = Asns::where('asn', '=', '64515')->get()->first();

        $this->assertTrue($asn !== null, 'The ASN needs to exist after this request.');
        $this->assertTrue($asn->approved == 0, 'The ASN should not be approved yet.');
        $this->assertTrue($asn->organisation_id == $this->users['D']->organisation_id, 'The ASN should be associated with the creating users organisation.');

        $code_claim = CodeAsnClaim::where('asn_id', '=', $asn->id)->get()->first();

        $this->assertTrue($code_claim !== null, 'There should be a code claim.');
        $this->assertTrue($code_claim->expiry_at !== null, 'There should be an unexpired code claim.');
        $this->assertTrue($code_claim->organisation_id == $this->users['D']->organisation_id, 'The organisation ID on the code claim must match the creating users organisation.');

        Mail::assertSent(AsnApproval::class, function ($mail) {
            $this->approvals['AS64515'] = $mail->code;
            return $mail->hasTo('a@b.io');
        });

        Mail::fake();

        /** Scenario 2 **/
        
        $this->assertTrue(Asns::where('asn', '=', '65537')->count() === 0, 'This ASN should not exist.');

        $this->assertNotEquals(200, $this->actingAs($this->users['A'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => '',
            'asn'       => '65537'
        ])->status(), 'This should fail - No email is found');

        $this->assertNotEquals(200, $this->actingAs($this->users['A'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'a@b.io',
            'asn'       => '65537'
        ])->status(), 'This should fail - No email is found');

        // Make sure an email was not sent and the ASN does not exist.
        $asn = Asns::where('asn', '=', '65537')->get()->first();

        $this->assertTrue($asn === null, 'The ASN should never exist in this scenario.');

        Mail::assertNotSent(AsnApproval::class);

        Mail::fake();

        /** Scenario 3 AS65541 */

        $this->assertTrue(Asns::where('asn', '=', '65541')->count() === 0, 'This ASN should not exist.');

        $this->assertNotEquals(200, $this->actingAs($this->users['A'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'test@gmail.com',
            'asn'       => '65541'
        ])->status(), 'This should fail - Gmail is a banned domain');


        // Make sure an email was not sent and the ASN does not exist.
        $asn = Asns::where('asn', '=', '65541')->get()->first();

        $this->assertTrue($asn === null, 'The ASN should never exist in this scenario.');

        Mail::assertNotSent(AsnApproval::class);

        Mail::fake();

        /** Scenario 4 AS65540 */

        $this->assertTrue(Asns::where('asn', '=', '65540')->count() === 0, 'This ASN should not exist.');

        $this->assertEquals(422, $this->actingAs($this->users['A'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'creatia@globalsecurelayer.com',
            'asn'       => '65540'
        ])->status(), 'This should fail - This is an old ASN');


        // Make sure an email was not sent and the ASN does not exist.
        $asn = Asns::where('asn', '=', '65540')->get()->first();

        $this->assertTrue($asn === null, 'The ASN should never exist in this scenario.');

        Mail::assertNotSent(AsnApproval::class);

        Mail::fake();

    }

    public function testApproveAsn() {

        /**
         * There are a variety of scenarios to test when approving an ASN. Building on the previous test, the following scenarios are available.
         * 
         * 1. AS65536 - A new ASN for organisation B - C attempts to steal it. They can do that now.
         * 2. AS65538 - Existing ASN for organisation A destined for B - Check it ony works for B.
         * 3. AS64515 - The ASN code has been requested again. Use the old code. Expire the old one, use the new one.
         * 
         */

        $this->testPostAsn();

        // Scenario 1

        // Setup

        $this->actingAs($this->users['C'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'creatia@globalsecurelayer.com',
            'asn'       => 'AS65536'
        ]);

        $approval_code = null;

        Mail::assertSent(AsnApproval::class, function($_m) use(&$approval_code) {
            $approval_code = $_m->code;

            return true;
        });

        Mail::fake();

        $this->assertEquals(2, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '65536')->get()->first()->id)->count(), 'There should be two ASN claims.');

        // Attempt to approve
        
        $this->actingAs($this->users['C'])->withoutMiddleware()->post('/api/organisation/asn/approval', [
            'token'     => $approval_code
        ]);

        $this->assertTrue(Asns::where([['asn', '=', '65536'], ['approved', '=', 1]])->count() === 1, 'This ASN should be approved.');
        $this->assertEquals(1, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '65536')->get()->first()->id)->count(), 'There should be one ASN claim left.');

        $this->actingAs($this->users['B'])->withoutMiddleware()->post('/api/organisation/asn/approval', [
            'token'     => $this->approvals['AS65536']
        ]);

        $this->assertTrue(Asns::where([['asn', '=', '65536'], ['approved', '=', 1]])->count() === 1, 'This ASN should be approved.');
        $this->assertEquals(0, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '65536')->get()->first()->id)->count(), 'There should still be no ASN claims left.');

        // Scenario 2 

        $this->assertTrue(Asns::where([['asn', '=', '65538'], ['approved', '=', 1], ['organisation_id', '=', $this->organisations['A']->id]])->count() === 1, 'This ASN should be approved and owned by primary.');
        $this->assertEquals(1, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '65538')->get()->first()->id)->count(), 'There should only be one ASN claim.');

        // Ensure that associated prefixes for this ASN don't go away / change - We want to make sure the clean doesn't break things

        $prefix_count_before = Prefixes::where('asn_id', '=', $this->asns['A']->id)->get()->count();

        $this->actingAs($this->users['B'])->withoutMiddleware()->post('/api/organisation/asn/approval', [
            'token'     => $this->approvals['AS65538']
        ]);

        $this->assertTrue(Asns::where([['asn', '=', '65538'], ['approved', '=', 1], ['organisation_id', '=', $this->organisations['B']->id]])->count() === 1, 'This ASN should be approved and owned by B.');
        $this->assertEquals(0, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '65538')->get()->first()->id)->count(), 'There should be no ASN claim left.');
        $this->assertEquals($prefix_count_before, Prefixes::where('asn_id', '=', $this->asns['A']->id)->get()->count(), "The number of prefixes should NOT change.");

        // Scenario 3

        // Set the time
        Carbon::setTestNow(Carbon::now()->addDays(2));

        // Attempt to use old code
        $this->actingAs($this->users['D'])->withoutMiddleware()->post('/api/organisation/asn/approval', [
            'token'     => $this->approvals['AS64515']
        ]);

        $this->assertTrue(Asns::where([['asn', '=', '64515'], ['approved', '=', 0]])->count() === 1, 'This ASN should still be unapproved.');
        $this->assertEquals(1, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '64515')->get()->first()->id)->count(), 'There should be one ASN claim.');

        // Request new code
        $this->actingAs($this->users['D'])->withoutMiddleware()->post('/api/organisation/asn', [
            'email'     => 'a@b.io',
            'asn'       => 'AS64515'
        ]);

        Mail::assertSent(AsnApproval::class, function ($mail) {
            $this->approvals['AS64515'] = $mail->code;
            return $mail->hasTo('a@b.io');
        });

        $this->assertTrue(Asns::where([['asn', '=', '64515'], ['approved', '=', 0]])->count() === 1, 'This ASN should still be unapproved.');
        $this->assertEquals(2, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '64515')->get()->first()->id)->count(), 'There should be two ASN claims.');

        // Use new code
        $this->actingAs($this->users['D'])->withoutMiddleware()->post('/api/organisation/asn/approval', [
            'token'     => $this->approvals['AS64515']
        ]);

        $this->assertTrue(Asns::where([['asn', '=', '64515'], ['approved', '=', 1], ['organisation_id', '=', $this->organisations['D']->id]])->count() === 1, 'This ASN should be approved and owned by D.');
        $this->assertEquals(1, CodeAsnClaim::where('asn_id', '=', Asns::where('asn', '=', '64515')->get()->first()->id)->count(), 'There should be one ASN claim left.');

    }

    public function testApproveTransferAsnClean() {

        // Make assumptions about our environment first to ensure they're correct

        $this->assertCount(1, Asns::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be one ASN associated with organisation B.');
        $this->assertCount(2, Prefixes::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be two prefixes associated with organisation B.');
        
        // Make the ASN
        $asn = Asns::create([ 'asn' => 41378, 'organisation_id' => $this->organisations['B']->id, 'approved' => 1 ]);

        // Make the AS-SETs - Should delete 3
        AsSets::create([ 'as_set' => 'AS41378:AS-A', 'approved' => 1, 'organisation_id' => $this->organisations['B']->id ]);
        AsSets::create([ 'as_set' => 'AS41378:AS-B', 'approved' => 0, 'organisation_id' => $this->organisations['B']->id ]);
        AsSets::create([ 'as_set' => 'AS41378:AS-C', 'approved' => 1, 'organisation_id' => $this->organisations['B']->id ]);
        AsSets::create([ 'as_set' => 'AS413781:AS-D', 'approved' => 1, 'organisation_id' => $this->organisations['B']->id ]);
        AsSets::create([ 'as_set' => 'AS413782:AS-E', 'approved' => 1, 'organisation_id' => $this->organisations['A']->id ]);

        // Create a bunch of prefixes - These are the only ones that will be deleted. Five of them.
        // Addresses must not collide with the setUp prefixes (111-118) - prefixes carry a unique (address, mask) constraint.
        Prefixes::factory()->address('141.0.0.0', 20)->ownership($this->organisations['B'], $asn)->create();
        Prefixes::factory()->address('142.0.0.0', 20)->ownership($this->organisations['B'], $asn)->create();
        Prefixes::factory()->address('143.0.0.0', 20)->ownership($this->organisations['B'], $asn)->create();
        Prefixes::factory()->address('144.0.0.0', 20)->ownership($this->organisations['B'], $asn)->create();
        Prefixes::factory()->address('145.0.0.0', 20)->ownership($this->organisations['B'], $asn)->create();

        // TO-DO - Add tests for tunnels

        // Make more assumptions

        $this->assertCount(2, Asns::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be two ASNs associated with organisation B.');
        $this->assertCount(7, Prefixes::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be seven prefixes associated with organisation B.');
        $this->assertCount(4, AsSets::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be four AS-SETs associated with organisation B.');

        CodeAsnClaim::create([
            'asn_id' => $asn->id, 
            'organisation_id' => $this->organisations['C']->id,
            'token' => hash('sha256', 'Testing'),
            'expiry_at' => Carbon::now()->addDays(2)
        ]);

        $this->actingAs($this->users['C'])->withoutMiddleware()->post('/api/organisation/asn/approval', [
            'token'     => 'Testing'
        ]);

        $this->assertCount(1, Asns::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be 1 ASNs associated with organisation B.');
        $this->assertCount(2, Prefixes::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be 2 prefixes associated with organisation B.');
        $this->assertCount(1, AsSets::where('organisation_id', '=', $this->organisations['B']->id)->get(), 'There should be 1 AS-SETs associated with organisation B.');

        // Determine whether or not the appropriate amount of session deletions were lodged
        $this->assertEquals(2, $this->counter, "There should have been two deletions for sessions.");

    }

    public function testGetAsn() {

        /**
         * This is relatively simple - Just determine whether or not the correct ASNs are shown for an organisation.
         */

        $this->testApproveAsn();

        $testing_criteria = [
            'A' => [ '137409', '136510', '7578' ],
            'B' => [ '65539', '65538', '65536' ],
            'C' => [ '64514' ],
            'D' => [ '64515' ]
        ];

        foreach($testing_criteria as $organisation => $asns) {

            // Retrieve ASNs            
            $data = collect($this->actingAs($this->users[$organisation])->withoutMiddleware()->get('/api/organisation/asn?draw=0&start=0&length=100&order_direction=asc&order_column=asn')->json()['data'])->pluck('asn');

            $should_find = sizeof($asns);
            $did_find = 0;

            foreach($asns as $asn) {
                if(array_search($asn, $data->toArray()) !== false) {
                    ++$did_find;
                }
            }

            $this->assertEquals($should_find, $did_find, "You should have found `$should_find` but you found `$did_find` - ASN return mismatch.");

        }

    }

    public function testAdminDeleteAsn() {

        /**
         * This is for testing whether an administrator can delete any ASN via the admin route.
         *
         * 1. An approved ASN owned by another organisation can be deleted - It should be soft deleted only.
         *      A. Associated prefixes must be cleaned alongside it.
         *      B. AS-SETs nested under the ASN must be deleted alongside it - Approved or not.
         *      C. Unrelated ASNs, prefixes and AS-SETs must NOT be touched.
         * 2. An unapproved ASN can also be deleted.
         * 3. The never-touch ASNs can NOT be deleted.
         * 4. A non-existent ASN identity is rejected.
         * 5. An already-deleted ASN identity is rejected.
         * 6. A non-numeric ASN identity never matches the route.
         */

        /** Scenario 1 **/

        $asn_count_before = Asns::count();
        $bystander_prefix_count = Prefixes::where('asn_id', '=', $this->asns['A']->id)->count();

        // AS-SETs nested under the ASN to be deleted - Both should be deleted with it (see 1B).
        $nested_as_sets = [
            AsSets::create([ 'as_set' => 'AS65539:AS-A', 'approved' => 1, 'organisation_id' => $this->organisations['B']->id ]),
            AsSets::create([ 'as_set' => 'AS65539:AS-B', 'approved' => 0, 'organisation_id' => $this->organisations['B']->id ])
        ];

        // An AS-SET nested under a different ASN - It should survive (see 1C).
        $bystander_as_set = AsSets::create([ 'as_set' => 'AS65538:AS-C', 'approved' => 1, 'organisation_id' => $this->organisations['A']->id ]);

        $this->assertTrue(Prefixes::where('asn_id', '=', $this->asns['B']->id)->count() > 0, 'This ASN should have associated prefixes before deletion.');

        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asn/' . $this->asns['B']->id);

        $this->assertEquals(200, $scenario_1->status(), 'This should have definitely succeeded.');
        $this->assertTrue(Asns::find($this->asns['B']->id) === null, 'The ASN should no longer be retrievable.');
        $this->assertTrue(Asns::withTrashed()->find($this->asns['B']->id) !== null, 'The ASN should be soft deleted, NOT hard deleted.');

        /** Scenario 1A **/

        $this->assertEquals(0, Prefixes::where('asn_id', '=', $this->asns['B']->id)->count(), 'The prefixes for this ASN should have been cleaned.');

        /** Scenario 1B **/

        foreach($nested_as_sets as $nested_as_set) {
            $this->assertTrue(AsSets::find($nested_as_set->id) === null, 'The nested AS-SET should be deleted with the ASN.');
            $this->assertTrue(AsSets::withTrashed()->find($nested_as_set->id) !== null, 'The nested AS-SET should be soft deleted, NOT hard deleted.');
        }

        /** Scenario 1C **/

        $this->assertEquals($asn_count_before - 1, Asns::count(), 'Exactly one ASN should have been deleted.');
        $this->assertEquals($bystander_prefix_count, Prefixes::where('asn_id', '=', $this->asns['A']->id)->count(), 'Unrelated prefixes should NOT be touched.');
        $this->assertTrue(AsSets::find($bystander_as_set->id) !== null, 'AS-SETs nested under other ASNs should NOT be touched.');

        /** Scenario 2 **/

        $this->assertTrue($this->asns['D']->approved == 0, 'This ASN should be unapproved.');

        $scenario_2 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asn/' . $this->asns['D']->id);

        $this->assertEquals(200, $scenario_2->status(), 'This should have definitely succeeded.');
        $this->assertTrue(Asns::find($this->asns['D']->id) === null, 'The ASN should no longer be retrievable.');
        $this->assertEquals(0, Prefixes::where('asn_id', '=', $this->asns['D']->id)->count(), 'The prefixes for this ASN should have been cleaned.');

        /** Scenario 3 **/

        foreach([137409, 136510, 7578] as $forbidden) {

            $forbidden_asn = Asns::where('asn', '=', $forbidden)->first();

            $this->assertTrue($forbidden_asn !== null, 'The never-touch ASN should exist before the attempt.');

            $scenario_3 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asn/' . $forbidden_asn->id);

            $this->assertEquals(500, $scenario_3->status(), 'This should have definitely failed - Never-touch ASNs must not be deletable.');
            $this->assertTrue(Asns::find($forbidden_asn->id) !== null, 'The never-touch ASN should remain untouched.');
        }

        /** Scenario 4 **/

        $this->assertTrue(Asns::withTrashed()->find(999999) === null, 'This ASN should never exist.');

        $scenario_4 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asn/999999');

        $this->assertEquals(500, $scenario_4->status(), 'This should have definitely failed - The identity does not exist.');

        /** Scenario 5 **/

        $scenario_5 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asn/' . $this->asns['B']->id);

        $this->assertEquals(500, $scenario_5->status(), 'This should have definitely failed - The identity is already deleted.');

        /** Scenario 6 **/

        $scenario_6 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asn/not-a-number');

        $this->assertEquals(404, $scenario_6->status(), 'This should never match the route - The identity must be numeric.');

        // Nothing else should have been deleted by the failing scenarios.
        $this->assertEquals($asn_count_before - 2, Asns::count(), 'Exactly two ASNs should have been deleted in total.');

    }

    public function testPostAsSet()
    {
        /**
         * This is for testing whether AS-SETs can be added.
         * 
         * - Organisation A can't add an invalid AS-SET.
         * - Organisation B can add a valid AS-SET.
         * - Organisation B attempts to add a non-existing AS-SET.
         * - Organisation D tries to add a B ASNs AS-SET.
         * - Organisation D tries to add a valid AS-SET.
         * 
         */

        $this->testGetAsn();

        // Scenario 1

        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->post('/api/organisation/asset/', [
            'as_set'       => 'AS-TEST'
        ]);

        $this->assertEquals(500, $scenario_1->status(), 'This should have definitely failed.');

        $this->assertEquals(0, AsSets::where('as_set', '=', 'AS-TEST')->count(), 'This AS-SET should NEVER be added.');

        // Scenario 2
        $scenario_2 = $this->actingAs($this->users['B'])->withoutMiddleware()->post('/api/organisation/asset/', [
            'as_set'       => 'AS65536:AS-TEST-1'
        ]);

        $this->assertEquals(200, $scenario_2->status(), 'This should have definitely succeeded.');

        $as_set = AsSets::where('as_set', '=', 'AS65536:AS-TEST-1')->get()->first();

        $this->assertTrue($as_set !== null, 'This AS-SET should exist.');
        $this->assertTrue($as_set->organisation_id == $this->users['B']->organisation_id, 'It should belong to the correct organisation.');

        // Scenario 3
        $scenario_3 = $this->actingAs($this->users['B'])->withoutMiddleware()->post('/api/organisation/asset/', [
            'as_set'       => 'AS65536:AS-TEST-2'
        ]);

        $this->assertEquals(200, $scenario_3->status(), 'This should have definitely succeeded.');

        $as_set = AsSets::where('as_set', '=', 'AS65536:AS-TEST-2')->get()->first();

        $this->assertTrue($as_set !== null, 'This AS-SET should exist.');

        // Scenario 4
        $scenario_4 = $this->actingAs($this->users['D'])->withoutMiddleware()->post('/api/organisation/asset/', [
            'as_set'       => 'AS65536:AS-TEST-1'
        ]);

        $this->assertEquals(500, $scenario_4->status(), 'This should have definitely failed.');

        $as_set = AsSets::where('as_set', '=', 'AS65536:AS-TEST-1')->get()->first();

        $this->assertTrue($as_set !== null, 'This AS-SET should still exist.');
        $this->assertTrue($as_set->organisation_id == $this->users['B']->organisation_id, 'It should belong to the correct organisation.');

        // Scenario 5
        $scenario_5 = $this->actingAs($this->users['D'])->withoutMiddleware()->post('/api/organisation/asset/', [
            'as_set'       => 'AS64515:AS-TEST-1'
        ]);

        $this->assertEquals(200, $scenario_5->status(), 'This should have definitely succeeded.');

        $as_set = AsSets::where('as_set', '=', 'AS64515:AS-TEST-1')->get()->first();

        $this->assertTrue($as_set !== null, 'This AS-SET should still exist.');
        $this->assertTrue($as_set->organisation_id == $this->users['D']->organisation_id, 'It should belong to the correct organisation.');

    }

    public function testGetAsSet() {
        /**
         * This is relatively simple - Just determine whether or not the correct ASNs are shown for an organisation.
         */

        $this->testPostAsSet();

        $testing_criteria = [
            'A' => [],
            'B' => [ 'AS65536:AS-TEST-1' ],
            'C' => [],
            'D' => [ 'AS64515:AS-TEST-1' ]
        ];

        foreach($testing_criteria as $organisation => $as_sets) {

            // Retrieve ASNs            
            $data = collect($this->actingAs($this->users[$organisation])->withoutMiddleware()->get('/api/organisation/asset?draw=0&start=0&length=100&order_direction=asc&order_column=as_set')->json()['data'])->pluck('as_set');

            $should_find = sizeof($as_sets);
            $did_find = 0;

            foreach($as_sets as $as_set) {
                if(array_search($as_set, $data->toArray()) !== false) {
                    ++$did_find;
                }
            }

            $this->assertEquals($should_find, $did_find, "You should have found `$should_find` but you found `$did_find` - AS-SET return mismatch for Org `$organisation`.");
        }
    }

    public function testAdminDeleteAsSet() {

        /**
         * This is for testing whether an administrator can delete any AS-SET via the admin route.
         *
         * 1. An approved AS-SET owned by another organisation can be deleted - It should be soft deleted only.
         *      A. Unrelated AS-SETs must NOT be touched.
         * 2. An unapproved AS-SET can also be deleted.
         * 3. A non-existent AS-SET identity is rejected.
         * 4. An already-deleted AS-SET identity is rejected.
         * 5. A non-numeric AS-SET identity never matches the route.
         */

        // Setup - The base environment holds no AS-SETs, so make our own.

        $as_sets = [
            'approved'      => AsSets::create([ 'as_set' => 'AS65539:AS-A', 'approved' => 1, 'organisation_id' => $this->organisations['B']->id ]),
            'unapproved'    => AsSets::create([ 'as_set' => 'AS65539:AS-B', 'approved' => 0, 'organisation_id' => $this->organisations['B']->id ]),
            'bystander'     => AsSets::create([ 'as_set' => 'AS65538:AS-C', 'approved' => 1, 'organisation_id' => $this->organisations['A']->id ])
        ];

        $as_set_count_before = AsSets::count();

        /** Scenario 1 **/

        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asset/' . $as_sets['approved']->id);

        $this->assertEquals(200, $scenario_1->status(), 'This should have definitely succeeded.');
        $this->assertTrue(AsSets::find($as_sets['approved']->id) === null, 'The AS-SET should no longer be retrievable.');
        $this->assertTrue(AsSets::withTrashed()->find($as_sets['approved']->id) !== null, 'The AS-SET should be soft deleted, NOT hard deleted.');

        /** Scenario 1A **/

        $this->assertEquals($as_set_count_before - 1, AsSets::count(), 'Exactly one AS-SET should have been deleted.');
        $this->assertTrue(AsSets::find($as_sets['bystander']->id) !== null, 'Unrelated AS-SETs should NOT be touched.');

        /** Scenario 2 **/

        $scenario_2 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asset/' . $as_sets['unapproved']->id);

        $this->assertEquals(200, $scenario_2->status(), 'This should have definitely succeeded.');
        $this->assertTrue(AsSets::find($as_sets['unapproved']->id) === null, 'The AS-SET should no longer be retrievable.');

        /** Scenario 3 **/

        $this->assertTrue(AsSets::withTrashed()->find(999999) === null, 'This AS-SET should never exist.');

        $scenario_3 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asset/999999');

        $this->assertEquals(500, $scenario_3->status(), 'This should have definitely failed - The identity does not exist.');

        /** Scenario 4 **/

        $scenario_4 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asset/' . $as_sets['approved']->id);

        $this->assertEquals(500, $scenario_4->status(), 'This should have definitely failed - The identity is already deleted.');

        /** Scenario 5 **/

        $scenario_5 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete('/api/admin/organisation/asset/not-a-number');

        $this->assertEquals(404, $scenario_5->status(), 'This should never match the route - The identity must be numeric.');

        // Nothing else should have been deleted by the failing scenarios.
        $this->assertEquals($as_set_count_before - 2, AsSets::count(), 'Exactly two AS-SETs should have been deleted in total.');

    }

    public function testListAsnEmails() {

        foreach([
            [ $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/organisation/asn/email?asn=AS65536'), ['creatia@globalsecurelayer.com', 'creatia+1@globalsecurelayer.com'] ],
            [ $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/organisation/asn/email?asn=AS65537'), [] ],
            [ $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/organisation/asn/email?asn=AS65538'), ['test@test.io'] ],
            [ $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/organisation/asn/email?asn=AS65539'), ['a@b.io'] ],
            [ $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/organisation/asn/email?asn=AS65539'), ['a@b.io'] ],
        ] as $response) {

            $found = 0;

            foreach($response[1] as $expected) {
                if(array_search($expected, $response[0]->json()) !== false) {
                    ++$found;
                }
            }

            $this->assertTrue($found === sizeof($response[1]), 'The correct emails were not returned');

        }

    }



}
