<?php

use App\Console\Commands\PruneUnverifiedAccounts;
use App\Livewire\Auth\VerifyEmail;
use App\Models\Customer;
use App\Models\Subscription;
use App\Models\User;
use Illuminate\Support\Facades\File;
use Livewire\Livewire;

/**
 * A registration nobody confirmed does not sit in the database for ever.
 *
 * The page used to say "sign out and register again", which cannot work: signing
 * out frees nothing, the address is still taken by the unique index, and the
 * abandoned row stays. So the button discards the account, and anything nobody
 * comes back to goes by itself after five days.
 */
it('discards the registration and frees the address', function () {
    $user = User::factory()->create(['email' => 'neu@example.test', 'email_verified_at' => null]);

    Livewire::actingAs($user)
        ->test(VerifyEmail::class)
        ->call('deleteAccount');

    expect(User::query()->where('email', 'neu@example.test')->exists())->toBeFalse();
});

it('refuses to discard an account that already has a contract behind it', function () {
    // The webhook creates a customer's login from a paid checkout, and such a
    // user can sit on this page unconfirmed while already having a contract.
    // Deleting that is data loss dressed up as a convenience.
    $user = User::factory()->create(['email' => 'zahlt@example.test', 'email_verified_at' => null]);
    $customer = Customer::factory()->create(['email' => 'zahlt@example.test', 'user_id' => $user->id]);
    Subscription::factory()->create(['customer_id' => $customer->id, 'status' => 'active']);

    Livewire::actingAs($user)
        ->test(VerifyEmail::class)
        ->call('deleteAccount')
        ->assertDispatched('notify');

    expect($user->fresh())->not->toBeNull();
});

it('sends a confirmed account to its dashboard instead of deleting it', function () {
    // Closing a confirmed account is a different action with an invoice history
    // behind it — ConfirmCloseAccount, not this.
    //
    // Confirmed AFTER mounting, because mount() already redirects a verified
    // user away and there would be no component left to call anything on. The
    // case this guards is the real one: a tab left open on this page while the
    // link was opened in another window.
    $user = User::factory()->create(['email_verified_at' => null]);

    $page = Livewire::actingAs($user)->test(VerifyEmail::class);

    $user->forceFill(['email_verified_at' => now()])->save();

    $page->call('deleteAccount');

    expect($user->fresh())->not->toBeNull();
});

it('confirms in a modal rather than on one click', function () {
    // R23. And the modal mutates nothing itself: it dispatches the event the
    // page listens for, so the checks stay in the one place they already are.
    $page = File::get(resource_path('views/livewire/auth/verify-email.blade.php'));

    expect($page)->toContain("component: 'confirm-delete-own-account'");

    $modal = File::get(app_path('Livewire/ConfirmDeleteOwnAccount.php'));

    expect($modal)->toContain("dispatch('own-account-delete-confirmed')")
        ->and($modal)->not->toContain('$user');
});

it('no longer tells anybody to sign out and register again', function () {
    // Advice that could not work: signing out frees nothing.
    expect(__('verify_email.notice_delete'))->not->toBe('verify_email.notice_delete');

    foreach (['de', 'en'] as $locale) {
        expect(File::get(base_path("lang/{$locale}/verify_email.php")))
            ->not->toContain("'notice_signout'");
    }
});

// ---- The five-day sweep ----

it('removes a registration nobody confirmed after five days', function () {
    $stale = User::factory()->create(['email_verified_at' => null]);
    $stale->forceFill(['created_at' => now()->subDays(PruneUnverifiedAccounts::AFTER_DAYS + 1)])->save();

    $this->artisan('clupilot:prune-unverified')->assertSuccessful();

    expect(User::query()->whereKey($stale->id)->exists())->toBeFalse();
});

it('leaves a fresh registration alone', function () {
    // Long enough for somebody who registered on a Friday and found the mail in
    // a spam folder on Monday.
    $fresh = User::factory()->create(['email_verified_at' => null]);
    $fresh->forceFill(['created_at' => now()->subDays(PruneUnverifiedAccounts::AFTER_DAYS - 1)])->save();

    $this->artisan('clupilot:prune-unverified');

    expect(User::query()->whereKey($fresh->id)->exists())->toBeTrue();
});

it('never touches a confirmed account, however old', function () {
    $old = User::factory()->create(['email_verified_at' => now()->subYear()]);
    $old->forceFill(['created_at' => now()->subYears(2)])->save();

    $this->artisan('clupilot:prune-unverified');

    expect(User::query()->whereKey($old->id)->exists())->toBeTrue();
});

it('never touches an unconfirmed account with a customer behind it', function () {
    // Both links count: a customers row can point at the user, or merely carry
    // the same address — see Customer::emailTaken for why one can exist without
    // the other.
    $byId = User::factory()->create(['email_verified_at' => null]);
    $byId->forceFill(['created_at' => now()->subDays(30)])->save();
    Customer::factory()->create(['user_id' => $byId->id, 'email' => 'andere@example.test']);

    $byEmail = User::factory()->create(['email' => 'gleich@example.test', 'email_verified_at' => null]);
    $byEmail->forceFill(['created_at' => now()->subDays(30)])->save();
    Customer::factory()->create(['email' => 'gleich@example.test', 'user_id' => null]);

    $this->artisan('clupilot:prune-unverified');

    expect(User::query()->whereKey($byId->id)->exists())->toBeTrue()
        ->and(User::query()->whereKey($byEmail->id)->exists())->toBeTrue();
});

it('says what it would remove without removing it', function () {
    $stale = User::factory()->create(['email' => 'weg@example.test', 'email_verified_at' => null]);
    $stale->forceFill(['created_at' => now()->subDays(30)])->save();

    $this->artisan('clupilot:prune-unverified --dry-run')
        ->expectsOutputToContain('weg@example.test')
        ->assertSuccessful();

    expect(User::query()->whereKey($stale->id)->exists())->toBeTrue();
});

it('runs on the schedule, or it never runs at all', function () {
    $console = File::get(base_path('routes/console.php'));

    expect($console)->toContain("Schedule::command('clupilot:prune-unverified')")
        ->toContain('withoutOverlapping');
});

it('tells the person about the deadline, on the page and in the mail', function () {
    // Somebody who never got the mail is looking at the PAGE, and the deadline
    // is the reason they do not have to do anything about an abandoned attempt.
    // Matched on the number, not on a whole phrase: "fünf Tage lang" and "nach
    // fünf Tagen" are both correct German and an assertion on one of them tells
    // the next person to write the sentence a particular way.
    expect(__('verify_email.notice_wrong_address'))->toContain('fünf')
        ->and(__('verify_email.not_you'))->toContain('fünf')
        // And the sentence and the command agree on the number.
        ->and(PruneUnverifiedAccounts::AFTER_DAYS)->toBe(5);
});
