<?php

use App\Console\Commands\PruneDormantAccounts;
use App\Console\Commands\PruneUnverifiedAccounts;
use App\Mail\DormantAccountWarningMail;
use App\Models\Customer;
use App\Models\User;
use App\Models\UserDevice;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Mail;

/**
 * A confirmed account that never had a package goes after a year.
 *
 * The five-day sweep is about registrations nobody confirmed — see
 * UnverifiedAccountTest. This is the other half of the same question the owner
 * asked: "wird nun der account gelöscht nach 5 tagen?" No. Only an unconfirmed
 * one. A confirmed account stays, unless a year passes with no package on it at
 * all — and then it is told a fortnight in advance.
 */
function dormantUser(int $daysOld, array $attributes = []): User
{
    $user = User::factory()->create(array_merge(['email_verified_at' => now()->subDays($daysOld)], $attributes));

    $user->forceFill(['created_at' => now()->subDays($daysOld)])->save();

    return $user;
}

it('warns a fortnight before, rather than deleting out of the blue', function () {
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS - PruneDormantAccounts::WARN_DAYS_BEFORE);

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

    Mail::assertQueued(DormantAccountWarningMail::class, fn ($mail) => $mail->hasTo($user->email));

    // Still here. The warning is a warning, not the deletion.
    expect($user->fresh())->not->toBeNull()
        ->and($user->fresh()->dormant_warned_at)->not->toBeNull();
});

it('warns once, not every night for a fortnight', function () {
    Mail::fake();

    dormantUser(PruneDormantAccounts::AFTER_DAYS - 5);

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

    Mail::assertQueuedCount(1);
});

it('deletes the account a year on, once the warning has had its fortnight', function () {
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS + 1);
    $user->forceFill(['dormant_warned_at' => now()->subDays(PruneDormantAccounts::WARN_DAYS_BEFORE + 1)])->save();

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

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

it('never deletes an account that was never warned', function () {
    // The stamp is what permits the deletion. An installation whose mail server
    // was down for a month delays the deletion; it does not skip the notice.
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS * 2);

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

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

it('gives the warning its full fortnight before deleting', function () {
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS + 30);
    $user->forceFill(['dormant_warned_at' => now()->subDays(PruneDormantAccounts::WARN_DAYS_BEFORE - 1)])->save();

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

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

it('leaves an account alone that signed in inside the year', function () {
    // What the terms promise: signing in once starts the period again. There is
    // no last_login_at on users, and the device rows carry exactly this.
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS * 3);
    UserDevice::query()->create([
        'guard' => 'web',
        'authenticatable_id' => $user->id,
        'token_hash' => hash('sha256', 'egal'),
        'name' => 'Firefox',
        'last_seen_at' => now()->subDays(30),
    ]);

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

    Mail::assertNothingQueued();
    expect(User::query()->whereKey($user->id)->exists())->toBeTrue();
});

it('counts a device of another guard as nobody, because operator 1 is not customer 1', function () {
    // R21. A device row of the operator guard with the same id says nothing
    // about this customer, and treating it as activity would keep an abandoned
    // account alive for as long as an operator keeps signing in.
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS - 1);
    UserDevice::query()->create([
        'guard' => 'operator',
        'authenticatable_id' => $user->id,
        'token_hash' => hash('sha256', 'anders'),
        'name' => 'Konsole',
        'last_seen_at' => now(),
    ]);

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

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

it('never touches an unconfirmed account, which is the other sweep business', function () {
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS * 2, ['email_verified_at' => null]);

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

    Mail::assertNothingQueued();
    expect(User::query()->whereKey($user->id)->exists())->toBeTrue();
});

it('never touches an account with a customer record, by id or by address', function () {
    // A customer record means somebody in the business knows this person, and
    // everything commercial — orders, contracts, invoices kept for seven years —
    // hangs off one. Both links count: see Customer::emailTaken.
    Mail::fake();

    $byId = dormantUser(PruneDormantAccounts::AFTER_DAYS * 2);
    Customer::factory()->create(['user_id' => $byId->id, 'email' => 'andere@example.test']);

    $byEmail = dormantUser(PruneDormantAccounts::AFTER_DAYS * 2, ['email' => 'gleich@example.test']);
    Customer::factory()->create(['email' => 'gleich@example.test', 'user_id' => null]);

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

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

it('says what it would do without doing it', function () {
    Mail::fake();

    $user = dormantUser(PruneDormantAccounts::AFTER_DAYS - 1, ['email' => 'ruht@example.test']);

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

    Mail::assertNothingQueued();
    expect($user->fresh()->dormant_warned_at)->toBeNull();
});

it('runs on the schedule, or it never runs at all', function () {
    // Daily even though the window is a year: the notice is what permits the
    // deletion, so the run that sends it has to happen every day.
    expect(File::get(base_path('routes/console.php')))
        ->toContain("Schedule::command('clupilot:prune-dormant')")
        ->toContain('withoutOverlapping');
});

// ---- What a customer is told ----

it('states both deadlines in the portal, not just the five days', function () {
    // The question this answers: "wird nun der account gelöscht nach 5 tagen?"
    // Reading only the verification page, the answer looked like yes.
    $settings = File::get(resource_path('views/livewire/settings.blade.php'));

    expect($settings)->toContain("__('settings.lifecycle_unverified'")
        ->and($settings)->toContain("__('settings.lifecycle_dormant'")
        // From the commands themselves, so the page cannot promise one number
        // while the sweep uses another.
        ->and($settings)->toContain('PruneUnverifiedAccounts::AFTER_DAYS')
        ->and($settings)->toContain('PruneDormantAccounts::WARN_DAYS_BEFORE');
});

it('regulates both deadlines in the terms, because that is where it was promised', function () {
    $terms = $this->get(route('legal.agb'))->assertOk()->getContent();

    expect($terms)->toContain('Löschfristen')
        ->and($terms)->toContain('Nicht bestätigte Registrierungen')
        ->and($terms)->toContain('Bestätigte Konten ohne Paket')
        // And the number in the text is the number the command uses.
        ->and($terms)->toContain(PruneUnverifiedAccounts::AFTER_DAYS.' Tagen');
});
