<?php

namespace Tests\Feature\Auth;

use App\Domain\Identity\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Tests\TestCase;

class LoginTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();
        RateLimiter::clear('user@mams.test|127.0.0.1');

        // The SPA authenticates over a stateful session; Sanctum only starts one
        // when the request originates from a configured first-party domain.
        $this->withHeader('Origin', config('app.url'));
    }

    private function user(array $attributes = []): User
    {
        return User::factory()->create(array_merge([
            'email' => 'user@mams.test',
            'password' => Hash::make('secret-password'),
            'status' => 'active',
        ], $attributes));
    }

    public function test_a_user_can_sign_in_with_valid_credentials(): void
    {
        $this->user();

        $response = $this->postJson('/api/v1/auth/login', [
            'email' => 'user@mams.test',
            'password' => 'secret-password',
        ]);

        $response->assertOk()
            ->assertJsonPath('success', true)
            ->assertJsonPath('data.email', 'user@mams.test');

        $this->assertAuthenticated();
    }

    public function test_login_records_the_last_login_metadata(): void
    {
        $user = $this->user();

        $this->postJson('/api/v1/auth/login', [
            'email' => 'user@mams.test',
            'password' => 'secret-password',
        ])->assertOk();

        $this->assertNotNull($user->fresh()->last_login_at);
    }

    public function test_invalid_credentials_are_rejected(): void
    {
        $this->user();

        $this->postJson('/api/v1/auth/login', [
            'email' => 'user@mams.test',
            'password' => 'wrong-password',
        ])->assertStatus(422)
            ->assertJsonPath('success', false)
            ->assertJsonStructure(['errors' => ['email']]);

        $this->assertGuest();
    }

    public function test_a_suspended_user_cannot_hold_a_session(): void
    {
        $this->user(['status' => 'suspended']);

        $this->postJson('/api/v1/auth/login', [
            'email' => 'user@mams.test',
            'password' => 'secret-password',
        ])->assertStatus(403)->assertJsonPath('success', false);

        $this->assertGuest();
    }

    public function test_repeated_failures_are_rate_limited(): void
    {
        $this->user();

        foreach (range(1, 5) as $ignored) {
            $this->postJson('/api/v1/auth/login', [
                'email' => 'user@mams.test',
                'password' => 'wrong-password',
            ])->assertStatus(422);
        }

        $response = $this->postJson('/api/v1/auth/login', [
            'email' => 'user@mams.test',
            'password' => 'secret-password',
        ]);

        $response->assertStatus(422);
        $this->assertStringContainsString('seconds', $response->json('errors.email.0'));
        $this->assertGuest();
    }

    public function test_me_requires_authentication(): void
    {
        $this->getJson('/api/v1/auth/me')
            ->assertStatus(401)
            ->assertJsonPath('success', false);
    }

    public function test_an_authenticated_user_can_read_their_profile_and_sign_out(): void
    {
        $user = $this->user();

        $this->actingAs($user)->getJson('/api/v1/auth/me')
            ->assertOk()
            ->assertJsonPath('data.email', 'user@mams.test')
            ->assertJsonStructure(['data' => ['permissions']]);

        $this->actingAs($user)->postJson('/api/v1/auth/logout')->assertOk();
    }

    public function test_errors_never_leak_a_stack_trace(): void
    {
        $this->getJson('/api/v1/does-not-exist')
            ->assertStatus(404)
            ->assertJsonPath('success', false)
            ->assertJsonMissingPath('trace');
    }

    public function test_signing_in_returns_the_permissions_the_ui_renders_from(): void
    {
        $this->seed([\Database\Seeders\PermissionSeeder::class, \Database\Seeders\RoleSeeder::class]);

        $this->user()->assignRole('branch-manager');

        $response = $this->postJson('/api/v1/auth/login', [
            'email' => 'user@mams.test',
            'password' => 'secret-password',
        ])->assertOk();

        // Without this the client signs someone in and shows them no menu at
        // all until they happen to reload the page.
        $this->assertNotEmpty($response->json('data.permissions'));
        $this->assertContains('applicant.view', $response->json('data.permissions'));
    }
}
