<?php

declare(strict_types=1);

use App\Jobs\RouteSupplierLeadJob;
use App\Models\Deal;
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 Illuminate\Support\Facades\Http;
use Tests\Concerns\SharesSupplierPdo;

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

beforeEach(function (): void {
    $this->seed(PricingTierSeeder::class);
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");
    config([
        'services.dadata.enabled' => true,
        'services.dadata.api_key' => 'k',
        'services.dadata.secret' => 's',
        'services.dadata.daily_cap_rub' => 100000,
    ]);
});

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

/**
 * Создаёт маршрутизируемый лид: supplier B1 site + tenant с балансом + project + snapshot.
 *
 * @return array{0: SupplierLead, 1: Project, 2: Tenant, 3: SupplierProject}
 */
function seedRoutableLead(string $regions, string $tag, string $phone, string $key = 'vashinvestor.ru'): array
{
    $supplier = SupplierProject::factory()->create([
        'platform' => 'B1', 'signal_type' => 'site', 'unique_key' => $key,
    ]);
    $tenant = Tenant::factory()->create(['balance_rub' => '100000.00']);
    $project = Project::factory()->create([
        'tenant_id' => $tenant->id,
        'signal_type' => 'site', 'signal_identifier' => $key,
        'is_active' => true, 'delivered_today' => 0, 'delivered_in_month' => 0,
        'daily_limit_target' => 100,
    ]);
    linkProjectToSupplier($project, $supplier);
    createRoutingSnapshotFromProject($project, dailyLimit: 100, regions: $regions);

    $vid = 432176649;
    $lead = SupplierLead::factory()->create([
        'supplier_project_id' => null,
        'platform' => 'B1',
        'vid' => $vid,
        'phone' => $phone,
        'received_at' => now(),
        'raw_payload' => [
            'vid' => $vid, 'project' => "B1_{$key}", 'tag' => $tag,
            'phone' => $phone, 'phones' => [$phone], 'time' => now()->getTimestamp(),
        ],
    ]);

    return [$lead, $project, $tenant, $supplier];
}

function dealFor(int $tenantId, int $projectId): ?Deal
{
    DB::statement("SET LOCAL app.current_tenant_id = '{$tenantId}'");
    $deal = Deal::query()->where('project_id', $projectId)->first();
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");

    return $deal;
}

it('lead with phone uses dadata region, not the tag', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([[
        'qc' => 0, 'region' => 'Москва', 'provider' => 'МТС', 'type' => 'Мобильный', 'phone' => '+7 916 123-45-67',
    ]], 200)]);
    // tag='Санкт-Петербург' (дал бы 83), но телефон резолвится в Москву (82).
    [$lead, $project, $tenant] = seedRoutableLead(regions: '{82}', tag: 'Санкт-Петербург', phone: '79161234567');

    runRegionJob($lead->id);

    $lead->refresh();
    expect($lead->resolved_subject_code)->toBe(82)
        ->and($lead->region_source)->toBe('dadata')
        ->and($lead->phone_operator)->toBe('МТС');

    $deal = dealFor($tenant->id, $project->id);
    expect($deal)->not->toBeNull()
        ->and((int) $deal->subject_code)->toBe(82)        // регион из DaData, не из тега (83)
        ->and((bool) $deal->region_substituted)->toBeFalse()
        ->and($deal->phone_operator)->toBe('МТС');
});

it('logs exactly one region resolution row per lead', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([[
        'qc' => 0, 'region' => 'Москва', 'provider' => 'МТС',
    ]], 200)]);
    [$lead] = seedRoutableLead(regions: '{82}', tag: 'tag', phone: '79161234567');

    runRegionJob($lead->id);

    $rows = DB::table('lead_region_resolution_log')->where('supplier_lead_id', $lead->id)->get();
    expect($rows)->toHaveCount(1);
    expect($rows->first()->region_source)->toBe('dadata');
    // Телефон в логе маскирован (не сырой номер) — §7.1.
    expect($rows->first()->phone_masked)->not->toBe('79161234567');
});

it('lead with invalid phone falls back to tag', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0, 'region' => 'Москва']], 200)]);
    // Невалидный телефон → DaData не дёргается → tag (Москва=82).
    [$lead, $project, $tenant] = seedRoutableLead(regions: '{82}', tag: 'Москва', phone: '123');

    runRegionJob($lead->id);

    $lead->refresh();
    expect($lead->region_source)->toBe('tag')->and($lead->resolved_subject_code)->toBe(82);
    Http::assertNothingSent();
});

it('lead with resolver disabled via flag uses tag', function (): void {
    config(['services.dadata.enabled' => false]);
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0, 'region' => 'Москва']], 200)]);
    [$lead, $project, $tenant] = seedRoutableLead(regions: '{82}', tag: 'Москва', phone: '79161234567');

    runRegionJob($lead->id);

    $lead->refresh();
    expect($lead->region_source)->toBe('tag')->and($lead->resolved_subject_code)->toBe(82);
    Http::assertNothingSent();
});

it('persistent idempotency: pre-resolved lead does not re-call dadata', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0, 'region' => 'Москва', 'provider' => 'МТС']], 200)]);
    [$lead, $project, $tenant] = seedRoutableLead(regions: '{83}', tag: 'tag', phone: '79161234567');
    // Эмулируем предыдущий try: резолв уже персистнут.
    $lead->update(['resolved_subject_code' => 83, 'region_source' => 'rossvyaz', 'phone_operator' => 'МегаФон']);

    runRegionJob($lead->id);

    Http::assertNothingSent(); // §3.11 — нет двойной оплаты DaData
    $lead->refresh();
    expect($lead->resolved_subject_code)->toBe(83)->and($lead->region_source)->toBe('rossvyaz');
});

it('step-3 fallback substitutes subject_code to client region and flags region_substituted', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([[
        'qc' => 0, 'region' => 'Москва', 'provider' => 'МТС',
    ]], 200)]);
    // Лид по Москве (82), но клиент подписан только на Питер (83): точных нет, «вся РФ» нет → шаг 3.
    [$lead, $project, $tenant] = seedRoutableLead(regions: '{83}', tag: 'tag', phone: '79161234567');

    runRegionJob($lead->id);

    $deal = dealFor($tenant->id, $project->id);
    expect($deal)->not->toBeNull()
        ->and((int) $deal->subject_code)->toBe(83)         // подменён на регион клиента (Питер)
        ->and((bool) $deal->region_substituted)->toBeTrue();

    // Настоящий регион (Москва=82) сохранён в журнале как actual_subject_code.
    $log = DB::table('lead_region_resolution_log')->where('supplier_lead_id', $lead->id)->first();
    expect((int) $log->actual_subject_code)->toBe(82)
        ->and((int) $log->substituted_subject_code)->toBe(83);
});

it('csv-merge updates subject_code and operator when webhook resolution outranks tag (dadata)', function (): void {
    Http::fake(['cleaner.dadata.ru/*' => Http::response([['qc' => 0, 'region' => 'Москва', 'provider' => 'МТС']], 200)]);
    [$lead, $project, $tenant] = seedRoutableLead(regions: '{82}', tag: 'tag', phone: '79161234567');

    // CSV-recovered сделка: source_crm_id=null, регион из тега «неправильный» (53 = ЛО).
    DB::statement("SET LOCAL app.current_tenant_id = '{$tenant->id}'");
    $csvDeal = Deal::create([
        'tenant_id' => $tenant->id, 'source_crm_id' => null, 'project_id' => $project->id,
        'phone' => '79161234567', 'phones' => ['79161234567'], 'status' => 'new',
        'received_at' => now(), 'subject_code' => 53,
    ]);
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");

    runRegionJob($lead->id);

    $merged = dealFor($tenant->id, $project->id);
    expect((int) $merged->id)->toBe($csvDeal->id)            // merge в существующую, не новая
        ->and((int) $merged->subject_code)->toBe(82)         // обновлено DaData (82) поверх tag (53)
        ->and($merged->phone_operator)->toBe('МТС')
        ->and((int) $merged->source_crm_id)->toBe($lead->vid);

    DB::statement("SET LOCAL app.current_tenant_id = '{$tenant->id}'");
    expect(Deal::query()->where('project_id', $project->id)->count())->toBe(1); // второй сделки нет
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");
});

it('csv-merge does not overwrite subject_code when webhook resolution is tag-level', function (): void {
    config(['services.dadata.enabled' => false]); // резолвер выключен → source='tag' (rank не выше CSV-tag)
    [$lead, $project, $tenant] = seedRoutableLead(regions: '{82}', tag: 'Москва', phone: '79161234567');

    DB::statement("SET LOCAL app.current_tenant_id = '{$tenant->id}'");
    Deal::create([
        'tenant_id' => $tenant->id, 'source_crm_id' => null, 'project_id' => $project->id,
        'phone' => '79161234567', 'phones' => ['79161234567'], 'status' => 'new',
        'received_at' => now(), 'subject_code' => 53,
    ]);
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");

    runRegionJob($lead->id);

    $merged = dealFor($tenant->id, $project->id);
    expect((int) $merged->subject_code)->toBe(53); // tag не выше tag → регион не тронут
});
