<?php

declare(strict_types=1);

use App\Jobs\SyncSupplierProjectJob;
use App\Models\Project;
use App\Models\SupplierProject;
use App\Models\Tenant;
use App\Services\Supplier\SupplierPortalClient;
use Illuminate\Foundation\Testing\DatabaseTransactions;

// TestCase auto-bound via tests/Pest.php (->in('Feature')).
// DatabaseTransactions — per-test isolation.
uses(DatabaseTransactions::class);

/**
 * Хелпер: разрешает мок SupplierPortalClient из контейнера и вызывает Job.handle().
 * Нельзя использовать (new Job)->handle() без аргументов — handle() требует DI-инъекцию
 * SupplierPortalClient; прямой вызов без аргументов обходит контейнер и мок не применяется.
 */
function dispatchJobSync(SyncSupplierProjectJob $job): void
{
    $client = app(SupplierPortalClient::class);
    $job->handle($client);
}

it('site project: links B1+B2+B3 supplier_projects and sets all three IDs', function () {
    $tenant = Tenant::factory()->create();
    $project = Project::factory()->create([
        'tenant_id' => $tenant->id,
        'signal_type' => 'site',
        'signal_identifier' => 'okna.ru',
    ]);

    $this->mock(SupplierPortalClient::class, function ($mock) {
        $mock->shouldReceive('ensureSupplierProject')->times(3)
            ->andReturnUsing(fn (string $platform, string $signalType, string $key) => SupplierProject::factory()->create([
                'platform' => $platform,   // uppercase: B1, B2, B3
                'signal_type' => $signalType,
                'unique_key' => $key,
                'sync_status' => 'ok',
            ])->id
            );
    });

    dispatchJobSync(new SyncSupplierProjectJob($project->id));

    $project->refresh();
    expect($project->supplier_b1_project_id)->not->toBeNull();
    expect($project->supplier_b2_project_id)->not->toBeNull();
    expect($project->supplier_b3_project_id)->not->toBeNull();
});

it('call project: links B1+B2+B3 with phone signal_identifier', function () {
    $project = Project::factory()->create([
        'signal_type' => 'call',
        'signal_identifier' => '79161234567',
    ]);

    $this->mock(SupplierPortalClient::class, function ($mock) {
        $mock->shouldReceive('ensureSupplierProject')->times(3)
            ->andReturn(SupplierProject::factory()->create([
                'platform' => 'B1',
                'signal_type' => 'call',
                'sync_status' => 'ok',
            ])->id);
    });

    dispatchJobSync(new SyncSupplierProjectJob($project->id));

    expect($project->fresh()->supplier_b1_project_id)->not->toBeNull();
    expect($project->fresh()->supplier_b2_project_id)->not->toBeNull();
    expect($project->fresh()->supplier_b3_project_id)->not->toBeNull();
});

it('sms project with keyword: links B2+B3 only (no B1)', function () {
    $project = Project::factory()->create([
        'signal_type' => 'sms',
        'sms_senders' => ['TINKOFF'],
        'sms_keyword' => 'ипотека',
    ]);

    $this->mock(SupplierPortalClient::class, function ($mock) {
        $mock->shouldReceive('ensureSupplierProject')->times(2)
            ->andReturnUsing(fn (string $platform) => SupplierProject::factory()->create([
                'platform' => $platform,   // B2 or B3 — both pass CHECK constraint
                'signal_type' => 'sms',
                'sync_status' => 'ok',
            ])->id
            );
    });

    dispatchJobSync(new SyncSupplierProjectJob($project->id));

    $project->refresh();
    expect($project->supplier_b1_project_id)->toBeNull();
    expect($project->supplier_b2_project_id)->not->toBeNull();
    expect($project->supplier_b3_project_id)->not->toBeNull();
});

it('sms project without keyword: links B3 only', function () {
    $project = Project::factory()->create([
        'signal_type' => 'sms',
        'sms_senders' => ['TINKOFF'],
        'sms_keyword' => null,
    ]);

    $this->mock(SupplierPortalClient::class, function ($mock) {
        $mock->shouldReceive('ensureSupplierProject')->once()
            ->andReturn(SupplierProject::factory()->create([
                'platform' => 'B3',
                'signal_type' => 'sms',
                'sync_status' => 'ok',
            ])->id);
    });

    dispatchJobSync(new SyncSupplierProjectJob($project->id));

    $project->refresh();
    expect($project->supplier_b1_project_id)->toBeNull();
    expect($project->supplier_b2_project_id)->toBeNull();
    expect($project->supplier_b3_project_id)->not->toBeNull();
});

it('portal exception: re-throws for queue retry', function () {
    $project = Project::factory()->create([
        'signal_type' => 'site',
        'signal_identifier' => 'x.ru',
    ]);

    $this->mock(SupplierPortalClient::class, function ($mock) {
        $mock->shouldReceive('ensureSupplierProject')
            ->andThrow(new RuntimeException('timeout'));
    });

    expect(fn () => dispatchJobSync(new SyncSupplierProjectJob($project->id)))
        ->toThrow(RuntimeException::class);
});

it('partial success: B1=ok, B2=failed (pre-created row), B3=ok — all three IDs written', function () {
    $project = Project::factory()->create([
        'signal_type' => 'site',
        'signal_identifier' => 'x.ru',
    ]);

    // Pre-create a supplier_project row for B2 with sync_status='failed' —
    // the mock returns its ID to simulate a failed B2 sync.
    // NOTE: supplier_projects has NO last_error column (schema v8.19);
    // "failed" status alone is the observable signal.
    $spB2 = SupplierProject::factory()->create([
        'platform' => 'B2',
        'signal_type' => 'site',
        'unique_key' => 'x.ru',
        'sync_status' => 'failed',
    ]);

    $this->mock(SupplierPortalClient::class, function ($mock) use ($spB2) {
        $spB1 = SupplierProject::factory()->create(['platform' => 'B1', 'signal_type' => 'site', 'sync_status' => 'ok'])->id;
        $spB3 = SupplierProject::factory()->create(['platform' => 'B3', 'signal_type' => 'site', 'sync_status' => 'ok'])->id;
        $mock->shouldReceive('ensureSupplierProject')->andReturn($spB1, $spB2->id, $spB3);
    });

    dispatchJobSync(new SyncSupplierProjectJob($project->id));

    $project->refresh();
    expect($project->supplier_b2_project_id)->not->toBeNull();
    expect(SupplierProject::find($project->supplier_b2_project_id)->sync_status)->toBe('failed');
    expect($project->supplier_b1_project_id)->not->toBeNull();
    expect($project->supplier_b3_project_id)->not->toBeNull();
});
