<?php

namespace Tests\Feature;

use App\Enums\Role;
use App\Livewire\Modals\CreateUser;
use App\Livewire\Settings\Users;
use App\Models\AuditEvent;
use App\Models\User;
use App\Support\Confirm\ConfirmToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;

/**
 * RBAC on the account-management surface: the mutating Settings\Users actions and the
 * CreateUser modal are admin-only (`manage-users`), role changes go through the R5 confirm
 * flow, and the last-admin invariant is never violated.
 */
class RbacUsersTest extends TestCase
{
    use RefreshDatabase;

    /** Issue + confirm a token so the apply handler can consume it (mirrors the modal). */
    private function confirmedToken(string $event, array $params): string
    {
        $token = ConfirmToken::issue($event, $params);
        ConfirmToken::confirm($token);

        return $token;
    }

    // ── Gate: non-admins are forbidden, admins allowed ───────────────────────────────

    public function test_non_admin_cannot_open_create(): void
    {
        foreach (['operator', 'viewer'] as $state) {
            $user = User::factory()->{$state}()->create();

            Livewire::actingAs($user)->test(Users::class)
                ->call('create')
                ->assertForbidden();
        }
    }

    public function test_non_admin_cannot_confirm_remove(): void
    {
        $operator = User::factory()->operator()->create();
        $victim = User::factory()->create();

        Livewire::actingAs($operator)->test(Users::class)
            ->call('confirmRemove', $victim->id)
            ->assertForbidden();
    }

    public function test_non_admin_cannot_confirm_role_change(): void
    {
        $viewer = User::factory()->viewer()->create();
        $target = User::factory()->operator()->create();

        Livewire::actingAs($viewer)->test(Users::class)
            ->call('confirmRoleChange', $target->id, 'viewer')
            ->assertForbidden();
    }

    public function test_admin_may_open_create_and_confirm_role_change(): void
    {
        $admin = User::factory()->create();
        $target = User::factory()->operator()->create();

        Livewire::actingAs($admin)->test(Users::class)
            ->call('create')
            ->assertOk()
            ->assertDispatched('openModal');

        Livewire::actingAs($admin)->test(Users::class)
            ->call('confirmRoleChange', $target->id, 'viewer')
            ->assertOk()
            ->assertDispatched('openModal');
    }

    // ── applyRoleChange: happy paths ─────────────────────────────────────────────────

    public function test_admin_demotes_operator_to_viewer_and_audits(): void
    {
        $admin = User::factory()->create();
        $target = User::factory()->operator()->create(['email' => 'op@example.com']);

        $this->actingAs($admin);
        $token = $this->confirmedToken('userRoleChanged', ['userId' => $target->id, 'role' => 'viewer']);

        Livewire::actingAs($admin)->test(Users::class)
            ->call('applyRoleChange', $token)
            ->assertDispatched('usersChanged');

        $this->assertSame(Role::Viewer, $target->fresh()->role);
        $this->assertDatabaseHas('audit_events', [
            'action' => 'user.role_change',
            'target' => 'op@example.com',
            'user_id' => $admin->id,
        ]);
    }

    public function test_admin_promotes_operator_to_admin(): void
    {
        $admin = User::factory()->create();
        $target = User::factory()->operator()->create();

        $this->actingAs($admin);
        $token = $this->confirmedToken('userRoleChanged', ['userId' => $target->id, 'role' => 'admin']);

        Livewire::actingAs($admin)->test(Users::class)
            ->call('applyRoleChange', $token);

        $this->assertSame(Role::Admin, $target->fresh()->role);
    }

    // ── Last-admin invariant ─────────────────────────────────────────────────────────

    public function test_sole_admin_cannot_be_demoted(): void
    {
        // Exactly one admin (self); an operator keeps the account count > 1.
        $admin = User::factory()->create();
        User::factory()->operator()->create();

        $this->actingAs($admin);
        $token = $this->confirmedToken('userRoleChanged', ['userId' => $admin->id, 'role' => 'viewer']);

        Livewire::actingAs($admin)->test(Users::class)
            ->call('applyRoleChange', $token)
            ->assertNotDispatched('usersChanged');

        // Role unchanged — the sole admin stays admin, no exception thrown.
        $this->assertSame(Role::Admin, $admin->fresh()->role);
        $this->assertSame(0, AuditEvent::where('action', 'user.role_change')->count());
    }

    public function test_one_of_two_admins_may_be_demoted(): void
    {
        $admin = User::factory()->create();
        $other = User::factory()->create(); // second admin (factory default role)

        $this->actingAs($admin);
        $token = $this->confirmedToken('userRoleChanged', ['userId' => $other->id, 'role' => 'operator']);

        Livewire::actingAs($admin)->test(Users::class)
            ->call('applyRoleChange', $token)
            ->assertDispatched('usersChanged');

        $this->assertSame(Role::Operator, $other->fresh()->role);
    }

    public function test_removing_the_sole_admin_is_refused_and_admin_survives(): void
    {
        // Two accounts so the last-ACCOUNT guard passes, but only one admin (self). The
        // acting admin removing itself is caught by the self-guard first; the last-admin
        // removal guard is the defense-in-depth behind it. Either way: the admin survives.
        $admin = User::factory()->create();
        User::factory()->operator()->create();

        $this->actingAs($admin);
        $token = $this->confirmedToken('userRemoved', ['userId' => $admin->id]);

        Livewire::actingAs($admin)->test(Users::class)
            ->call('remove', $token);

        $this->assertDatabaseHas('users', ['id' => $admin->id]);
        $this->assertSame(Role::Admin, $admin->fresh()->role);
    }

    // ── CreateUser modal: admin-only, default role, validation ───────────────────────

    public function test_non_admin_cannot_mount_create_user(): void
    {
        $operator = User::factory()->operator()->create();

        Livewire::actingAs($operator)->test(CreateUser::class)
            ->assertForbidden();
    }

    public function test_admin_creates_user_defaulting_to_operator(): void
    {
        $admin = User::factory()->create();

        Livewire::actingAs($admin)->test(CreateUser::class)
            ->set('name', 'New Op')
            ->set('email', 'newop@example.com')
            ->call('save')
            ->assertHasNoErrors();

        $new = User::where('email', 'newop@example.com')->first();
        $this->assertNotNull($new);
        $this->assertSame(Role::Operator, $new->role);
    }

    public function test_create_user_role_validation_rejects_invalid_role(): void
    {
        $admin = User::factory()->create();

        Livewire::actingAs($admin)->test(CreateUser::class)
            ->set('name', 'Bad Role')
            ->set('email', 'badrole@example.com')
            ->set('role', 'superuser')
            ->call('save')
            ->assertHasErrors('role');

        $this->assertNull(User::where('email', 'badrole@example.com')->first());
    }
}
