<?php

namespace Tests\Feature;

use App\Http\Controllers\Network\AdminApiController;
use App\Http\Controllers\Network\AdminNetworkController;
use App\Http\Controllers\Network\NetworkController;
use App\Models\Asns;
use App\Models\Group;
use App\Models\Organisation;
use App\Models\Prefixes;
use App\Models\PrefixSubtenants;
use App\Models\Suborganisation;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class NetworkModuleTest extends TestCase
{

    /**
     *      @@@ TEST CERTIFICATION @@@
     *      This set of tests has been confirmed to be functioning as intended.
     *       
     *      Jesse Cunningham, 2026-04-21 4:00 PM AEST
     */

    use RefreshDatabase;

    private $organisations = [];
    private $users = [];
    private $asns = [];
    private $prefixes = [];

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

        // Setup two top level organisations and two suborganisations.
        $this->organisations = [
            'A' => Organisation::create([ 'id' => 11, 'name' => 'A' ]),
            'B' => Organisation::create([ 'id' => 2, 'name' => 'B' ]),
            'C' => Organisation::create([ 'id' => 3, 'name' => 'C' ]),
            'D' => Organisation::create([ 'id' => 4, 'name' => 'D' ]),
            'E' => Organisation::create([ 'id' => 5, 'name' => 'E' ])
        ];

        Suborganisation::insert([
            [ 'parent_id' => 11 , 'child_id' => 3 ],
            [ 'parent_id' => 2 , 'child_id' => 4 ],
            [ 'parent_id' => 2 , 'child_id' => 5 ]
        ]);

        // 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' => 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 testGetSubtenants() {
        /**
         * The idea of this is you need to be able to retrieve a list of subtenants associated with a given prefix, as well as options.
         */

        // Scenario 1 - Retrieve information from a prefix that has no subtenants - There should be exactly 2 subtenants (Organisation B, Prefix B1)
        $scenario_1 = $this->actingAs($this->users['B'])->withoutMiddleware()->get('/api/network/prefix/' . $this->prefixes['B']['1']->id . '/subtenant');

        $scenario_1_data = $scenario_1->json();

        $this->assertTrue($scenario_1->status() === 200, 'The status for scenario 1 should be 200');
        $this->assertEquals(sizeof($scenario_1_data['data']), 2, 'The amount of subtenants returned for scenario 1 is wrong.');
        $this->assertEquals(collect($scenario_1_data['data'])->whereIn('value', [4, 5])->count(), 2, 'The subtenants returned for scenario 1 are wrong.');
        $this->assertEquals(sizeof($scenario_1_data['selectedPrefixOnly']), 0, 'The amount of selectedPrefixOnly entries for scenario 1 is wrong.');
        $this->assertEquals(sizeof($scenario_1_data['selectedChildrenAllowed']), 0, 'The amount of selectedPrefixOnly entries for scenario 1 is wrong.');

        // Scenario 2 - Retrieve information from a prefix that has a subtenant - There should be 1 subtenant (Organisation A, prefix A1)
        PrefixSubtenants::insert([[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 11,
            'also_subnets'      => 1
        ],[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 11,
            'also_subnets'      => 0
        ]]);

        $scenario_2 = $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/network/prefix/' . $this->prefixes['A']['1']->id . '/subtenant');

        $scenario_2_data = $scenario_2->json();

        $this->assertTrue($scenario_2->status() === 200, 'The status for scenario 2 should be 200');
        $this->assertEquals(sizeof($scenario_2_data['data']), 1, 'The amount of subtenants returned for scenario 2 is wrong.');
        $this->assertEquals(collect($scenario_2_data['data'])->whereIn('value', [3])->count(), 1, 'The subtenants returned for scenario 2 are wrong.');
        $this->assertEquals(sizeof($scenario_2_data['selectedPrefixOnly']), 1, 'The amount of selectedPrefixOnly entries for scenario 2 is wrong.');
        $this->assertEquals(sizeof($scenario_2_data['selectedChildrenAllowed']), 1, 'The amount of selectedPrefixOnly entries for scenario 2 is wrong.');

        $this->assertTrue($scenario_2->status() === 200, 'The status for scenario 2 should be 200');

        // Scenario 3 - Retrieve information from a prefix that doesn't have anything and there are no subtenants.
        $scenario_3 = $this->actingAs($this->users['C'])->withoutMiddleware()->get('/api/network/prefix/' . $this->prefixes['C']['1']->id . '/subtenant');

        $scenario_3_data = $scenario_3->json();

        $this->assertTrue($scenario_3->status() === 200, 'The status for scenario 3 should be 200');
        $this->assertEquals(sizeof($scenario_3_data['data']), 0, 'The amount of subtenants returned for scenario 3 is wrong.');
        $this->assertEquals(collect($scenario_3_data['data'])->count(), 0, 'The subtenants returned for scenario 3 are wrong.');
        $this->assertEquals(sizeof($scenario_3_data['selectedPrefixOnly']), 0, 'The amount of selectedPrefixOnly entries for scenario 3 is wrong.');
        $this->assertEquals(sizeof($scenario_3_data['selectedChildrenAllowed']), 0, 'The amount of selectedPrefixOnly entries for scenario 3 is wrong.');

        // Scenario 4 - Malicious access attempt
        $scenario_4 = $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/network/prefix/' . $this->prefixes['C']['1']->id . '/subtenant');

        $this->assertTrue($scenario_4->status() !== 200, 'The status for scenario 4 should not be 200');
        $this->assertEquals(sizeof($scenario_4->json()), 1, 'The amount of subtenants returned for scenario 4 is wrong.');

    }

    public function testPutSubtenants() {

        // Scenario 1 - Add subtenants
        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->put('/api/network/prefix/' . $this->prefixes['A']['1']->id . '/subtenant', [
            'selectedPrefixOnly' => [3],
            'selectedChildrenAllowed' => [3] 
        ]);

        $this->assertTrue($scenario_1->status() === 200, 'The status for scenario 1 should be 200');
        $this->assertTrue(PrefixSubtenants::where('prefix_id', '=', $this->prefixes['A']['1']->id)->get()->count() === 2, 'The subtenancy entries for scenario 1 were not generated correctly.');

        // Scenario 2 - Attempt to subtenant to an organisation you don't have
        $scenario_2 = $this->actingAs($this->users['A'])->withoutMiddleware()->put('/api/network/prefix/' . $this->prefixes['A']['1']->id . '/subtenant', [
            'selectedPrefixOnly' => [4, 5],
            'selectedChildrenAllowed' => [4, 5] 
        ]);

        $this->assertTrue($scenario_2->status() === 400, 'The status for scenario 2 should be 400');
        $this->assertTrue(PrefixSubtenants::where('prefix_id', '=', $this->prefixes['A']['1']->id)->get()->count() === 2, 'The subtenancy entries for scenario 2 were not generated correctly.');

        // Scenario 3 - Attempt malicious assignment attempt
        $scenario_3 = $this->actingAs($this->users['C'])->withoutMiddleware()->put('/api/network/prefix/' . $this->prefixes['B']['1']->id . '/subtenant', [
            'selectedPrefixOnly' => [4, 5],
            'selectedChildrenAllowed' => [4, 5] 
        ]);

        $this->assertTrue($scenario_3->status() === 400, 'The status for scenario 3 should be 400');
        $this->assertTrue(PrefixSubtenants::where('prefix_id', '=', $this->prefixes['B']['1']->id)->get()->count() === 0, 'The subtenancy entries for scenario 3 were not generated correctly.');

        // Scenario 5 - Erase subtenants
        $scenario_4 = $this->actingAs($this->users['A'])->withoutMiddleware()->put('/api/network/prefix/' . $this->prefixes['A']['1']->id . '/subtenant', [
            'selectedPrefixOnly' => [],
            'selectedChildrenAllowed' => [] 
        ]);

        $this->assertTrue($scenario_4->status() === 200, 'The status for scenario 1 should be 200');
        $this->assertTrue(PrefixSubtenants::where('prefix_id', '=', $this->prefixes['A']['1']->id)->get()->count() === 0, 'The subtenancy entries for scenario 4 were not generated correctly.');

    }

    public function testGetInsideSubnet() {

        // Scenario 1 - Check that an owned prefix contains a valid prefix - Must succeed
        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/goliath/prefix/' . $this->prefixes['A']['1']->id . '/subnet?cidr=111.0.0.0/32', [
            'cidr' => '111.0.0.0/32',
        ]);

        $this->assertTrue($scenario_1->status() === 200, 'Scenario 1 should have succeeded.');

        // Scenario 2 - Check that an unowned prefix contains a valid prefix - Must fail - Quirk, Organisation 1 SHOULD ALWAYS PASS
        $scenario_2 = $this->actingAs($this->users['B'])->withoutMiddleware()->get('/api/goliath/prefix/' . $this->prefixes['A']['1']->id . '/subnet?cidr=111.0.0.0/32');

        $this->assertTrue($scenario_2->status() === 404, 'Scenario 2 should have failed.');

        // Scenario 3 - Check that an owned prefix contains an invalid prefix - Will fail
        $scenario_3 = $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/goliath/prefix/' . $this->prefixes['A']['1']->id . '/subnet?cidr=211.0.0.0/32');

        $this->assertTrue($scenario_3->status() === 404, 'Scenario 3 should have failed.');

        // Scenario 4 - Check that an unowned prefix contains and invalid prefix - Must fail
        $scenario_4 = $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/goliath/prefix/' . $this->prefixes['B']['1']->id . '/subnet?cidr=213.0.0.0/32');

        $this->assertTrue($scenario_4->status() === 404, 'Scenario 4 should have failed.');

    }

    public function testPostPrefix() {

        // This is a little more complicated - This function is NEVER called directly via a route, it's something templated on top of. I'm going to just test it as it stands.
        $x = new NetworkController;

        $empty_validation_shim = ['input' => [], 'rule' => [] ];
        $empty_creation_shim = function($new, $parent, $request) {};

        // Scenario 1 - An owned prefix contains a valid prefix - Must succeed
        Auth::setUser($this->users['A']);

        $scenario_1 = $x->post_prefix(
                new Request(['cidr' => '111.0.0.0/32', 'name' => '']),
                $this->prefixes['A']['1']->id,
                $empty_validation_shim,
                $empty_creation_shim,
                false
        );

        $this->assertTrue($scenario_1->status() === 200, 'Scenario 1 should have succeeded.');
        $this->assertTrue(Prefixes::where([['address', '=', '111.0.0.0'], ['mask', '=', 32]])->get()->count() === 1, 'Scenario 1 should result in one prefix being made.');

        // Scenario 2 - An unowned prefix contains a valid prefix - Must fail - Quirk, Organisation 1 SHOULD ALWAYS PASS

        Auth::setUser($this->users['B']);

        $scenario_2 = $x->post_prefix(
                new Request(['cidr' => '111.0.0.1/32', 'name' => '']),
                $this->prefixes['A']['1']->id,
                $empty_validation_shim,
                $empty_creation_shim,
                false
        );

        $this->assertTrue($scenario_2->status() !== 200, 'Scenario 2 should have failed.');
        $this->assertTrue(Prefixes::where([['address', '=', '111.0.0.1'], ['mask', '=', 32]])->get()->count() === 0, 'Scenario 2 should result in no prefixes being made.');

        // Scenario 3 - An owned prefix contains an invalid prefix - Will fail
        Auth::setUser($this->users['A']);

        $scenario_3 = $x->post_prefix(
                new Request(['cidr' => '211.0.0.0/32', 'name' => '']),
                $this->prefixes['A']['1']->id,
                $empty_validation_shim,
                $empty_creation_shim,
                false
        );

        $this->assertTrue($scenario_3->status() !== 200, 'Scenario 3 should have failed.');
        $this->assertTrue(Prefixes::where([['address', '=', '211.0.0.0'], ['mask', '=', 32]])->get()->count() === 0, 'Scenario 3 should result in no prefixes being made.');

        // Scenario 4 - An unowned prefix contains and invalid prefix - Must fail
        Auth::setUser($this->users['B']);

        $scenario_4 = $x->post_prefix(
                new Request(['cidr' => '111.0.0.4/32', 'name' => '']),
                $this->prefixes['A']['1']->id,
                $empty_validation_shim,
                $empty_creation_shim,
                false
        );

        $this->assertTrue($scenario_4->status() !== 200, 'Scenario 4 should have failed.');
        $this->assertTrue(Prefixes::where([['address', '=', '111.0.0.4'], ['mask', '=', 32]])->get()->count() === 0, 'Scenario 4 should result in no prefixes being made.');

    }

    public function testGetPrefixDetails() {

        // Test whether an appropriate user has access to their own prefixes details.
        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/network/prefix/' . $this->prefixes['A']['1']->id);

        $this->assertTrue($scenario_1->status() === 200,  'Scenario 1 should have succeded.');
        $this->assertTrue($scenario_1->json()['prefix'] === '111.0.0.0/20');

        // Make sure the user can't access a prefix they don't own.
        $scenario_2 = $this->actingAs($this->users['A'])->withoutMiddleware()->get('/api/network/prefix/' . $this->prefixes['B']['1']->id);

        $this->assertTrue($scenario_2->status() === 500,  'Scenario 2 should have failed.');
        $this->assertTrue(sizeof($scenario_2->json()) === 0);

        // Make sure a subtenant can access a prefixes details.

        PrefixSubtenants::insert([[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 3,
            'also_subnets'      => 1
        ],[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 3,
            'also_subnets'      => 0
        ]]);

        $scenario_3 = $this->actingAs($this->users['C'])->withoutMiddleware()->get('/api/network/prefix/' . $this->prefixes['A']['1']->id);

        $this->assertTrue($scenario_3->status() === 200,  'Scenario 3 should have succeded.');
        $this->assertTrue($scenario_3->json()['prefix'] === '111.0.0.0/20');

    }

    public function testRenamePrefix() {

        // Test whether I can rename a prefix I have access too
        
        PrefixSubtenants::insert([[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 3,
            'also_subnets'      => 1
        ],[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 3,
            'also_subnets'      => 0
        ]]);

        $this->actingAs($this->users['C'])->withoutMiddleware()->patch('/api/network/prefix/' . $this->prefixes['A']['1']->id . '/data', [
            'name' => 'TEST-1'
        ]);

        $this->assertTrue(Prefixes::where('name', '=', 'TEST-1')->get()->count() === 1, 'Scenario 1 failed to rename a prefix.');

        // Test whether I can rename a prefix I own
        $this->actingAs($this->users['A'])->withoutMiddleware()->patch('/api/network/prefix/' . $this->prefixes['A']['1']->id . '/data', [
            'name' => 'TEST-2'
        ]);

        $this->assertTrue(Prefixes::where('name', '=', 'TEST-2')->get()->count() === 1, 'Scenario 2 failed to rename a prefix.');

        // Test that I cannot rename a prefix I do not own
        $this->actingAs($this->users['B'])->withoutMiddleware()->patch('/api/network/prefix/' . $this->prefixes['A']['1']->id . '/data', [
            'name' => 'TEST-3'
        ]);

        $this->assertTrue(Prefixes::where('name', '=', 'TEST-3')->get()->count() === 0, 'Scenario 3 succeeded when it should not have.');

    }

    public function testDeletePrefix() {

        // Setup some prefixes for this.

        $x = new NetworkController;

        $empty_validation_shim = ['input' => [], 'rule' => [] ];
        $empty_creation_shim = function($new, $parent, $request) {};

        Auth::setUser($this->users['A']);

        $x->post_prefix(
                new Request(['cidr' => '111.0.0.0/32', 'name' => '']),
                $this->prefixes['A']['1']->id,
                $empty_validation_shim,
                $empty_creation_shim,
                false
        );

        $prefix_1 = Prefixes::where([['address', '=', '111.0.0.0'], ['mask', '=', 32]])->get()->first();

        $x->post_prefix(
            new Request(['cidr' => '111.0.0.1/32', 'name' => '']),
            $this->prefixes['A']['1']->id,
            $empty_validation_shim,
            $empty_creation_shim,
            false
        );

        $prefix_2 = Prefixes::where([['address', '=', '111.0.0.1'], ['mask', '=', 32]])->get()->first();

        // Delete one that is owned
        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->delete("/api/network/prefix/$prefix_1->id");

        $this->assertTrue($scenario_1->status() === 200, 'The response should have returned 200 for scenario 1');
        $this->assertTrue(Prefixes::where([['address', '=', '111.0.0.0'], ['mask', '=', 32]])->get()->count() === 0, 'The prefix should be deleted for scenario 1.');

        // Try to delete one that is not owned
        $scenario_2 = $this->actingAs($this->users['B'])->withoutMiddleware()->delete("/api/network/prefix/$prefix_2->id");

        $this->assertTrue($scenario_2->status() === 400, 'The response should have returned 400 for scenario 2');
        $this->assertTrue(Prefixes::where([['address', '=', '111.0.0.1'], ['mask', '=', 32]])->get()->count() === 1, 'The prefix should not have been deleted for scenario 2.');

    }

    public function testGetValidOwnedSubnet() {

        PrefixSubtenants::insert([[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 11,
            'also_subnets'      => 1
        ],[
            'prefix_id'         => $this->prefixes['A'][1]->id,
            'subtenant_id'      => 11,
            'also_subnets'      => 0
        ]]);

        // Test valid subnet
        $scenario_1 = $this->actingAs($this->users['A'])->withoutMiddleware()->get("/api/network/prefix/address?address=111.0.0.1");

        $this->assertTrue($scenario_1->json()['valid'] === true, 'This should have passed in scenario 1.');

        // Test invalid subnet
        $scenario_2 = $this->actingAs($this->users['A'])->withoutMiddleware()->get("/api/network/prefix/address?address=117.0.0.0");

        $this->assertTrue($scenario_2->json()['valid'] === false, 'This should have failed in scenario 2.');

        // Try V6 in V4
        $scenario_3 = $this->actingAs($this->users['A'])->withoutMiddleware()->get("/api/network/prefix/address?address=::ffff:111.0.0.1");

        $this->assertTrue($scenario_3->json()['valid'] === false, 'This should have failed in scenario 3.');

    }

    /**
     * The brand automation endpoint plants subnets under GSL's blocks. A subnet that lands inside a
     * carve-out somebody else holds beneath the GSL parent is refused unless the caller overrides, in
     * which case it is created under GSL as usual.
     */
    public function testAssociateSubnetRefusesAContestedRange() {

        $gsl = Organisation::create([ 'name' => config('creatia.automation.main_organisation_name') ]);
        $gsl_asn = Asns::create([ 'asn' => 137409, 'organisation_id' => $gsl->id, 'approved' => 1 ]);

        // GSL's /22, with B's /24 carved out of it.
        Prefixes::factory()->address('103.9.0.0', 22)->ownership($gsl, $gsl_asn)->announced()->create();
        Prefixes::factory()->address('103.9.1.0', 24)->ownership($this->organisations['B'], $this->asns['B'])->announced()->create();

        $payload = [ 'name' => 'Customer C', 'prefix_only' => true, 'children_allowed' => false, 'brand' => 'gsl' ];

        // Scenario 1 - inside B's carve-out. Refused.
        $scenario_1 = $this->withoutMiddleware()->post('/api/internal/organisation/' . $this->organisations['C']->id . '/network', [ ...$payload, 'subnet' => '103.9.1.0/26' ]);

        $this->assertEquals(409, $scenario_1->status(), 'Scenario 1 should have been refused as contested.');
        $this->assertEquals(10, Prefixes::count(), 'Scenario 1 should not have created anything.');

        // Scenario 2 - beside the carve-out. Business as usual.
        $scenario_2 = $this->withoutMiddleware()->post('/api/internal/organisation/' . $this->organisations['C']->id . '/network', [ ...$payload, 'subnet' => '103.9.2.0/26' ]);

        $this->assertEquals(200, $scenario_2->status(), 'Scenario 2 should have succeeded.');
        $this->assertEquals(11, Prefixes::count(), 'Scenario 2 should have created the subnet.');

        // Scenario 3 - inside B's carve-out with an override. Created under GSL.
        $scenario_3 = $this->withoutMiddleware()->post('/api/internal/organisation/' . $this->organisations['C']->id . '/network', [ ...$payload, 'subnet' => '103.9.1.0/26', 'override' => true ]);

        $this->assertEquals(200, $scenario_3->status(), 'Scenario 3 should have succeeded under override.');
        $this->assertEquals(1, Prefixes::where([['address', '=', '103.9.1.0'], ['mask', '=', 26], ['organisation_id', '=', $gsl->id]])->count(), 'The override creates the subnet under GSL.');
    }

    /**
     * The admin bulk upsert finds its parent among the chosen organisation's prefixes. A row that lands
     * inside a carve-out somebody else holds beneath that parent is reported as contested and skipped,
     * unless the request overrides.
     */
    public function testBulkUpsertRefusesAContestedRange() {

        // B's /24, carved out of A's 111.0.0.0/20.
        Prefixes::factory()->address('111.0.1.0', 24)->ownership($this->organisations['B'], $this->asns['B'])->announced()->create();

        $row = [ 'prefix' => '111.0.1.0/26', 'name' => 'Contested', 'subtenant_delete_previous_state' => false ];

        // Scenario 1 - no override. Reported and skipped.
        $scenario_1 = (new AdminNetworkController)->patch_bulk_upsert(new Request([ 'organisation_id' => $this->organisations['A']->id, 'prefixes' => [ $row ] ]));

        $this->assertEquals(200, $scenario_1->status());
        $this->assertSame('Contested', json_decode($scenario_1->content(), true)['statement']['111.0.1.0/26']['prefix']);
        $this->assertEquals(9, Prefixes::count(), 'Scenario 1 should not have created anything.');

        // Scenario 2 - override. Inserted under A.
        $scenario_2 = (new AdminNetworkController)->patch_bulk_upsert(new Request([ 'organisation_id' => $this->organisations['A']->id, 'prefixes' => [ $row ], 'override' => true ]));

        $this->assertEquals(200, $scenario_2->status());
        $this->assertSame('Inserted', json_decode($scenario_2->content(), true)['statement']['111.0.1.0/26']['prefix']);
        $this->assertEquals(1, Prefixes::where([['address', '=', '111.0.1.0'], ['mask', '=', 26], ['organisation_id', '=', $this->organisations['A']->id]])->count(), 'The override creates the prefix under the parent owner.');
    }

}
