<?php

declare(strict_types=1);

use App\Jobs\SendNewLeadsDigestJob;
use App\Mail\NewLeadNotification;
use App\Mail\NewLeadsDigestMail;
use App\Models\Deal;
use App\Models\Tenant;
use App\Models\User;
use App\Services\NotificationService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;

uses(DatabaseTransactions::class);

beforeEach(function () {
    Mail::fake();
});

function digestUser(Tenant $tenant, string $email, bool $emailOn): User
{
    return User::factory()->create([
        'tenant_id' => $tenant->id,
        'email' => $email,
        'notification_preferences' => [
            'new_lead' => ['email' => $emailOn, 'inapp' => true],
        ],
    ]);
}

it('шлёт одно письмо-сводку с N сделками подписанному пользователю', function () {
    $tenant = Tenant::factory()->create();
    digestUser($tenant, 'digest-on@example.test', true);
    DB::statement('SET app.current_tenant_id = '.$tenant->id);
    Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(5), 'is_test' => false]);
    Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(10), 'is_test' => false]);
    Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(15), 'is_test' => false]);

    (new SendNewLeadsDigestJob)->handle(app(NotificationService::class));

    Mail::assertSent(
        NewLeadsDigestMail::class,
        fn (NewLeadsDigestMail $m) => $m->hasTo('digest-on@example.test') && $m->deals->count() === 3,
    );
});

it('не шлёт сводку пользователю с выключенным email', function () {
    $tenant = Tenant::factory()->create();
    digestUser($tenant, 'digest-off@example.test', false);
    DB::statement('SET app.current_tenant_id = '.$tenant->id);
    Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(5), 'is_test' => false]);

    (new SendNewLeadsDigestJob)->handle(app(NotificationService::class));

    Mail::assertNotSent(
        NewLeadsDigestMail::class,
        fn (NewLeadsDigestMail $m) => $m->hasTo('digest-off@example.test'),
    );
});

it('не шлёт сводку, если за окно нет новых сделок', function () {
    $tenant = Tenant::factory()->create();
    digestUser($tenant, 'digest-old@example.test', true);
    DB::statement('SET app.current_tenant_id = '.$tenant->id);
    Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(45), 'is_test' => false]);

    (new SendNewLeadsDigestJob)->handle(app(NotificationService::class));

    Mail::assertNotSent(
        NewLeadsDigestMail::class,
        fn (NewLeadsDigestMail $m) => $m->hasTo('digest-old@example.test'),
    );
});

it('повторный прогон в том же окне НЕ дублирует дайджест (N-4)', function () {
    $tenant = Tenant::factory()->create();
    digestUser($tenant, 'digest-dup@example.test', true);
    DB::statement('SET app.current_tenant_id = '.$tenant->id);
    Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(5), 'is_test' => false]);
    Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(10), 'is_test' => false]);

    // ранбук R3b сам велит дёргать джоб вручную → два прогона в одном окне
    (new SendNewLeadsDigestJob)->handle(app(NotificationService::class));
    (new SendNewLeadsDigestJob)->handle(app(NotificationService::class));

    // письмо-сводка ушло РОВНО один раз — уже отправленные сделки не дублируются
    Mail::assertSent(NewLeadsDigestMail::class, 1);
});

it('при падении отправки НЕ помечает сделки — следующий прогон повторит (N-4)', function () {
    $tenant = Tenant::factory()->create();
    digestUser($tenant, 'digest-fail@example.test', true);
    DB::statement('SET app.current_tenant_id = '.$tenant->id);
    $deal = Deal::factory()->for($tenant)->create(['received_at' => now()->subMinutes(5), 'is_test' => false]);

    // джоб прерван ДО отправки: notify бросает (анон-subclass — тип-чистый для larastan)
    $throwing = new class extends NotificationService
    {
        public function notifyNewLeadsDigest(Tenant $tenant, Collection $deals): void
        {
            throw new RuntimeException('mail down');
        }
    };

    try {
        (new SendNewLeadsDigestJob)->handle($throwing);
    } catch (Throwable) {
        // ожидаемо — джоб упал, очередь повторит
    }

    expect(Cache::has('digest_sent:'.$deal->id))->toBeFalse();

    // следующий прогон с рабочим сервисом — дайджест НЕ потерян, уходит
    (new SendNewLeadsDigestJob)->handle(app(NotificationService::class));
    Mail::assertSent(NewLeadsDigestMail::class, 1);
});

it('notifyNewLead больше не шлёт пер-лид письмо', function () {
    $tenant = Tenant::factory()->create();
    digestUser($tenant, 'perlead@example.test', true);
    DB::statement('SET app.current_tenant_id = '.$tenant->id);
    $deal = Deal::factory()->for($tenant)->create(['received_at' => now(), 'is_test' => false]);

    app(NotificationService::class)->notifyNewLead($tenant, $deal);

    Mail::assertNotSent(NewLeadNotification::class);
});
