<?php

namespace Tests\Feature\Users\Api;

use App\Models\Asset;
use App\Models\Company;
use App\Models\Department;
use App\Models\Group;
use App\Models\Location;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class UpdateUserTest extends TestCase
{
    public function test_can_update_user_via_patch()
    {
        $admin = User::factory()->superuser()->create();
        $manager = User::factory()->create();
        $company = Company::factory()->create();
        $department = Department::factory()->create();
        $location = Location::factory()->create();
        [$groupA, $groupB] = Group::factory()->count(2)->create();

        $user = User::factory()->create([
            'activated' => false,
            'remote' => false,
            'vip' => false,
        ]);

        $this->actingAsForApi($admin)
            ->patchJson(route('api.users.update', $user), [
                'first_name' => 'Mabel',
                'last_name' => 'Mora',
                'username' => 'mabel',
                'password' => 'super-secret',
                'email' => 'mabel@onlymurderspod.com',
                'permissions' => '{"a.new.permission":"1"}',
                'activated' => true,
                'phone' => '619-555-5555',
                'mobile' => '619-666-6666',
                'jobtitle' => 'Host',
                'manager_id' => $manager->id,
                'employee_num' => '1111',
                'notes' => 'Pretty good artist',
                'company_id' => $company->id,
                'department_id' => $department->id,
                'location_id' => $location->id,
                'remote' => true,
                'groups' => $groupA->id,
                'vip' => true,
                'start_date' => '2021-08-01',
                'end_date' => '2025-12-31',
                'avatar' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEsAQMAAADXeXeBAAAABlBMVEX+AAD///+KQee0AAAACXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH5QQbCAoNcoiTQAAAACZJREFUaN7twTEBAAAAwqD1T20JT6AAAAAAAAAAAAAAAAAAAICnATvEAAEnf54JAAAAAElFTkSuQmCC',
            ])
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('success')
            ->json();

        $user->refresh();
        $this->assertEquals('Mabel', $user->first_name, 'First name was not updated');
        $this->assertEquals('Mora', $user->last_name, 'Last name was not updated');
        $this->assertEquals('mabel', $user->username, 'Username was not updated');
        $this->assertTrue(Hash::check('super-secret', $user->password), 'Password was not updated');
        $this->assertEquals('mabel@onlymurderspod.com', $user->email, 'Email was not updated');
        $this->assertArrayHasKey('a.new.permission', $user->decodePermissions(), 'Permissions were not updated');
        $this->assertTrue((bool) $user->activated, 'User not marked as activated');
        $this->assertEquals('619-555-5555', $user->phone, 'Phone was not updated');
        $this->assertEquals('619-666-6666', $user->mobile, 'Mobile was not updated');
        $this->assertEquals('Host', $user->jobtitle, 'Job title was not updated');
        $this->assertTrue($user->manager->is($manager), 'Manager was not updated');
        $this->assertEquals('1111', $user->employee_num, 'Employee number was not updated');
        $this->assertEquals('Pretty good artist', $user->notes, 'Notes was not updated');
        $this->assertTrue($user->company->is($company), 'Company was not updated');
        $this->assertTrue($user->department->is($department), 'Department was not updated');
        $this->assertTrue($user->location->is($location), 'Location was not updated');
        $this->assertEquals(1, $user->remote, 'Remote was not updated');
        $this->assertTrue($user->groups->contains($groupA), 'Groups were not updated');
        $this->assertEquals(1, $user->vip, 'VIP was not updated');
        $this->assertEquals('2021-08-01', $user->start_date, 'Start date was not updated');
        $this->assertEquals('2025-12-31', $user->end_date, 'End date was not updated');

        // assert against resized hash
        $this->assertEquals(
            'db2e13ba04318c99058ca429d67777322f48566b',
            sha1(Storage::disk('public')->get(app('users_upload_path').$user->avatar))
        );

        // `groups` can be an id or array or ids
        $this->patch(route('api.users.update', $user), ['groups' => [$groupA->id, $groupB->id]]);

        $user->refresh();
        $this->assertTrue($user->groups->contains($groupA), 'Not part of expected group');
        $this->assertTrue($user->groups->contains($groupB), 'Not part of expected group');
    }

    public function test_can_update_user_via_put()
    {
        $admin = User::factory()->superuser()->create();
        $manager = User::factory()->create();
        $company = Company::factory()->create();
        $department = Department::factory()->create();
        $location = Location::factory()->create();
        [$groupA, $groupB] = Group::factory()->count(2)->create();

        $user = User::factory()->create([
            'activated' => false,
            'remote' => false,
            'vip' => false,
        ]);

        $response = $this->actingAsForApi($admin)
            ->putJson(route('api.users.update', $user), [
                'first_name' => 'Mabel',
                'last_name' => 'Mora',
                'username' => 'mabel',
                'password' => 'super-secret',
                'password_confirmation' => 'super-secret',
                'email' => 'mabel@example.org',
                'permissions' => '{"a.new.permission":"1"}',
                'activated' => true,
                'phone' => '619-555-5555',
                'mobile' => '619-666-6666',
                'jobtitle' => 'Host',
                'manager_id' => $manager->id,
                'employee_num' => '1111',
                'notes' => 'Pretty good artist',
                'company_id' => $company->id,
                'department_id' => $department->id,
                'location_id' => $location->id,
                'remote' => true,
                'groups' => $groupA->id,
                'vip' => true,
                'start_date' => '2021-08-01',
                'end_date' => '2025-12-31',
                'avatar' => 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEsAQMAAADXeXeBAAAABlBMVEX+AAD///+KQee0AAAACXBIWXMAAAsSAAALEgHS3X78AAAAB3RJTUUH5QQbCAoNcoiTQAAAACZJREFUaN7twTEBAAAAwqD1T20JT6AAAAAAAAAAAAAAAAAAAICnATvEAAEnf54JAAAAAElFTkSuQmCC',
            ])
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('success')
            ->json();

        $user->refresh();
        $this->assertEquals('Mabel', $user->first_name, 'First name was not updated');
        $this->assertEquals('Mora', $user->last_name, 'Last name was not updated');
        $this->assertEquals('mabel', $user->username, 'Username was not updated');
        $this->assertTrue(Hash::check('super-secret', $user->password), 'Password was not updated');
        $this->assertEquals('mabel@example.org', $user->email, 'Email was not updated');
        $this->assertArrayHasKey('a.new.permission', $user->decodePermissions(), 'Permissions were not updated');
        $this->assertTrue((bool) $user->activated, 'User not marked as activated');
        $this->assertEquals('619-555-5555', $user->phone, 'Phone was not updated');
        $this->assertEquals('619-666-6666', $user->mobile, 'Mobile was not updated');
        $this->assertEquals('Host', $user->jobtitle, 'Job title was not updated');
        $this->assertTrue($user->manager->is($manager), 'Manager was not updated');
        $this->assertEquals('1111', $user->employee_num, 'Employee number was not updated');
        $this->assertEquals('Pretty good artist', $user->notes, 'Notes was not updated');
        $this->assertTrue($user->company->is($company), 'Company was not updated');
        $this->assertTrue($user->department->is($department), 'Department was not updated');
        $this->assertTrue($user->location->is($location), 'Location was not updated');
        $this->assertEquals(1, $user->remote, 'Remote was not updated');
        $this->assertTrue($user->groups->contains($groupA), 'Groups were not updated');
        $this->assertEquals(1, $user->vip, 'VIP was not updated');
        $this->assertEquals('2021-08-01', $user->start_date, 'Start date was not updated');
        $this->assertEquals('2025-12-31', $user->end_date, 'End date was not updated');

        // assert against resized hash
        $this->assertEquals(
            'db2e13ba04318c99058ca429d67777322f48566b',
            sha1(Storage::disk('public')->get(app('users_upload_path').$user->avatar))
        );

        // `groups` can be an id or array or ids
        $this->patch(route('api.users.update', $user), ['groups' => [$groupA->id, $groupB->id]]);

        $user->refresh();
        $this->assertTrue($user->groups->contains($groupA), 'Not part of expected group');
        $this->assertTrue($user->groups->contains($groupB), 'Not part of expected group');
    }

    public function test_api_users_can_be_activated_with_number()
    {
        $admin = User::factory()->editUsers()->create();
        $user = User::factory()->create(['activated' => 0]);

        $this->actingAsForApi($admin)
            ->patch(route('api.users.update', $user), [
                'activated' => 1,
            ]);

        $this->assertEquals(1, $user->refresh()->activated);
    }

    public function test_api_users_can_be_activated_with_boolean_true()
    {
        $admin = User::factory()->editUsers()->create();
        $user = User::factory()->create(['activated' => false]);

        $this->actingAsForApi($admin)
            ->patch(route('api.users.update', $user), [
                'activated' => true,
            ]);

        $this->assertEquals(1, $user->refresh()->activated);
    }

    /**
     * Companion regression to
     * tests/Feature/Users/Ui/UpdateUserTest::test_editing_users_cannot_toggle_admin_activated_via_full_valid_payload
     * The API path already excludes `activated` from the pre-gate fill()
     * and only assigns it inside the canEditAuthFields branch, so this
     * has always been safe. Pinning the behavior explicitly so a future
     * refactor to `$user->fill($request->all())` or similar can't
     * silently regress it.
     */
    public function test_api_editing_users_cannot_toggle_admin_activated()
    {
        $editing_user = User::factory()->editUsers()->create(['activated' => true]);
        $admin = User::factory()->admin()->create([
            'first_name' => 'Admin',
            'last_name' => 'Target',
            'username' => 'api_admin_target',
            'email' => 'api-admin-target@example.test',
            'activated' => true,
        ]);

        $this->actingAsForApi($editing_user)
            ->patch(route('api.users.update', $admin), [
                'first_name' => $admin->first_name,
                'last_name' => $admin->last_name,
                'username' => $admin->username,
                'email' => $admin->email,
                'activated' => 0,
            ]);

        $this->assertSame(1, (int) $admin->fresh()->activated, 'Non-admin actor must not be able to deactivate an admin via API.');
    }

    /**
     * When a caller who cannot pass canEditAuthFields on the target sends any
     * User::GATED_AUTH_FIELDS in the request payload, the API must return an
     * error status naming the denied fields rather than silently dropping
     * them and returning `success`. Prior behavior returned
     * `{"status":"success", "messages":"User was successfully updated."}`
     * even when the password / permissions / activated / username / email
     * write in the payload never persisted, misrepresenting the actual
     * outcome to API clients.
     */
    public function test_api_returns_error_when_auth_fields_requested_without_permission(): void
    {
        $editing_user = User::factory()->editUsers()->create();
        $admin = User::factory()->admin()->create([
            'username' => 'api_admin_authfield_target',
            'email' => 'api-admin-authfield-target@example.test',
            'first_name' => 'Original',
            'last_name' => 'Name',
        ]);

        $originalPasswordHash = $admin->password;

        $response = $this->actingAsForApi($editing_user)
            ->patch(route('api.users.update', $admin), [
                'first_name' => 'Tampered',
                'password' => 'attempted-new-password',
                'permissions' => ['licenses.keys' => '1'],
            ])
            ->assertOk();

        $response->assertJson([
            'status' => 'error',
            'messages' => trans('admin/users/message.auth_fields_denied', ['fields' => 'password, permissions']),
        ]);

        $fresh = $admin->fresh();
        $this->assertSame('Original', $fresh->first_name, 'Non-auth fields must not persist when the request is rejected for auth-field denial.');
        $this->assertSame($originalPasswordHash, $fresh->password, 'Password must not change when the caller cannot canEditAuthFields on the target.');
    }

    /**
     * The inverse contract: when the request carries no GATED_AUTH_FIELDS,
     * the response stays `success` and non-auth fields persist as normal.
     * Pins that the new loud-fail path is entered only when the payload
     * actually asks for gated fields.
     */
    public function test_api_returns_success_when_no_auth_fields_are_requested(): void
    {
        $editing_user = User::factory()->editUsers()->create();
        $admin = User::factory()->admin()->create([
            'first_name' => 'Original',
            'last_name' => 'Name',
            'jobtitle' => 'Previous Title',
        ]);

        $this->actingAsForApi($editing_user)
            ->patch(route('api.users.update', $admin), [
                'first_name' => 'Updated',
                'jobtitle' => 'New Title',
            ])
            ->assertOk()
            ->assertJson(['status' => 'success']);

        $fresh = $admin->fresh();
        $this->assertSame('Updated', $fresh->first_name);
        $this->assertSame('New Title', $fresh->jobtitle);
    }

    public function test_api_users_can_be_deactivated_with_number()
    {
        $admin = User::factory()->editUsers()->create();
        $user = User::factory()->create(['activated' => true]);

        $this->actingAsForApi($admin)
            ->patch(route('api.users.update', $user), [
                'activated' => 0,
            ]);

        $this->assertEquals(0, $user->refresh()->activated);
    }

    public function test_api_users_can_be_deactivated_with_boolean_false()
    {
        $admin = User::factory()->editUsers()->create();
        $user = User::factory()->create(['activated' => true]);

        $this->actingAsForApi($admin)
            ->patch(route('api.users.update', $user), [
                'activated' => false,
            ]);

        $this->assertEquals(0, $user->refresh()->activated);
    }

    public function test_editing_users_cannot_edit_escalation_fields_for_admins()
    {
        $hashed_original = Hash::make('!!094850394680980380kfejlskjfl');
        $hashed_new = Hash::make('!ABCDEFGIJKL123!!!');

        $editing_user = User::factory()->editUsers()->create();
        $adminuser = User::factory()->admin()->create(['username' => 'TestAdminUser', 'email' => 'admin@example.org', 'password' => $hashed_original, 'activated' => 1]);

        // The admin being edited
        $this->assertDatabaseHas('users', [
            'id' => $adminuser->id,
            'username' => 'TestAdminUser',
            'email' => 'admin@example.org',
            'activated' => 1,
            'password' => $hashed_original,
            'permissions' => '{"admin":"1"}',
        ]);

        $this->actingAsForApi($editing_user)
            ->patch(route('api.users.update', $adminuser), [
                'username' => 'testnewusername',
                'email' => 'testnewemail@example.org',
                'activated' => 0,
                'permissions' => "{'superadmin':1}",
                'password' => $hashed_new,
            ]);

        // These should keep their old values
        $this->assertEquals('TestAdminUser', $adminuser->refresh()->username);
        $this->assertEquals('admin@example.org', $adminuser->refresh()->email);
        $this->assertEquals(1, $adminuser->refresh()->activated);
        $this->assertEquals($hashed_original, $adminuser->refresh()->password);
        $this->assertEquals('{"admin":"1"}', $adminuser->refresh()->permissions);

    }

    public function test_admins_cannot_deescalate_superadmins()
    {
        $hashed_original = Hash::make('my-awesome-password!!!!!12345');
        $hashed_new = Hash::make('!ABCDEFGIJKL123!!!');

        $editing_user = User::factory()->admin()->create();
        $superuser = User::factory()->superuser()->create(['username' => 'TestSuperUser', 'email' => 'superuser@example.org', 'password' => $hashed_original, 'activated' => 1]);

        // The admin being edited
        $this->assertDatabaseHas('users', [
            'id' => $superuser->id,
            'username' => 'TestSuperUser',
            'email' => 'superuser@example.org',
            'activated' => 1,
            'password' => $hashed_original,
            'permissions' => '{"superuser":"1"}',
        ]);

        $this->actingAsForApi($editing_user)
            ->patch(route('api.users.update', $superuser), [
                'username' => 'testnewusername',
                'email' => 'testnewemail@example.org',
                'activated' => 0,
                'permissions' => '{"admin":"1"}',
                'password' => $hashed_new,
            ]);

        // These should keep their old values
        $this->assertEquals('TestSuperUser', $superuser->refresh()->username);
        $this->assertEquals('superuser@example.org', $superuser->refresh()->email);
        $this->assertEquals(1, $superuser->refresh()->activated);
        $this->assertEquals($hashed_original, $superuser->refresh()->password);
        $this->assertEquals('{"superuser":"1"}', $superuser->refresh()->permissions);
        $this->assertNotEquals('testnewusername', $superuser->refresh()->username);
        $this->assertNotEquals('testnewemail@example.org', $superuser->refresh()->email);
        $this->assertNotTrue(Hash::check('super-secret-new-password', $superuser->password), $superuser->refresh()->password);

    }

    public function test_users_scoped_to_company_during_update_when_multiple_full_company_support_enabled()
    {
        $this->settings->enableMultipleFullCompanySupport();

        $companyA = Company::factory()->create(['name' => 'Company A']);
        $companyB = Company::factory()->create(['name' => 'Company B']);

        $adminA = User::factory()->forCompany($companyA)->admin()->create();
        $adminB = User::factory()->forCompany($companyB)->admin()->create();
        $adminNoCompany = User::factory()->withoutCompany()->admin()->create();

        // Create users that belongs to company A and B and one that is unscoped
        $scoped_user_in_companyA = User::factory()->forCompany($companyA->id)->create();
        $scoped_user_in_companyB = User::factory()->forCompany($companyB->id)->create();
        $scoped_user_in_no_company = User::factory()->withoutCompany()->create();

        // Each PATCH carries company_ids so the strict-FMCS gate added
        // for #19192 doesn't hijack the authorization assertion — the
        // test's intent is to verify company-scoped authorization, not
        // to exercise the empty-pivot gate.
        $bodyA = ['company_ids' => [$companyA->id]];
        $bodyB = ['company_ids' => [$companyB->id]];

        // Admin for Company A should allow updating user from Company A
        $this->actingAsForApi($adminA)
            ->patchJson(route('api.users.update', $scoped_user_in_companyA), $bodyA)
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('success')
            ->json();

        // Admin for Company A should get denied updating user from Company B
        $this->actingAsForApi($adminA)
            ->patchJson(route('api.users.update', $scoped_user_in_companyB), $bodyB)
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('error')
            ->json();

        // Admin for Company A should get denied updating user without a company
        $this->actingAsForApi($adminA)
            ->patchJson(route('api.users.update', $scoped_user_in_no_company), $bodyA)
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('error')
            ->json();

        // Admin for Company B should allow updating user from Company B
        $this->actingAsForApi($adminB)
            ->patchJson(route('api.users.update', $scoped_user_in_companyB), $bodyB)
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('success')
            ->json();

        // Admin for Company B should get denied updating user from Company A
        $this->actingAsForApi($adminB)
            ->patchJson(route('api.users.update', $scoped_user_in_companyA), $bodyA)
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('error')
            ->json();

        // Admin for Company B should get denied updating user without a company
        $this->actingAsForApi($adminB)
            ->patchJson(route('api.users.update', $scoped_user_in_no_company), $bodyB)
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('error')
            ->json();

        // Admin without a company should allow updating user without
        // a company. Under strict FMCS mode uncompanied users operate
        // in the null pseudo-company namespace (Company scoping shows
        // them null-company rows); the #19192 gate steps aside for
        // them so this normal workflow keeps working.
        $this->actingAsForApi($adminNoCompany)
            ->patchJson(route('api.users.update', $scoped_user_in_no_company))
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('success')
            ->json();

        // Admin without a company should get denied updating user from Company A
        $this->actingAsForApi($adminNoCompany)
            ->patchJson(route('api.users.update', $scoped_user_in_companyA))
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('error')
            ->json();

        // Admin without a company should get denied updating user from Company B
        $this->actingAsForApi($adminNoCompany)
            ->patchJson(route('api.users.update', $scoped_user_in_companyB))
            ->assertOk()
            ->assertStatus(200)
            ->assertStatusMessageIs('error')
            ->json();
    }

    public function test_user_groups_are_only_updated_if_authenticated_user_is_super_user()
    {
        $groupToJoin = Group::factory()->create();

        $userWhoCanEditUsers = User::factory()->editUsers()->create();
        $superUser = User::factory()->superuser()->create();

        $userToUpdateByUserWhoCanEditUsers = User::factory()->create();
        $userToUpdateByToUserBySuperuser = User::factory()->create();

        $this->actingAsForApi($userWhoCanEditUsers)
            ->patchJson(route('api.users.update', $userToUpdateByUserWhoCanEditUsers), [
                'groups' => [$groupToJoin->id],
            ]);

        $this->actingAsForApi($superUser)
            ->patchJson(route('api.users.update', $userToUpdateByToUserBySuperuser), [
                'groups' => [$groupToJoin->id],
            ]);

        $this->assertFalse($userToUpdateByUserWhoCanEditUsers->refresh()->groups->contains($groupToJoin),
            'Non-super-user was able to modify user group'
        );

        $this->assertTrue($userToUpdateByToUserBySuperuser->refresh()->groups->contains($groupToJoin));
    }

    public function test_user_groups_can_be_cleared_by_super_user()
    {
        $normalUser = User::factory()->editUsers()->create();
        $superUser = User::factory()->superuser()->create();

        $oneUserToUpdate = User::factory()->create();
        $anotherUserToUpdate = User::factory()->create();

        $joinedGroup = Group::factory()->create();
        $oneUserToUpdate->groups()->sync([$joinedGroup->id]);
        $anotherUserToUpdate->groups()->sync([$joinedGroup->id]);

        $this->actingAsForApi($normalUser)
            ->patchJson(route('api.users.update', $oneUserToUpdate), [
                'groups' => null,
            ]);

        $this->actingAsForApi($superUser)
            ->patchJson(route('api.users.update', $anotherUserToUpdate), [
                'groups' => null,
            ]);

        $this->assertTrue($oneUserToUpdate->refresh()->groups->contains($joinedGroup));
        $this->assertFalse($anotherUserToUpdate->refresh()->groups->contains($joinedGroup));
    }

    public function test_non_superuser_cannot_update_own_groups()
    {
        $groupToJoin = Group::factory()->create();
        $user = User::factory()->editUsers()->create();

        $this->actingAsForApi($user)
            ->patchJson(route('api.users.update', $user), [
                'groups' => [$groupToJoin->id],
            ]);

        $this->assertFalse($user->refresh()->groups->contains($groupToJoin),
            'Non-super-user was able to modify user group'
        );

    }

    public function test_non_superuser_cannot_update_groups()
    {
        $user = User::factory()->editUsers()->create();
        $group = Group::factory()->create();
        $user->groups()->sync([$group->id]);
        $newGroupToJoin = Group::factory()->create();

        $this->actingAsForApi($user)
            ->patchJson(route('api.users.update', $user), [
                'groups' => [$newGroupToJoin->id],
            ]);

        $this->assertFalse($user->refresh()->groups->contains($newGroupToJoin),
            'Non-super-user was able to modify user group membership'
        );

        $this->assertTrue($user->refresh()->groups->contains($group));

    }

    public function test_users_groups_are_not_cleared_if_no_group_passed_by_super_user()
    {
        $user = User::factory()->create();
        $superUser = User::factory()->superuser()->create();

        $group = Group::factory()->create();
        $user->groups()->sync([$group->id]);

        $this->actingAsForApi($superUser)
            ->patchJson(route('api.users.update', $user), []);

        $this->assertTrue($user->refresh()->groups->contains($group));
    }

    public function test_multiple_groups_update_by_super_user()
    {
        $user = User::factory()->create();
        $superUser = User::factory()->superuser()->create();

        $groupA = Group::factory()->create(['name' => 'Group A']);
        $groupB = Group::factory()->create(['name' => 'Group B']);

        $this->actingAsForApi($superUser)
            ->patchJson(route('api.users.update', $user), [
                'groups' => [$groupA->id, $groupB->id],
            ])->json();

        $this->assertTrue($user->refresh()->groups->contains($groupA));
        $this->assertTrue($user->refresh()->groups->contains($groupB));
    }

    public function test_multi_company_user_cannot_be_moved_if_has_asset_in_different_company()
    {
        $this->settings->enableMultipleFullCompanySupport();

        $companyA = Company::factory()->create();
        $companyB = Company::factory()->create();

        $user = User::factory()->forCompany($companyA)->create();
        $superUser = User::factory()->superuser()->create();

        $asset = Asset::factory()->create([
            'company_id' => $companyA->id,
        ]);

        // no assets assigned, therefore success
        $this->actingAsForApi($superUser)->patchJson(route('api.users.update', $user), [
            'username' => 'test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('success');

        // same test but PUT
        $this->actingAsForApi($superUser)->putJson(route('api.users.update', $user), [
            'username' => 'test',
            'first_name' => 'Test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('success');

        $asset->checkOut($user, $superUser);

        // asset assigned, therefore error
        $this->actingAsForApi($superUser)->patchJson(route('api.users.update', $user), [
            'username' => 'test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('error');

        // same test but PUT
        $this->actingAsForApi($superUser)->putJson(route('api.users.update', $user), [
            'username' => 'test',
            'first_name' => 'Test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('error');
    }

    public function test_multi_company_user_can_be_updated_if_has_asset_in_same_company()
    {
        $this->settings->enableMultipleFullCompanySupport();

        $companyA = Company::factory()->create();
        $companyB = Company::factory()->create();

        $user = User::factory()->forCompany($companyA)->create();
        $superUser = User::factory()->superuser()->create();

        $asset = Asset::factory()->create([
            'company_id' => $companyA->id,
        ]);

        // no assets assigned from other company, therefore success
        $this->actingAsForApi($superUser)->patchJson(route('api.users.update', $user), [
            'username' => 'test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('success');

        // same test but PUT
        $this->actingAsForApi($superUser)->putJson(route('api.users.update', $user), [
            'username' => 'test',
            'first_name' => 'Test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('success');

        $asset->checkOut($user, $superUser);

        // asset assigned from other company, therefore error
        $this->actingAsForApi($superUser)->patchJson(route('api.users.update', $user), [
            'username' => 'test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('error');

        // same test but PUT
        $this->actingAsForApi($superUser)->putJson(route('api.users.update', $user), [
            'username' => 'test',
            'first_name' => 'Test',
            'company_id' => $companyB->id,
        ])->assertStatusMessageIs('error');
    }

    public function test_edit_users_permission_cannot_escalate_empty_permissions_user_to_admin_or_superuser_via_api()
    {
        $editingUser = User::factory()->editUsers()->create();
        $targetUser = User::factory()->create([
            'permissions' => null,
        ]);

        $this->actingAsForApi($editingUser)
            ->putJson(route('api.users.update', $targetUser), [
                'first_name' => $targetUser->first_name,
                'username' => $targetUser->username,
                'permissions' => [
                    'admin' => '1',
                    'superuser' => '1',
                    'users.view' => '1',
                ],
            ])
            ->assertOk()
            ->assertStatusMessageIs('success');

        $decoded = (array) $targetUser->refresh()->decodePermissions();

        $this->assertArrayNotHasKey('admin', $decoded, 'Non-admin user should not be able to grant admin');
        $this->assertArrayNotHasKey('superuser', $decoded, 'Non-admin user should not be able to grant superuser');
        $this->assertEquals(1, $decoded['users.view'] ?? null, 'Non-privileged permissions should still be updateable');
    }

    public function test_admin_cannot_escalate_empty_permissions_user_to_superuser_via_api()
    {
        $adminUser = User::factory()->admin()->create();
        $targetUser = User::factory()->create([
            'permissions' => null,
        ]);

        $this->actingAsForApi($adminUser)
            ->putJson(route('api.users.update', $targetUser), [
                'first_name' => $targetUser->first_name,
                'username' => $targetUser->username,
                'permissions' => [
                    'admin' => '1',
                    'superuser' => '1',
                ],
            ])
            ->assertOk()
            ->assertStatusMessageIs('success');

        $decoded = (array) $targetUser->refresh()->decodePermissions();

        $this->assertArrayHasKey('admin', $decoded, 'Admin should be able to grant admin');
        $this->assertSame('1', (string) $decoded['admin']);
        $this->assertArrayNotHasKey('superuser', $decoded, 'Admin should not be able to grant superuser');
    }
}
