<?php

declare(strict_types=1);

use App\Jobs\ProcessWebhookJob;
use App\Mail\InvoicePaidNotification;
use App\Mail\LowBalanceNotification;
use App\Mail\TopupSuccessNotification;
use App\Mail\ZeroBalanceNotification;
use App\Models\InAppNotification;
use App\Models\RejectedDealsLog;
use App\Models\Tenant;
use App\Models\User;
use App\Services\NotificationService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;

uses(DatabaseTransactions::class);

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

function balancePayload(int $vid = 500): array
{
    return [
        'vid' => $vid,
        'project' => 'B2_Caranga',
        'tag' => 'Caranga',
        'phone' => '79000000'.$vid,
        'phones' => ['79000000'.$vid],
        'time' => time(),
    ];
}

function makeUserForBalance(Tenant $tenant, string $email, array $events = []): User
{
    return User::factory()->create([
        'tenant_id' => $tenant->id,
        'email' => $email,
        'notification_preferences' => array_merge([
            'new_lead' => ['email' => false, 'inapp' => false],
            'reminder' => ['email' => true, 'inapp' => true],
            'low_balance' => ['email' => true, 'inapp' => true],
            'zero_balance' => ['email' => true, 'inapp' => true],
            'topup_success' => ['email' => true, 'inapp' => true],
            'invoice_paid' => ['email' => true, 'inapp' => true],
            'new_device_login' => ['email' => true, 'inapp' => false],
            'marketing' => ['email' => false, 'inapp' => false],
        ], $events),
    ]);
}

// ============== low_balance ==============

test('low_balance: при пересечении порога сверху-вниз → email + inapp', function () {
    // Default threshold: 10 (system_settings seeded). Установим balance=11.
    $tenant = Tenant::factory()->create(['balance_leads' => 11]);
    makeUserForBalance($tenant, 'on@example.ru');

    (new ProcessWebhookJob($tenant->id, balancePayload()))->handle();

    $tenant->refresh();
    expect($tenant->balance_leads)->toBe(10);  // 11 → 10 (пересекли порог)

    Mail::assertSent(LowBalanceNotification::class, 1);
    expect(InAppNotification::query()->where('event', 'low_balance')->count())->toBe(1);
});

test('low_balance: balance уже < threshold — НЕ шлёт повторно', function () {
    $tenant = Tenant::factory()->create(['balance_leads' => 5]);
    makeUserForBalance($tenant, 'on@example.ru');

    (new ProcessWebhookJob($tenant->id, balancePayload()))->handle();

    $tenant->refresh();
    expect($tenant->balance_leads)->toBe(4);  // 5 → 4 (всё ещё < threshold=10)

    // Не пересекали порог — НЕ шлём.
    Mail::assertNothingSent();
    expect(InAppNotification::query()->where('event', 'low_balance')->count())->toBe(0);
});

test('low_balance: balance > threshold после decrement — НЕ шлёт', function () {
    $tenant = Tenant::factory()->create(['balance_leads' => 50]);
    makeUserForBalance($tenant, 'on@example.ru');

    (new ProcessWebhookJob($tenant->id, balancePayload()))->handle();

    $tenant->refresh();
    expect($tenant->balance_leads)->toBe(49);

    Mail::assertNothingSent();
});

test('low_balance: prefs.low_balance.email=false — только inapp', function () {
    $tenant = Tenant::factory()->create(['balance_leads' => 11]);
    makeUserForBalance($tenant, 'on@example.ru', [
        'low_balance' => ['email' => false, 'inapp' => true],
    ]);

    (new ProcessWebhookJob($tenant->id, balancePayload()))->handle();

    Mail::assertNothingSent();
    expect(InAppNotification::query()->where('event', 'low_balance')->count())->toBe(1);
});

// ============== zero_balance ==============

test('zero_balance: первое отклонение → email + inapp', function () {
    $tenant = Tenant::factory()->create(['balance_leads' => 0]);
    makeUserForBalance($tenant, 'on@example.ru');

    (new ProcessWebhookJob($tenant->id, balancePayload()))->handle();

    Mail::assertSent(ZeroBalanceNotification::class, 1);
    expect(InAppNotification::query()->where('event', 'zero_balance')->count())->toBe(1);
    expect(RejectedDealsLog::query()->where('tenant_id', $tenant->id)->count())->toBe(1);
});

test('zero_balance: 2-е отклонение в течение часа — НЕ дублирует email', function () {
    $tenant = Tenant::factory()->create(['balance_leads' => 0]);
    makeUserForBalance($tenant, 'on@example.ru');

    (new ProcessWebhookJob($tenant->id, balancePayload(vid: 1)))->handle();
    Mail::assertSent(ZeroBalanceNotification::class, 1);

    (new ProcessWebhookJob($tenant->id, balancePayload(vid: 2)))->handle();
    Mail::assertSent(ZeroBalanceNotification::class, 1);  // всё ещё один
    expect(RejectedDealsLog::query()->where('tenant_id', $tenant->id)->count())->toBe(2);
});

test('zero_balance: отклонение через >1ч — снова шлёт', function () {
    $tenant = Tenant::factory()->create(['balance_leads' => 0]);
    makeUserForBalance($tenant, 'on@example.ru');

    // Создаём старый RejectedDealsLog (>1ч назад) — он не должен суппрессить.
    DB::table('rejected_deals_log')->insert([
        'tenant_id' => $tenant->id,
        'reason' => RejectedDealsLog::REASON_ZERO_BALANCE,
        'payload' => json_encode(['vid' => 999]),
        'created_at' => Carbon::now()->subHours(2),
    ]);

    (new ProcessWebhookJob($tenant->id, balancePayload()))->handle();

    Mail::assertSent(ZeroBalanceNotification::class, 1);
});

// ============== topup_success ==============

test('topup_success: notifyTopupSuccess создаёт email + inapp', function () {
    $tenant = Tenant::factory()->create();
    makeUserForBalance($tenant, 'on@example.ru');

    app(NotificationService::class)->notifyTopupSuccess($tenant, '5000.00', 100);

    Mail::assertSent(TopupSuccessNotification::class, 1);
    Mail::assertSent(function (TopupSuccessNotification $m): bool {
        return $m->amountRub === '5000.00' && $m->amountLeads === 100 && $m->hasTo('on@example.ru');
    });
    expect(InAppNotification::query()->where('event', 'topup_success')->count())->toBe(1);
});

test('topup_success: prefs=email:false — только inapp', function () {
    $tenant = Tenant::factory()->create();
    makeUserForBalance($tenant, 'on@example.ru', [
        'topup_success' => ['email' => false, 'inapp' => true],
    ]);

    app(NotificationService::class)->notifyTopupSuccess($tenant, '1000.00', null);

    Mail::assertNothingSent();
    expect(InAppNotification::query()->where('event', 'topup_success')->count())->toBe(1);
});

// ============== invoice_paid ==============

test('invoice_paid: notifyInvoicePaid создаёт email + inapp', function () {
    $tenant = Tenant::factory()->create();
    makeUserForBalance($tenant, 'on@example.ru');

    app(NotificationService::class)->notifyInvoicePaid($tenant, '990.00', 'INV-2026-0042', 'Команда');

    Mail::assertSent(InvoicePaidNotification::class, 1);
    Mail::assertSent(function (InvoicePaidNotification $m): bool {
        return $m->amountRub === '990.00'
            && $m->invoiceNumber === 'INV-2026-0042'
            && $m->tariffName === 'Команда';
    });
    expect(InAppNotification::query()->where('event', 'invoice_paid')->count())->toBe(1);
});

test('invoice_paid: prefs=email:false — только inapp', function () {
    $tenant = Tenant::factory()->create();
    makeUserForBalance($tenant, 'on@example.ru', [
        'invoice_paid' => ['email' => false, 'inapp' => true],
    ]);

    app(NotificationService::class)->notifyInvoicePaid($tenant, '990.00');

    Mail::assertNothingSent();
    expect(InAppNotification::query()->where('event', 'invoice_paid')->count())->toBe(1);
});

// ============== isolation ==============

test('balance events изолированы между тенантами', function () {
    $tenantA = Tenant::factory()->create(['balance_leads' => 11]);
    $tenantB = Tenant::factory()->create(['balance_leads' => 11]);
    $userA = makeUserForBalance($tenantA, 'a@example.ru');
    makeUserForBalance($tenantB, 'b@example.ru');

    (new ProcessWebhookJob($tenantA->id, balancePayload()))->handle();

    Mail::assertSent(LowBalanceNotification::class, 1);
    Mail::assertSent(fn (LowBalanceNotification $m) => $m->hasTo($userA->email));
    Mail::assertNotSent(fn (LowBalanceNotification $m) => $m->hasTo('b@example.ru'));
});
