<?php

use App\Livewire\Auth\ForgotPassword;
use App\Livewire\Auth\ResetPassword;
use App\Mail\ResetPasswordMail;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Password;
use Livewire\Livewire;

/**
 * Getting back in after forgetting the password.
 *
 * There was no way to. Fortify's resetPasswords feature was switched off, so
 * there was no link on the sign-in form, no page and no route — a customer who
 * forgot their password was locked out of their own cloud until somebody
 * opened a shell. On a product whose selling point is that you can ring
 * somebody, that is a support call a week and an embarrassing one.
 */
it('offers the way back on the sign-in form', function () {
    $this->get(route('login'))
        ->assertOk()
        ->assertSee(__('auth.forgot_link'))
        ->assertSee(route('password.request'), false);
});

it('sends a link in this product’s own design', function () {
    Mail::fake();

    $user = User::factory()->create(['email' => 'kunde@example.test']);

    Livewire::test(ForgotPassword::class)
        ->set('email', $user->email)
        ->call('send')
        ->assertHasNoErrors();

    // Not the framework's default MailMessage: somebody who has just been
    // locked out is exactly the person a phishing mail is aimed at, and a
    // message that looks nothing like the rest of our post is one they cannot
    // check against the others.
    Mail::assertQueued(ResetPasswordMail::class, fn ($mail) => $mail->hasTo($user->email));
});

it('answers the same whether the address is known or not', function () {
    // Otherwise the form is a way of finding out who is a customer, which is
    // the first thing anybody targeting a business does.
    Mail::fake();

    $known = Livewire::test(ForgotPassword::class)->set('email', User::factory()->create()->email)->call('send');
    $unknown = Livewire::test(ForgotPassword::class)->set('email', 'niemand@example.test')->call('send');

    $known->assertSet('sent', true)->assertHasNoErrors();
    $unknown->assertSet('sent', true)->assertHasNoErrors();

    Mail::assertQueuedCount(1);
});

it('sets the new password and kills every other session', function () {
    $user = User::factory()->create(['password' => Hash::make('das-alte-passwort')]);
    $before = $user->remember_token;
    $token = Password::broker()->createToken($user);

    Livewire::test(ResetPassword::class, ['token' => $token])
        ->set('email', $user->email)
        ->set('password', 'ein-langes-neues-passwort')
        ->set('password_confirmation', 'ein-langes-neues-passwort')
        ->call('save')
        ->assertHasNoErrors()
        ->assertSet('done', true);

    $user->refresh();

    expect(Hash::check('ein-langes-neues-passwort', $user->password))->toBeTrue()
        // Whoever knew the old password may still be signed in somewhere. A
        // reset that leaves them there has fixed nothing.
        ->and($user->remember_token)->not->toBe($before);
});

it('does not sign the visitor in', function () {
    // The link arrived by email. A mailbox somebody else can read would
    // otherwise be a session somebody else gets.
    $user = User::factory()->create();
    $token = Password::broker()->createToken($user);

    Livewire::test(ResetPassword::class, ['token' => $token])
        ->set('email', $user->email)
        ->set('password', 'ein-langes-neues-passwort')
        ->set('password_confirmation', 'ein-langes-neues-passwort')
        ->call('save');

    expect(auth()->check())->toBeFalse();
});

it('refuses a token that is not this user’s', function () {
    $mine = User::factory()->create();
    $theirs = User::factory()->create();

    Livewire::test(ResetPassword::class, ['token' => Password::broker()->createToken($theirs)])
        ->set('email', $mine->email)
        ->set('password', 'ein-langes-neues-passwort')
        ->set('password_confirmation', 'ein-langes-neues-passwort')
        ->call('save')
        ->assertHasErrors('email')
        ->assertSet('done', false);
});

it('spends the token, so a forwarded link cannot be used twice', function () {
    $user = User::factory()->create();
    $token = Password::broker()->createToken($user);

    $reset = fn () => Livewire::test(ResetPassword::class, ['token' => $token])
        ->set('email', $user->email)
        ->set('password', 'ein-langes-neues-passwort')
        ->set('password_confirmation', 'ein-langes-neues-passwort')
        ->call('save');

    $reset()->assertSet('done', true);
    $reset()->assertSet('done', false)->assertHasErrors('email');
});

it('insists on a password long enough to be worth resetting to', function () {
    $user = User::factory()->create();

    Livewire::test(ResetPassword::class, ['token' => Password::broker()->createToken($user)])
        ->set('email', $user->email)
        ->set('password', 'kurz')
        ->set('password_confirmation', 'kurz')
        ->call('save')
        ->assertHasErrors('password');
});

it('renders the reset mail, which it could not do at all', function () {
    // The view was missing its closing </x-mail.layout>. The component's
    // contents were never terminated, the layout's own @if ran off the end of
    // the file, and the mail raised a Blade syntax error instead of rendering —
    // so the link never reached anybody who asked for one. Every test until now
    // asserted that the mail was QUEUED, which it faithfully was.
    $user = App\Models\User::factory()->create(['name' => 'Bea Berger']);

    $html = (new App\Mail\ResetPasswordMail($user, 'https://example.test/reset/abc', 60))
        ->render();

    expect($html)->toContain('https://example.test/reset/abc')
        ->toContain(__('reset_password.action'));
});

it('closes every mail layout it opens', function () {
    // One unterminated component is a mail that cannot be sent, and the failure
    // only shows at render time — long after the test that queued it went green.
    foreach (glob(resource_path('views/mail/*.blade.php')) as $file) {
        $body = file_get_contents($file);

        expect(substr_count($body, '<x-mail.layout'))
            ->toBe(substr_count($body, '</x-mail.layout>'), basename($file));
    }
});
