<?php

declare(strict_types=1);

use App\Jobs\RouteSupplierLeadJob;
use App\Mail\ZeroBalancePausedMail;
use App\Models\Project;
use App\Models\Supplier;
use App\Models\SupplierLead;
use App\Models\SupplierProject;
use App\Models\Tenant;
use App\Services\Billing\LedgerService;
use App\Services\LeadDistributor;
use App\Services\LeadRouter;
use App\Services\NotificationService;
use App\Services\RegionTagResolver;
use App\Services\SupplierProjects\SupplierProjectResolver;
use Database\Seeders\PricingTierSeeder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Tests\Concerns\SharesSupplierPdo;

uses(DatabaseTransactions::class);
uses(SharesSupplierPdo::class);

beforeEach(function () {
    Mail::fake();
    Cache::store('redis')->flush();
    $this->seed(PricingTierSeeder::class);
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");
});

function makeFlowWithBalance(array $balance): array
{
    $supplier = Supplier::where('code', 'b1')->first();
    $supplierProject = SupplierProject::factory()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'example.com',
    ]);
    $tenant = Tenant::factory()->create($balance);
    $project = Project::factory()->create([
        'tenant_id' => $tenant->id,
        'signal_type' => 'site', 'signal_identifier' => 'example.com',
        'supplier_b1_project_id' => $supplierProject->id,
        'is_active' => true, 'daily_limit_target' => 10,
        'effective_daily_limit_today' => 10, 'delivered_today' => 0,
        'delivery_days_mask' => 127, 'region_mask' => 255,
    ]);
    linkProjectToSupplier($project, $supplierProject);
    // LeadRouter::matchEligibleProjects() выбирает кандидатов ТОЛЬКО из project_routing_snapshots
    // (slepok-инвариант). Без снапшота на сегодня кандидатов 0 → джоб не доходит до auto-pause.
    createRoutingSnapshotFromProject($project, signalType: 'site', signalIdentifier: 'example.com');
    $lead = SupplierLead::factory()->create([
        'vid' => random_int(100_000_000, 999_999_999),
        'phone' => '79991234567',
        'raw_payload' => ['project' => 'B1_example.com', 'phone' => '79991234567', 'time' => time()],
        'supplier_project_id' => $supplierProject->id,
        'received_at' => now(),
    ]);

    return compact('tenant', 'project', 'lead');
}

function runJob(int $leadId): void
{
    (new RouteSupplierLeadJob($leadId))->handle(
        app(LeadRouter::class),
        app(SupplierProjectResolver::class),
        app(NotificationService::class),
        app(LedgerService::class),
        app(LeadDistributor::class),
        app(RegionTagResolver::class),
    );
}

it('pauses project (is_active=false) when both balances empty', function () {
    $ctx = makeFlowWithBalance(['balance_leads' => 0, 'balance_rub' => '100.00']);

    runJob($ctx['lead']->id);

    expect($ctx['project']->fresh()->is_active)->toBeFalse();
});

it('sends ZeroBalancePausedMail на email tenant'.chr(8217).'а', function () {
    $ctx = makeFlowWithBalance(['balance_leads' => 0, 'balance_rub' => '100.00']);

    runJob($ctx['lead']->id);

    Mail::assertSent(ZeroBalancePausedMail::class, function ($mail) use ($ctx) {
        return $mail->hasTo($ctx['tenant']->contact_email);
    });
});

it('respects rate-limit 1 hour per tenant: 2 consecutive calls → 1 email only', function () {
    $ctx1 = makeFlowWithBalance(['balance_leads' => 0, 'balance_rub' => '100.00']);

    runJob($ctx1['lead']->id);

    // Создаём второй lead для того же tenant'а
    $ctx2lead = SupplierLead::factory()->create([
        'vid' => random_int(100_000_000, 999_999_999),
        'phone' => '79991234567',
        'raw_payload' => ['project' => 'B1_example.com', 'phone' => '79991234567', 'time' => time()],
        'supplier_project_id' => $ctx1['lead']->supplier_project_id,
        'received_at' => now(),
    ]);

    runJob($ctx2lead->id);

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

it('sends 2nd email after 65 minutes (rate-limit expired)', function () {
    $ctx = makeFlowWithBalance(['balance_leads' => 0, 'balance_rub' => '100.00']);

    runJob($ctx['lead']->id);

    Mail::assertSent(ZeroBalancePausedMail::class, 1);

    Carbon::setTestNow(now()->addMinutes(65));
    Cache::store('redis')->flush();  // имитируем TTL expiry

    // Реактивируем проект (real-world: admin/cron вернул is_active=true, но клиент так и не пополнил баланс).
    // matchEligibleProjects() в LeadRouter фильтрует by is_active=true, иначе 2-й lead не дойдёт до handleInsufficientBalance.
    // NB: Eloquent $project->update(['is_active' => true]) — no-op (in-memory attr остался true с момента ::create()),
    //     поэтому используем прямой UPDATE через pgsql_supplier (как делает handleInsufficientBalance при pause).
    DB::connection('pgsql_supplier')
        ->update('UPDATE projects SET is_active = true WHERE id = ?', [$ctx['project']->id]);

    $lead2 = SupplierLead::factory()->create([
        'vid' => random_int(100_000_000, 999_999_999),
        'phone' => '79991234567',
        'raw_payload' => ['project' => 'B1_example.com', 'phone' => '79991234567', 'time' => time()],
        'supplier_project_id' => $ctx['lead']->supplier_project_id,
        'received_at' => now(),
    ]);

    runJob($lead2->id);

    Mail::assertSent(ZeroBalancePausedMail::class, 2);
});

it('sharing-flow isolation: tenant A on zero paused, tenant B with balance receives deal', function () {
    $supplier = Supplier::where('code', 'b1')->first();
    $supplierProject = SupplierProject::factory()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'example.com',
    ]);
    // tenantA: balance_rub > 0 (проходит WHERE EXISTS-фильтр LeadRouter), но < tier_price (500 ₽).
    // Поэтому projectA попадает в matched, LedgerService падает с InsufficientBalanceException → auto-pause.
    $tenantA = Tenant::factory()->create(['balance_leads' => 0, 'balance_rub' => '100.00']);
    $tenantB = Tenant::factory()->create(['balance_rub' => '100000.00']);
    $projectA = Project::factory()->create([
        'tenant_id' => $tenantA->id, 'signal_type' => 'site', 'signal_identifier' => 'example.com',
        'supplier_b1_project_id' => $supplierProject->id, 'is_active' => true,
        'daily_limit_target' => 10, 'effective_daily_limit_today' => 10,
        'delivered_today' => 0, 'delivery_days_mask' => 127, 'region_mask' => 255,
    ]);
    linkProjectToSupplier($projectA, $supplierProject);
    createRoutingSnapshotFromProject($projectA, signalType: 'site', signalIdentifier: 'example.com');
    $projectB = Project::factory()->create([
        'tenant_id' => $tenantB->id, 'signal_type' => 'site', 'signal_identifier' => 'example.com',
        'supplier_b1_project_id' => $supplierProject->id, 'is_active' => true,
        'daily_limit_target' => 10, 'effective_daily_limit_today' => 10,
        'delivered_today' => 0, 'delivery_days_mask' => 127, 'region_mask' => 255,
    ]);
    linkProjectToSupplier($projectB, $supplierProject);
    createRoutingSnapshotFromProject($projectB, signalType: 'site', signalIdentifier: 'example.com');
    $lead = SupplierLead::factory()->create([
        'vid' => random_int(100_000_000, 999_999_999),
        'phone' => '79991234567',
        'raw_payload' => ['project' => 'B1_example.com', 'phone' => '79991234567', 'time' => time()],
        'supplier_project_id' => $supplierProject->id,
        'received_at' => now(),
    ]);

    runJob($lead->id);

    expect($projectA->fresh()->is_active)->toBeFalse();
    expect($projectB->fresh()->is_active)->toBeTrue();
    expect((string) $tenantB->fresh()->balance_rub)->toBe('99500.00');
});
