<?php

use App\Mail\VerifyEmailMail;
use App\Models\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\URL;

/**
 * Nobody gets into the portal on an address they have not proved is theirs.
 *
 * Anybody can type a stranger's address into a registration form. Until it is
 * confirmed, an account is not known to belong to the person holding it — and
 * everything behind the portal, the servers and the backups and the invoices,
 * is worth as much to the person who typed it as to the person who owns it.
 */
it('does not let an unconfirmed account into the portal', function () {
    $user = User::factory()->unverified()->create();

    $this->actingAs($user)
        ->get(route('dashboard'))
        ->assertRedirect(route('verification.notice'));
});

it('lets a confirmed account through', function () {
    $this->actingAs(User::factory()->create())
        ->get(route('dashboard'))
        ->assertOk();
});

it('sends the confirmation in this product’s design, not the framework’s', function () {
    // Laravel's own notification would arrive with its logo, its button and
    // its footer — as the first thing anybody ever receives from CluPilot.
    Mail::fake();

    User::factory()->unverified()->create()->sendEmailVerificationNotification();

    Mail::assertQueued(VerifyEmailMail::class);
});

it('confirms the address when the signed link is opened', function () {
    $user = User::factory()->unverified()->create();

    $this->actingAs($user)->get(verificationUrlFor($user))->assertRedirect();

    expect($user->refresh()->hasVerifiedEmail())->toBeTrue();
});

it('refuses a link whose signature has been tampered with', function () {
    $user = User::factory()->unverified()->create();

    $tampered = verificationUrlFor($user).'x';

    $this->actingAs($user)->get($tampered)->assertForbidden();

    expect($user->refresh()->hasVerifiedEmail())->toBeFalse();
});

it('refuses a link that has expired', function () {
    $user = User::factory()->unverified()->create();
    $url = verificationUrlFor($user, 60);

    $this->travel(61)->minutes();

    $this->actingAs($user)->get($url)->assertForbidden();

    expect($user->refresh()->hasVerifiedEmail())->toBeFalse();
});

it('stops working once the address it was issued for has changed', function () {
    // The address is part of what is signed. Without that, a link mailed to an
    // address somebody has since corrected would still confirm the account —
    // confirming an address nobody can read.
    $user = User::factory()->unverified()->create(['email' => 'typo@example.com']);
    $url = verificationUrlFor($user);

    $user->update(['email' => 'correct@example.com']);

    $this->actingAs($user)->get($url)->assertForbidden();

    expect($user->refresh()->hasVerifiedEmail())->toBeFalse();
});

it('will not let one account confirm another', function () {
    $mine = User::factory()->unverified()->create();
    $theirs = User::factory()->unverified()->create();

    $this->actingAs($mine)->get(verificationUrlFor($theirs))->assertForbidden();

    expect($theirs->refresh()->hasVerifiedEmail())->toBeFalse();
});

it('throttles the resend button', function () {
    // Without it this is a button that makes the server send mail to an
    // arbitrary address as fast as it can be clicked.
    Mail::fake();

    $user = User::factory()->unverified()->create();

    $component = Livewire\Livewire::actingAs($user)->test(App\Livewire\Auth\VerifyEmail::class);

    foreach (range(1, 6) as $ignored) {
        $component->call('resend');
    }

    Mail::assertQueuedCount(6);

    $component->call('resend');

    Mail::assertQueuedCount(6);
});

it('sends nobody back to the waiting room once they are through', function () {
    $this->actingAs(User::factory()->create())
        ->get(route('verification.notice'))
        ->assertRedirect(route('dashboard'));
});

/** The signed link the mail carries, built the same way VerifyEmailMail builds it. */
function verificationUrlFor(User $user, int $minutes = 60): string
{
    return URL::temporarySignedRoute('verification.verify', now()->addMinutes($minutes), [
        'id' => $user->getKey(),
        'hash' => sha1($user->getEmailForVerification()),
    ]);
}
