<?php

declare(strict_types=1);

use App\Jobs\RouteSupplierLeadJob;
use App\Models\Deal;
use App\Models\LeadCharge;
use App\Models\Project;
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\Facades\DB;
use Random\Engine\Mt19937;
use Random\Randomizer;
use Tests\Concerns\SharesSupplierPdo;

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

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

it('supplier_lead_deliveries table exists with PK (supplier_lead_id, tenant_id) and RLS', function (): void {
    $cols = collect(DB::select(
        "SELECT column_name FROM information_schema.columns WHERE table_name = 'supplier_lead_deliveries'"
    ))->pluck('column_name')->all();
    expect($cols)->toContain('supplier_lead_id')
        ->toContain('tenant_id')
        ->toContain('deal_id')
        ->toContain('created_at');

    $pk = collect(DB::select(
        "SELECT a.attname FROM pg_index i
         JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)
         WHERE i.indrelid = 'supplier_lead_deliveries'::regclass AND i.indisprimary"
    ))->pluck('attname')->sort()->values()->all();
    expect($pk)->toBe(['supplier_lead_id', 'tenant_id']);

    $rls = DB::selectOne(
        "SELECT relrowsecurity FROM pg_class WHERE relname = 'supplier_lead_deliveries'"
    );
    expect($rls->relrowsecurity)->toBeTrue();
});

it('one delivery to a tenant with 2 eligible projects → exactly 1 deal + 1 charge (max-remaining-limit tie-break)', function (): void {
    $this->seed(PricingTierSeeder::class);

    $sp = SupplierProject::factory()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'twoproj.ru',
    ]);
    $tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);

    // Two eligible projects for the SAME tenant, different remaining limit.
    $pLow = Project::factory()->create([
        'tenant_id' => $tenant->id, 'is_active' => true,
        'supplier_b1_project_id' => $sp->id,
        'signal_type' => 'site', 'signal_identifier' => 'twoproj.ru',
        'daily_limit_target' => 10, 'effective_daily_limit_today' => 10,
        'delivered_today' => 9, 'delivery_days_mask' => 127, 'region_mask' => 255,
    ]);
    $pHigh = Project::factory()->create([
        'tenant_id' => $tenant->id, 'is_active' => true,
        'supplier_b1_project_id' => $sp->id,
        'signal_type' => 'site', 'signal_identifier' => 'twoproj.ru',
        'daily_limit_target' => 10, 'effective_daily_limit_today' => 10,
        'delivered_today' => 0, 'delivery_days_mask' => 127, 'region_mask' => 255,
    ]);
    linkProjectToSupplier($pLow, $sp);
    linkProjectToSupplier($pHigh, $sp);
    // LeadRouter выбирает кандидатов только из project_routing_snapshots (slepok-инвариант).
    createRoutingSnapshotFromProject($pLow, signalType: 'site', signalIdentifier: 'twoproj.ru');
    createRoutingSnapshotFromProject($pHigh, signalType: 'site', signalIdentifier: 'twoproj.ru');

    $vid = 600001;
    $lead = SupplierLead::factory()->create([
        'supplier_project_id' => null, 'platform' => 'B1', 'vid' => $vid,
        'phone' => '79991234567',
        'raw_payload' => ['vid' => $vid, 'project' => 'B1_twoproj.ru', 'phone' => '79991234567', 'time' => now()->getTimestamp()],
    ]);

    runRouteJobB($lead->id);

    DB::statement("SET LOCAL app.current_tenant_id = '{$tenant->id}'");
    expect(Deal::query()->where('source_crm_id', $vid)->count())->toBe(1);
    expect(LeadCharge::query()->where('tenant_id', $tenant->id)->count())->toBe(1);
    // The project with most remaining limit was chosen.
    expect($pHigh->fresh()->delivered_today)->toBe(1);
    expect($pLow->fresh()->delivered_today)->toBe(9);
});

it('lock: re-running same delivery to same tenant does not double-charge (Spec B)', function (): void {
    $this->seed(PricingTierSeeder::class);

    $sp = SupplierProject::factory()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'lock.ru',
    ]);
    $tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
    $p = Project::factory()->create([
        'tenant_id' => $tenant->id, 'is_active' => true,
        'supplier_b1_project_id' => $sp->id,
        'signal_type' => 'site', 'signal_identifier' => 'lock.ru',
        'daily_limit_target' => 10, 'effective_daily_limit_today' => 10,
        'delivered_today' => 0, 'delivery_days_mask' => 127, 'region_mask' => 255,
    ]);
    linkProjectToSupplier($p, $sp);
    createRoutingSnapshotFromProject($p, signalType: 'site', signalIdentifier: 'lock.ru');

    $vid = 610001;
    $lead = SupplierLead::factory()->create([
        'supplier_project_id' => null, 'platform' => 'B1', 'vid' => $vid,
        'phone' => '79991234567',
        'raw_payload' => ['vid' => $vid, 'project' => 'B1_lock.ru', 'phone' => '79991234567', 'time' => now()->getTimestamp()],
    ]);

    runRouteJobB($lead->id);

    // Reset processed_at to force a SECOND pass (bypass the existing $lead->processed_at idempotency
    // guard so we are testing the DB-level lock specifically).
    $lead->update(['processed_at' => null]);
    runRouteJobB($lead->id);

    DB::statement("SET LOCAL app.current_tenant_id = '{$tenant->id}'");
    expect(Deal::query()->where('source_crm_id', $vid)->count())->toBe(1);
    expect(LeadCharge::query()->where('tenant_id', $tenant->id)->count())->toBe(1);
    expect(DB::table('supplier_lead_deliveries')
        ->where('supplier_lead_id', $lead->id)->where('tenant_id', $tenant->id)->count())->toBe(1);
    // Balance debited exactly once.
    expect((string) $tenant->fresh()->balance_rub)->toBe('99500.00');
});

it('same phone, two different deliveries to one tenant → both charged (no phone dedup, Spec B)', function (): void {
    $this->seed(PricingTierSeeder::class);

    $sp = SupplierProject::factory()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'twohit.ru',
    ]);
    $tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
    $p = Project::factory()->create([
        'tenant_id' => $tenant->id, 'is_active' => true,
        'supplier_b1_project_id' => $sp->id,
        'signal_type' => 'site', 'signal_identifier' => 'twohit.ru',
        'daily_limit_target' => 10, 'effective_daily_limit_today' => 10,
        'delivered_today' => 0, 'delivery_days_mask' => 127, 'region_mask' => 255,
    ]);
    linkProjectToSupplier($p, $sp);
    createRoutingSnapshotFromProject($p, signalType: 'site', signalIdentifier: 'twohit.ru');

    foreach ([700001, 700002] as $vid) {
        $lead = SupplierLead::factory()->create([
            'supplier_project_id' => null, 'platform' => 'B1', 'vid' => $vid,
            'phone' => '79991234567',
            'raw_payload' => ['vid' => $vid, 'project' => 'B1_twohit.ru', 'phone' => '79991234567', 'time' => now()->getTimestamp()],
        ]);
        runRouteJobB($lead->id);
    }

    DB::statement("SET LOCAL app.current_tenant_id = '{$tenant->id}'");
    expect(Deal::query()->where('tenant_id', $tenant->id)->whereIn('source_crm_id', [700001, 700002])->count())->toBe(2);
    expect(LeadCharge::query()->where('tenant_id', $tenant->id)->count())->toBe(2);
    expect((string) $tenant->fresh()->balance_rub)->toBe('99000.00');
});

it('cap = 3 distinct tenants: 5 eligible tenants → exactly 3 charged (Spec B)', function (): void {
    $this->seed(PricingTierSeeder::class);
    app()->bind(LeadDistributor::class, fn () => new LeadDistributor(new Randomizer(new Mt19937(7))));

    $sp = SupplierProject::factory()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => 'cap3.ru',
    ]);
    foreach (range(1, 5) as $i) {
        $t = Tenant::factory()->create(['balance_rub' => '100000.00']);
        $p = Project::factory()->create([
            'tenant_id' => $t->id, 'is_active' => true,
            'supplier_b1_project_id' => $sp->id,
            'signal_type' => 'site', 'signal_identifier' => 'cap3.ru',
            'daily_limit_target' => 10, 'effective_daily_limit_today' => 10,
            'delivered_today' => 0, 'delivery_days_mask' => 127, 'region_mask' => 255,
        ]);
        linkProjectToSupplier($p, $sp);
        createRoutingSnapshotFromProject($p, signalType: 'site', signalIdentifier: 'cap3.ru');
    }

    $vid = 710001;
    $lead = SupplierLead::factory()->create([
        'supplier_project_id' => null, 'platform' => 'B1', 'vid' => $vid,
        'phone' => '79991234567',
        'raw_payload' => ['vid' => $vid, 'project' => 'B1_cap3.ru', 'phone' => '79991234567', 'time' => now()->getTimestamp()],
    ]);

    runRouteJobB($lead->id);

    $lead->refresh();
    expect($lead->deals_created_count)->toBe(3);
    expect(DB::table('supplier_lead_deliveries')->where('supplier_lead_id', $lead->id)->count())->toBe(3);
    expect(DB::table('supplier_lead_deliveries')->where('supplier_lead_id', $lead->id)->distinct()->count('tenant_id'))->toBe(3);
});
