<?php

namespace Tests\Feature;

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 Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
use Livewire\Livewire;
use Tests\TestCase;

class MultiUserTest extends TestCase
{
    use RefreshDatabase;

    /** Insert a raw session row for $user (mirrors SessionService's table contract). */
    private function seedSession(User $user, string $id): void
    {
        DB::table('sessions')->insert([
            'id' => $id,
            'user_id' => $user->id,
            'ip_address' => '203.0.113.1',
            'user_agent' => 'Mozilla/5.0 Chrome',
            'payload' => base64_encode(serialize([])),
            'last_activity' => now()->getTimestamp(),
        ]);
    }

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

        Livewire::actingAs($admin)->test(CreateUser::class)
            ->set('name', 'Erika Mustermann')
            ->set('email', 'erika@example.com')
            ->call('save')
            ->assertHasNoErrors()
            // The clear-text temp password is revealed once in component state.
            ->assertSet('createdName', 'Erika Mustermann')
            ->assertSet('tempPassword', fn ($pw) => is_string($pw) && strlen($pw) === 16);

        $new = User::where('email', 'erika@example.com')->first();
        $this->assertNotNull($new);
        $this->assertTrue($new->must_change_password);
        // Password is hashed at rest (never the clear text).
        $this->assertNotSame('', $new->password);
        $this->assertNotSame(16, strlen($new->password));

        $this->assertDatabaseHas('audit_events', [
            'action' => 'user.create',
            'target' => 'erika@example.com',
            'user_id' => $admin->id,
        ]);
    }

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

        $component = Livewire::actingAs($admin)->test(CreateUser::class)
            ->set('name', 'Temp User')
            ->set('email', 'temp@example.com')
            ->call('save');

        $temp = $component->get('tempPassword');
        $new = User::where('email', 'temp@example.com')->first();

        $this->assertTrue(Hash::check($temp, $new->password));
    }

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

        Livewire::actingAs($admin)->test(CreateUser::class)
            ->set('name', 'Set Pw')
            ->set('email', 'setpw@example.com')
            ->set('password', 'Chosen-Pass-123')
            ->call('save')
            ->assertHasNoErrors()
            ->assertSet('tempPassword', null) // no reveal — the operator already knows it
            ->assertDispatched('usersChanged');

        $new = User::where('email', 'setpw@example.com')->first();
        $this->assertNotNull($new);
        $this->assertFalse($new->must_change_password); // operator-set = the user's own login
        $this->assertTrue(Hash::check('Chosen-Pass-123', $new->password));
    }

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

        Livewire::actingAs($admin)->test(CreateUser::class)
            ->set('name', 'Weak')
            ->set('email', 'weak@example.com')
            ->set('password', 'short')
            ->call('save')
            ->assertHasErrors('password');

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

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

        Livewire::actingAs($admin)->test(CreateUser::class)
            ->set('name', 'Dup')
            ->set('email', 'taken@example.com')
            ->call('save')
            ->assertHasErrors('email');

        $this->assertSame(2, User::count());
    }

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

        Livewire::actingAs($admin)->test(CreateUser::class)
            ->call('finish')
            ->assertDispatched('usersChanged')
            ->assertDispatched('closeModal');
    }

    public function test_cannot_delete_self(): void
    {
        $admin = User::factory()->create();
        User::factory()->create(); // keep count > 1 so only the self-guard is in play

        Livewire::actingAs($admin)->test(Users::class)
            ->call('confirmRemove', $admin->id)
            ->assertNotDispatched('openModal');

        $this->actingAs($admin);
        $token = ConfirmToken::issue('userRemoved', ['userId' => $admin->id]);
        ConfirmToken::confirm($token);
        Livewire::actingAs($admin)->test(Users::class)
            ->call('remove', $token);

        $this->assertDatabaseHas('users', ['id' => $admin->id]);
        $this->assertSame(0, AuditEvent::where('action', 'user.delete')->count());
    }

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

        $this->assertSame(1, User::count());

        Livewire::actingAs($admin)->test(Users::class)
            ->call('confirmRemove', $admin->id)
            ->assertNotDispatched('openModal');

        $this->actingAs($admin);
        $token = ConfirmToken::issue('userRemoved', ['userId' => $admin->id]);
        ConfirmToken::confirm($token);
        Livewire::actingAs($admin)->test(Users::class)
            ->call('remove', $token);

        $this->assertDatabaseHas('users', ['id' => $admin->id]);
    }

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

        Livewire::actingAs($admin)->test(Users::class)
            ->call('confirmRemove', $victim->id)
            ->assertDispatched('openModal');
    }

    public function test_remove_deletes_user_kills_sessions_and_audits(): void
    {
        config(['session.driver' => 'database']);

        $admin = User::factory()->create();
        $victim = User::factory()->create(['email' => 'victim@example.com', 'remember_token' => 'victim-token']);
        $this->seedSession($victim, 'victim-sess');

        $this->actingAs($admin);
        $token = ConfirmToken::issue('userRemoved', ['userId' => $victim->id]);
        ConfirmToken::confirm($token);
        Livewire::actingAs($admin)->test(Users::class)
            ->call('remove', $token);

        // User gone.
        $this->assertDatabaseMissing('users', ['id' => $victim->id]);
        // SessionService::logoutUserEverywhere ran BEFORE delete — rows cleared.
        $this->assertSame(0, DB::table('sessions')->where('id', 'victim-sess')->count());
        // Audit row written with the current actor.
        $this->assertDatabaseHas('audit_events', [
            'action' => 'user.delete',
            'target' => 'victim@example.com',
            'user_id' => $admin->id,
        ]);
    }

    public function test_per_row_logout_rotates_target_token_and_audits(): void
    {
        config(['session.driver' => 'database']);

        $admin = User::factory()->create();
        $target = User::factory()->create(['email' => 'target@example.com', 'remember_token' => 'target-token']);
        $this->seedSession($target, 'target-sess');

        $this->actingAs($admin);
        $token = ConfirmToken::issue('userLoggedOut', ['userId' => $target->id]);
        ConfirmToken::confirm($token);
        Livewire::actingAs($admin)->test(Users::class)
            ->call('logout', $token);

        // Target still exists but is signed out everywhere + token rotated.
        $this->assertDatabaseHas('users', ['id' => $target->id]);
        $this->assertSame(0, DB::table('sessions')->where('id', 'target-sess')->count());
        $this->assertNotSame('target-token', $target->fresh()->remember_token);
        $this->assertNotNull($target->fresh()->remember_token);

        $this->assertDatabaseHas('audit_events', [
            'action' => 'user.logout',
            'target' => 'target@example.com',
            'user_id' => $admin->id,
        ]);
    }

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

        Livewire::actingAs($admin)->test(Users::class)
            ->call('confirmLogout', $admin->id)
            ->assertNotDispatched('openModal');

        $this->actingAs($admin);
        $token = ConfirmToken::issue('userLoggedOut', ['userId' => $admin->id]);
        ConfirmToken::confirm($token);
        Livewire::actingAs($admin)->test(Users::class)
            ->call('logout', $token);

        $this->assertSame(0, AuditEvent::where('action', 'user.logout')->count());
    }

    public function test_list_renders_all_users(): void
    {
        $admin = User::factory()->create(['name' => 'Aaron Admin']);
        User::factory()->create(['name' => 'Bonnie Boss']);

        Livewire::actingAs($admin)->test(Users::class)
            ->assertOk()
            ->assertViewHas('users', fn ($users) => $users->count() === 2)
            ->assertSee('Aaron Admin')
            ->assertSee('Bonnie Boss');
    }
}
