<?php

use App\Mail\ResetPasswordMail;
use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Facades\URL;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    config()->set('app.url', 'https://api.bowli.test');
    URL::forceRootUrl('https://api.bowli.test');
    URL::forceScheme('https');
});

it('sends a password reset email with a clickable api web url', function () {
    Notification::fake();

    $user = User::factory()->create([
        'email' => 'leon@example.com',
        'locale' => 'fr',
    ]);

    $this
        ->postJson('/api/auth/forgot-password', [
            'email' => 'LEON@example.com',
        ])
        ->assertOk()
        ->assertJsonPath('message', __('api.auth.password_reset_link_sent'));

    Notification::assertSentTo(
        $user,
        ResetPassword::class,
        function (ResetPassword $notification) use ($user): bool {
            $mail = $notification->toMail($user);

            expect($mail)->toBeInstanceOf(ResetPasswordMail::class);

            $url = $mail->resetUrl;

            expect($url)->toStartWith('https://api.bowli.test/password/reset/');

            parse_str((string) parse_url($url, PHP_URL_QUERY), $query);

            expect($query['email'])->toBe($user->email)
                ->and((string) parse_url($url, PHP_URL_PATH))->toStartWith('/password/reset/');

            $mail
                ->assertSeeInHtml('Réinitialise ton mot de passe')
                ->assertSeeInHtml('Choisir un nouveau mot de passe')
                ->assertSeeInHtml($user->name);

            return true;
        }
    );
});

it('uses the user locale for password reset emails', function () {
    Notification::fake();

    $user = User::factory()->create([
        'email' => 'leon@example.com',
        'locale' => 'en',
    ]);

    $this
        ->postJson('/api/auth/forgot-password', [
            'email' => $user->email,
        ])
        ->assertOk();

    Notification::assertSentTo(
        $user,
        ResetPassword::class,
        function (ResetPassword $notification) use ($user): bool {
            $mail = $notification->toMail($user);

            expect($mail)->toBeInstanceOf(ResetPasswordMail::class);

            $mail
                ->assertSeeInHtml('Reset your password')
                ->assertSeeInHtml('Choose a new password')
                ->assertDontSeeInHtml('Réinitialise ton mot de passe');

            return true;
        }
    );
});

it('does not reveal whether a reset email can be sent', function () {
    Notification::fake();

    $this
        ->postJson('/api/auth/forgot-password', [
            'email' => 'missing@example.com',
        ])
        ->assertOk()
        ->assertJsonPath('message', __('api.auth.password_reset_link_sent'));

    Notification::assertNothingSent();
});

it('resets the password and invalidates existing api tokens', function () {
    $user = User::factory()->create([
        'email' => 'leon@example.com',
        'password' => 'old-password',
    ]);
    $token = Password::broker()->createToken($user);

    $user->createToken('api');

    $this
        ->postJson('/api/auth/reset-password', [
            'email' => 'LEON@example.com',
            'password' => 'new-password',
            'password_confirmation' => 'new-password',
            'token' => $token,
        ])
        ->assertOk()
        ->assertJsonPath('message', __('api.auth.password_reset'));

    expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue();

    $this->assertDatabaseMissing('personal_access_tokens', [
        'tokenable_id' => $user->getKey(),
    ]);
});

it('shows the api password reset form from the email link', function () {
    $user = User::factory()->create([
        'email' => 'leon@example.com',
        'locale' => 'fr',
    ]);
    $token = Password::broker()->createToken($user);

    $this
        ->get(route('password.reset', [
            'email' => $user->email,
            'token' => $token,
        ], absolute: false))
        ->assertOk()
        ->assertViewIs('auth.reset-password')
        ->assertSee('Nouveau mot de passe')
        ->assertSee($user->email);
});

it('shows the api password reset form in the user locale', function () {
    $user = User::factory()->create([
        'email' => 'leon@example.com',
        'locale' => 'en',
    ]);
    $token = Password::broker()->createToken($user);

    $this
        ->get(route('password.reset', [
            'email' => $user->email,
            'token' => $token,
        ], absolute: false))
        ->assertOk()
        ->assertViewIs('auth.reset-password')
        ->assertSee('New password')
        ->assertSee('Update password')
        ->assertDontSee('Nouveau mot de passe');
});

it('resets the password from the api web form', function () {
    $user = User::factory()->create([
        'email' => 'leon@example.com',
        'locale' => 'fr',
        'password' => 'old-password',
    ]);
    $token = Password::broker()->createToken($user);

    $user->createToken('api');

    $this
        ->post('/password/reset', [
            'email' => 'LEON@example.com',
            'password' => 'new-password',
            'password_confirmation' => 'new-password',
            'token' => $token,
        ])
        ->assertOk()
        ->assertViewIs('auth.reset-password')
        ->assertSee('Mot de passe modifié');

    expect(Hash::check('new-password', $user->fresh()->password))->toBeTrue();

    $this->assertDatabaseMissing('personal_access_tokens', [
        'tokenable_id' => $user->getKey(),
    ]);
});

it('rejects an invalid password reset token', function () {
    $user = User::factory()->create([
        'email' => 'leon@example.com',
    ]);

    $this
        ->postJson('/api/auth/reset-password', [
            'email' => $user->email,
            'password' => 'new-password',
            'password_confirmation' => 'new-password',
            'token' => 'invalid-token',
        ])
        ->assertUnprocessable()
        ->assertJsonPath('message', __('passwords.token'));

    expect(Hash::check('new-password', $user->fresh()->password))->toBeFalse();
});
