<?php

namespace Tests\Feature;

use App\Models\Permission;
use App\Models\Role;
use App\Models\User;
use App\Services\Authorization\AuthorizationAssignmentService;
use App\Services\Authorization\AuthorizationDelegationService;
use App\Support\Authorization\PermissionName;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Tests\TestCase;

class SecurityAdministrationTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        $this->seed(DatabaseSeeder::class);
    }

    public function test_branch_administrator_can_delegate_only_roles_within_its_permission_ceiling(): void
    {
        $actor = $this->userWithRole('branch-administrator');
        $delegation = app(AuthorizationDelegationService::class);

        $this->assertTrue($delegation->canDelegateRole($actor, Role::where('name', 'loan-officer')->firstOrFail()));
        $this->assertTrue($delegation->canDelegateRole($actor, Role::where('name', 'branch-manager')->firstOrFail()));
        $this->assertFalse($delegation->canDelegateRole($actor, Role::where('name', 'super-administrator')->firstOrFail()));

        $privilegedCustom = Role::query()->create([
            'name' => 'system-settings-controller',
            'guard_name' => 'web',
            'display_name' => 'System Settings Controller',
            'is_system' => false,
        ]);
        app(AuthorizationAssignmentService::class)->syncRolePermissionIds($privilegedCustom, [
            Permission::where('name', PermissionName::SYSTEM_SETTINGS_MANAGE->value)->value('id'),
        ]);

        $this->assertFalse($delegation->canDelegateRole($actor->fresh(), $privilegedCustom->fresh('permissions')));
    }

    public function test_non_super_administrator_cannot_add_roles_to_own_account(): void
    {
        $actor = $this->userWithRole('branch-administrator');
        $branchAdministrator = Role::where('name', 'branch-administrator')->firstOrFail();
        $loanOfficer = Role::where('name', 'loan-officer')->firstOrFail();

        $this->expectException(HttpException::class);
        $this->expectExceptionCode(0);

        try {
            app(AuthorizationDelegationService::class)->assertRoleTransitionAllowed(
                $actor,
                $actor,
                [$branchAdministrator->id, $loanOfficer->id],
                'active',
            );
        } catch (HttpException $exception) {
            $this->assertSame(403, $exception->getStatusCode());
            throw $exception;
        }
    }

    public function test_last_active_super_administrator_cannot_be_demoted_or_deactivated(): void
    {
        $super = User::query()
            ->where('status', 'active')
            ->whereHas('roles', fn ($query) => $query->where('name', 'super-administrator'))
            ->firstOrFail();
        $superRole = Role::where('name', 'super-administrator')->firstOrFail();
        $delegation = app(AuthorizationDelegationService::class);

        try {
            $delegation->assertRoleTransitionAllowed($super, $super, [], 'active');
            $this->fail('Expected last-super-administrator demotion protection to reject the change.');
        } catch (HttpException $exception) {
            $this->assertSame(422, $exception->getStatusCode());
        }

        try {
            $delegation->assertRoleTransitionAllowed($super, $super, [$superRole->id], 'inactive');
            $this->fail('Expected last-super-administrator deactivation protection to reject the change.');
        } catch (HttpException $exception) {
            $this->assertSame(422, $exception->getStatusCode());
        }
    }

    public function test_super_administrator_assignment_requires_explicit_super_admin_delegation(): void
    {
        $branchAdmin = $this->userWithRole('branch-administrator');
        $superRole = Role::where('name', 'super-administrator')->firstOrFail();
        $delegation = app(AuthorizationDelegationService::class);

        $this->assertFalse($branchAdmin->can(PermissionName::ROLES_ASSIGN_SUPER_ADMIN->value));
        $this->assertFalse($delegation->canDelegateRole($branchAdmin, $superRole));

        $super = User::query()->whereHas('roles', fn ($query) => $query->where('name', 'super-administrator'))->firstOrFail();
        $this->assertTrue($super->can(PermissionName::ROLES_ASSIGN_SUPER_ADMIN->value));
        $this->assertTrue($delegation->canDelegateRole($super, $superRole));
    }

    public function test_system_roles_are_immutable_and_custom_roles_are_manageable(): void
    {
        $delegation = app(AuthorizationDelegationService::class);
        $systemRole = Role::where('name', 'loan-officer')->firstOrFail();
        $customRole = Role::query()->create([
            'name' => 'custom-reviewer',
            'guard_name' => 'web',
            'display_name' => 'Custom Reviewer',
            'is_system' => false,
        ]);

        try {
            $delegation->assertCustomRoleMutable($systemRole);
            $this->fail('Expected protected system role mutation to be rejected.');
        } catch (HttpException $exception) {
            $this->assertSame(422, $exception->getStatusCode());
        }

        $delegation->assertCustomRoleMutable($customRole);
        $this->assertTrue(true);
    }

    public function test_super_administrator_can_create_and_delete_an_unused_custom_role_with_audit_records(): void
    {
        $super = User::query()->whereHas('roles', fn ($query) => $query->where('name', 'super-administrator'))->firstOrFail();
        $permission = Permission::where('name', PermissionName::CUSTOMERS_VIEW->value)->firstOrFail();

        $response = $this->actingAs($super)->post(route('admin.roles.store'), [
            'name' => 'customer-reviewer',
            'display_name' => 'Customer Reviewer',
            'description' => 'Read-only customer review role.',
            'permission_ids' => [$permission->id],
        ]);

        $response->assertRedirect(route('admin.roles.index'));
        $role = Role::where('name', 'customer-reviewer')->firstOrFail();
        $this->assertFalse((bool) $role->is_system);
        $this->assertTrue($role->hasPermissionTo(PermissionName::CUSTOMERS_VIEW->value));
        $this->assertDatabaseHas('audit_logs', ['action' => 'authorization.role.created', 'auditable_id' => $role->id]);

        $delete = $this->actingAs($super)->delete(route('admin.roles.destroy', $role));
        $delete->assertRedirect(route('admin.roles.index'));
        $this->assertDatabaseMissing('roles', ['id' => $role->id]);
        $this->assertDatabaseHas('audit_logs', ['action' => 'authorization.role.deleted', 'auditable_id' => $role->id]);
    }

    public function test_custom_role_assigned_to_staff_cannot_be_deleted(): void
    {
        $super = User::query()->whereHas('roles', fn ($query) => $query->where('name', 'super-administrator'))->firstOrFail();
        $role = Role::query()->create([
            'name' => 'assigned-custom-role',
            'guard_name' => 'web',
            'display_name' => 'Assigned Custom Role',
            'is_system' => false,
        ]);
        $user = User::factory()->create(['status' => 'active']);
        app(AuthorizationAssignmentService::class)->assignUserRole($user, $role);

        $this->actingAs($super)
            ->delete(route('admin.roles.destroy', $role))
            ->assertStatus(422);

        $this->assertDatabaseHas('roles', ['id' => $role->id]);
    }

    private function userWithRole(string $roleName): User
    {
        $user = User::factory()->create(['status' => 'active']);
        app(AuthorizationAssignmentService::class)->assignUserRole(
            $user,
            Role::where('name', $roleName)->firstOrFail(),
        );

        return $user->fresh();
    }
}
