<?php

use App\Models\Permission;
use App\Models\Role;
use App\Models\User;

function grantManageUserDirectory(User $user): void
{
    $perm = Permission::firstOrCreate(['name' => 'manage user directory', 'guard_name' => 'web']);
    Permission::firstOrCreate(['name' => 'view user directory', 'guard_name' => 'web']);
    $user->givePermissionTo($perm);
}

function defaultRole(): Role
{
    return Role::firstOrCreate(['name' => 'member', 'guard_name' => 'web']);
}

const STRONG_PASSWORD = 'Str0ng!Passw0rd2026';

test('guest cannot access user index', function () {
    $this->get('/users')->assertRedirect('/login');
});

test('user without permission gets 403', function () {
    $u = User::factory()->create();
    $this->actingAs($u)->get('/users')->assertForbidden();
});

test('store creates user, hashes password, assigns roles', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $role = defaultRole();

    $response = $this->actingAs($admin)->postJson('/users', [
        'name' => 'Jane Doe',
        'email' => 'jane@example.com',
        'password' => STRONG_PASSWORD,
        'roles' => [$role->id],
    ]);

    $response->assertOk()->assertJson(['success' => true]);

    $created = User::where('email', 'jane@example.com')->first();
    expect($created)->not->toBeNull();
    expect($created->password)->not->toBe(STRONG_PASSWORD);
    expect($created->hasRole('member'))->toBeTrue();
});

test('store rejects weak password', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $role = defaultRole();

    $this->actingAs($admin)->postJson('/users', [
        'name' => 'Weak Pass',
        'email' => 'weak@example.com',
        'password' => 'short',
        'roles' => [$role->id],
    ])->assertStatus(422);
});

test('store rejects duplicate email', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $role = defaultRole();
    User::factory()->create(['email' => 'taken@example.com']);

    $this->actingAs($admin)->postJson('/users', [
        'name' => 'Dup Email',
        'email' => 'taken@example.com',
        'password' => STRONG_PASSWORD,
        'roles' => [$role->id],
    ])->assertStatus(422);
});

test('store rejects name with digits', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $role = defaultRole();

    $this->actingAs($admin)->postJson('/users', [
        'name' => 'John123',
        'email' => 'john@example.com',
        'password' => STRONG_PASSWORD,
        'roles' => [$role->id],
    ])->assertStatus(422);
});

test('store requires at least one role', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);

    $this->actingAs($admin)->postJson('/users', [
        'name' => 'No Role',
        'email' => 'norole@example.com',
        'password' => STRONG_PASSWORD,
        'roles' => [],
    ])->assertStatus(422);
});

test('update can change name and reset roles without touching password', function () {
    $admin = User::factory()->create(['name' => 'Admin User']);
    grantManageUserDirectory($admin);
    $r1 = defaultRole();
    $r2 = Role::firstOrCreate(['name' => 'editor', 'guard_name' => 'web']);
    $target = User::factory()->create(['name' => 'Old Name']);
    $target->assignRole($r1);
    $originalHash = $target->password;

    $this->actingAs($admin)->putJson("/users/{$target->id}", [
        'name' => 'Renamed User',
        'email' => $target->email,
        'roles' => [$r2->id],
    ])->assertOk();

    $target->refresh();
    expect($target->name)->toBe('Renamed User');
    expect($target->password)->toBe($originalHash);
    expect($target->hasRole('editor'))->toBeTrue();
    expect($target->hasRole('member'))->toBeFalse();
});

test('update changes password when provided', function () {
    $admin = User::factory()->create(['name' => 'Admin User']);
    grantManageUserDirectory($admin);
    $role = defaultRole();
    $target = User::factory()->create(['name' => 'Target User']);
    $target->assignRole($role);
    $original = $target->password;

    $this->actingAs($admin)->putJson("/users/{$target->id}", [
        'name' => 'Target User',
        'email' => $target->email,
        'password' => STRONG_PASSWORD,
        'roles' => [$role->id],
    ])->assertOk();

    expect($target->fresh()->password)->not->toBe($original);
});

test('toggleStatus deactivates and activates user', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $target = User::factory()->create(['is_active' => 1]);

    $this->actingAs($admin)
        ->postJson('/users/toggle-status', ['id' => $target->id, 'status' => 'deactivate'])
        ->assertOk();
    expect((bool) $target->fresh()->is_active)->toBeFalse();

    $this->actingAs($admin)
        ->postJson('/users/toggle-status', ['id' => $target->id, 'status' => 'activate'])
        ->assertOk();
    expect((bool) $target->fresh()->is_active)->toBeTrue();
});

test('destroy soft deletes user', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $target = User::factory()->create();

    $this->actingAs($admin)->deleteJson("/users/{$target->id}")->assertOk();
    expect(User::withTrashed()->find($target->id)->trashed())->toBeTrue();
});

test('restore brings soft-deleted user back', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $target = User::factory()->create();
    $target->delete();

    $this->actingAs($admin)->postJson("/users/{$target->id}/restore")->assertOk();
    expect(User::find($target->id)->trashed())->toBeFalse();
});

test('forceDelete blocks self-deletion', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $admin->delete();

    $this->actingAs($admin)
        ->deleteJson("/users/{$admin->id}/force")
        ->assertStatus(403)
        ->assertJson(['success' => false]);

    expect(User::withTrashed()->find($admin->id))->not->toBeNull();
});

test('forceDelete permanently removes another user', function () {
    $admin = User::factory()->create();
    grantManageUserDirectory($admin);
    $target = User::factory()->create();
    $target->delete();

    $this->actingAs($admin)->deleteJson("/users/{$target->id}/force")->assertOk();
    expect(User::withTrashed()->find($target->id))->toBeNull();
});
