<?php

declare(strict_types=1);

use App\Models\Project;
use App\Models\SupplierLead;
use App\Models\SupplierProject;
use App\Models\Tenant;
use App\Services\DaData\DaDataPhoneClient;
use Database\Seeders\PricingTierSeeder;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\DB;
use Tests\Concerns\SharesSupplierPdo;
use Tests\Support\Imitation\ConditionLevers;
use Tests\Support\Imitation\FakeDaDataPhoneClient;
use Tests\Support\Imitation\LeadInjector;
use Tests\Support\Imitation\SnapshotForge;

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

/**
 * Scenario G3 — «осиротевшая» заявка (orphan lead): все три фазы маршрутизации
 * возвращают пустой результат — никто не eligible.
 *
 * Спек: docs/superpowers/specs/2026-06-03-portal-client-imitation-phase1-design.md §6.2 G3
 * План: docs/superpowers/plans/2026-06-03-portal-client-imitation-phase1.md §Task 10
 *
 * VERIFICATION test against existing prod routing code (LeadRouter + RouteSupplierLeadJob).
 * NOT TDD — no prod code is modified. Differences vs plan → reported as FINDINGs.
 *
 * Сценарий:
 *   - Один SupplierProject (B1, site-сигнал).
 *   - Три клиента (проекта), каждый по-своему негоден:
 *       P1: лимит исчерпан (fillToLimit) — выбывает из всех трёх фаз SQL-фильтром
 *           delivered_today < snap.daily_limit.
 *       P2: заморожен (tenants.frozen_by_balance_at IS NOT NULL) — выбывает через
 *           tenant-фильтр LeadRouter; snapshot есть (фаза 3 всё равно не возьмёт).
 *       P3: баланс = 0 (tenants.balance_rub = 0) — выбывает через balance_rub > 0;
 *           snapshot есть (фаза 3 всё равно не возьмёт).
 *   - Регион лида → Москва (code 82, qc=0, via FakeDaDataPhoneClient).
 *   - Регион всех трёх проектов в snapshot → Москва (код 82) — это важно, чтобы
 *     фаза 1 могла бы найти их, если бы не другие барьеры; при этом phase 3
 *     (any-region) тоже не найдёт — те же tenant/limit барьеры работают во всех фазах.
 *
 * Ожидания (из плана §Task 10):
 *   - deals created = 0;
 *   - lead_charges = 0, balance_transactions = 0 (деньги не тронуты);
 *   - SupplierLead.processed_at IS NOT NULL (job завершился);
 *   - SupplierLead.deals_created_count = 0;
 *   - NO exception (job не упал);
 *   - Непроданный лид виден в supplier_leads.
 */
const G3_MOSCOW_CODE = 82;
const G3_SUPPLIER_DOMAIN = 'scenario-g3-orphan.ru';
const G3_SUPPLIER_PLATFORM = 'B1';
const G3_DAILY_LIMIT = 5;
const G3_LEAD_PHONE = '79161234599';

beforeEach(function (): void {
    $this->seed(PricingTierSeeder::class);
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");

    // Bind FakeDaDataPhoneClient: lead's phone resolves to Москва (qc=0, code=82).
    config([
        'services.dadata.enabled' => true,
        'services.dadata.api_key' => 'fake-key',
        'services.dadata.secret' => 'fake-secret',
        'services.dadata.daily_cap_rub' => 1_000_000,
    ]);

    $fakeDaData = new FakeDaDataPhoneClient;
    $fakeDaData->stub(G3_LEAD_PHONE, qc: 0, region: 'Москва', provider: 'МТС');
    app()->instance(DaDataPhoneClient::class, $fakeDaData);
});

it('orphan lead: no deals created, processed_at set, no exception, no money moved', function (): void {
    // ── ARRANGE ─────────────────────────────────────────────────────────────────

    // One shared SupplierProject (B1 site signal).
    $supplier = SupplierProject::factory()->create([
        'platform' => G3_SUPPLIER_PLATFORM,
        'signal_type' => 'site',
        'unique_key' => G3_SUPPLIER_DOMAIN,
    ]);

    $activeDate = SnapshotForge::activeDate();

    // ── Client P1: limit exhausted — fillToLimit makes delivered_today = daily_limit_target.
    // queryCandidates: projects.delivered_today < snap.daily_limit → FALSE → excluded from all phases.
    $tenantP1 = Tenant::factory()->create([
        'balance_rub' => '999.00',
        'frozen_by_balance_at' => null,
    ]);
    $projectP1 = Project::factory()->create([
        'tenant_id' => $tenantP1->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => G3_SUPPLIER_DOMAIN,
        'daily_limit_target' => G3_DAILY_LIMIT,
        'effective_daily_limit_today' => null,
        'delivered_today' => 0,
        'delivered_in_month' => 0,
        'delivery_days_mask' => 127,
        'preflight_blocked_at' => null,
        'regions' => [G3_MOSCOW_CODE],
    ]);
    linkProjectToSupplier($projectP1, $supplier);
    createRoutingSnapshotFromProject(
        project: $projectP1,
        date: $activeDate,
        signalType: 'site',
        signalIdentifier: G3_SUPPLIER_DOMAIN,
        dailyLimit: G3_DAILY_LIMIT,
        regions: '{'.G3_MOSCOW_CODE.'}',
    );
    // Exhaust the limit: delivered_today = G3_DAILY_LIMIT → SQL condition false in all phases.
    ConditionLevers::fillToLimit($projectP1);

    // ── Client P2: tenant frozen (frozen_by_balance_at IS NOT NULL).
    // queryCandidates: WHERE tenants.frozen_by_balance_at IS NULL → FALSE → excluded from all phases.
    $tenantP2 = Tenant::factory()->create([
        'balance_rub' => '999.00',
        'frozen_by_balance_at' => null,
    ]);
    $projectP2 = Project::factory()->create([
        'tenant_id' => $tenantP2->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => G3_SUPPLIER_DOMAIN,
        'daily_limit_target' => G3_DAILY_LIMIT,
        'effective_daily_limit_today' => null,
        'delivered_today' => 0,
        'delivered_in_month' => 0,
        'delivery_days_mask' => 127,
        'preflight_blocked_at' => null,
        'regions' => [G3_MOSCOW_CODE],
    ]);
    linkProjectToSupplier($projectP2, $supplier);
    createRoutingSnapshotFromProject(
        project: $projectP2,
        date: $activeDate,
        signalType: 'site',
        signalIdentifier: G3_SUPPLIER_DOMAIN,
        dailyLimit: G3_DAILY_LIMIT,
        regions: '{'.G3_MOSCOW_CODE.'}',
    );
    // Freeze the tenant: frozen_by_balance_at IS NOT NULL → excluded from all phases.
    ConditionLevers::freeze($tenantP2);

    // ── Client P3: zero balance (balance_rub = 0).
    // queryCandidates: WHERE tenants.balance_rub > 0 → FALSE → excluded from all phases.
    $tenantP3 = Tenant::factory()->create([
        'balance_rub' => '999.00',
        'frozen_by_balance_at' => null,
    ]);
    $projectP3 = Project::factory()->create([
        'tenant_id' => $tenantP3->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => G3_SUPPLIER_DOMAIN,
        'daily_limit_target' => G3_DAILY_LIMIT,
        'effective_daily_limit_today' => null,
        'delivered_today' => 0,
        'delivered_in_month' => 0,
        'delivery_days_mask' => 127,
        'preflight_blocked_at' => null,
        'regions' => [G3_MOSCOW_CODE],
    ]);
    linkProjectToSupplier($projectP3, $supplier);
    createRoutingSnapshotFromProject(
        project: $projectP3,
        date: $activeDate,
        signalType: 'site',
        signalIdentifier: G3_SUPPLIER_DOMAIN,
        dailyLimit: G3_DAILY_LIMIT,
        regions: '{'.G3_MOSCOW_CODE.'}',
    );
    // Drain balance: balance_rub = 0 → balance_rub > 0 condition fails in all phases.
    ConditionLevers::drainBalance($tenantP3);

    // Record counts BEFORE injection to detect any pre-existing rows.
    $dealsCountBefore = DB::connection('pgsql_supplier')->table('deals')->count();
    $chargesCountBefore = DB::connection('pgsql_supplier')->table('lead_charges')->count();
    $balanceTxCountBefore = DB::connection('pgsql_supplier')->table('balance_transactions')->count();

    // ── ACT — inject one lead and run the job synchronously ─────────────────────
    // We wrap in try/catch to detect exceptions — the plan says NO exception should bubble.
    $thrownException = null;
    $injectedLead = null;

    try {
        $injector = new LeadInjector;
        $injectedLead = $injector->site(
            domain: G3_SUPPLIER_DOMAIN,
            phone: G3_LEAD_PHONE,
            tag: 'Москва',
            platform: G3_SUPPLIER_PLATFORM,
            vid: 8_888_000_001,
        );
    } catch (Throwable $e) {
        $thrownException = $e;
    }

    // ── ASSERT ──────────────────────────────────────────────────────────────────

    // 1. No exception bubbled — the job must complete cleanly.
    expect($thrownException)->toBeNull(
        'FINDING: RouteSupplierLeadJob threw an exception for an orphan lead. '.
        'Expected: no exception. Got: '.($thrownException?->getMessage() ?? 'none')
    );

    // 2. The SupplierLead was created and is accessible.
    expect($injectedLead)->not->toBeNull(
        'FINDING: LeadInjector returned null — SupplierLead was not created.'
    );

    // Re-fetch fresh from DB to get updated processed_at / deals_created_count.
    /** @var SupplierLead $freshLead */
    $freshLead = SupplierLead::find($injectedLead->id);
    expect($freshLead)->not->toBeNull(
        'FINDING: SupplierLead id='.$injectedLead->id.' not found in DB after injection.'
    );

    // 3. processed_at IS set — job stamped the lead as "processed" even though nobody received it.
    // WHERE: supplier_leads table, column processed_at — this is WHERE the orphan lead "rests".
    expect($freshLead->processed_at)->not->toBeNull(
        'FINDING: SupplierLead.processed_at is NULL after routing with no eligible clients. '.
        'Expected: RouteSupplierLeadJob always sets processed_at=now() at step 6, '.
        'even when deals_created_count=0. The orphan lead should rest in supplier_leads '.
        'with processed_at set (idempotency guard).'
    );

    // 4. deals_created_count = 0 — no deals were created.
    expect((int) $freshLead->deals_created_count)->toBe(0,
        'FINDING: SupplierLead.deals_created_count expected 0 but got '.
        $freshLead->deals_created_count.'. '.
        'No eligible project was found — no deal should have been created.'
    );

    // 5. No deals created across all three tenants.
    $newDealsCount = DB::connection('pgsql_supplier')->table('deals')->count() - $dealsCountBefore;
    expect($newDealsCount)->toBe(0,
        'FINDING: '.$newDealsCount.' deal(s) were created despite all clients being ineligible. '.
        'P1(limit-exhausted), P2(frozen), P3(zero-balance) should all fail LeadRouter SQL filter.'
    );

    // 6. No lead_charges created — no money moved.
    $newChargesCount = DB::connection('pgsql_supplier')->table('lead_charges')->count() - $chargesCountBefore;
    expect($newChargesCount)->toBe(0,
        'FINDING: '.$newChargesCount.' lead_charge row(s) created despite no eligible clients. '.
        'LedgerService::chargeForDelivery should not have been called.'
    );

    // 7. No balance_transactions created — balances untouched.
    $newBalanceTxCount = DB::connection('pgsql_supplier')->table('balance_transactions')->count() - $balanceTxCountBefore;
    expect($newBalanceTxCount)->toBe(0,
        'FINDING: '.$newBalanceTxCount.' balance_transaction(s) created despite no eligible clients.'
    );

    // 8. The orphan lead is visible/recorded — WHERE it rests.
    // It lives in `supplier_leads` with processed_at IS NOT NULL and deals_created_count = 0.
    $orphanCount = DB::table('supplier_leads')
        ->where('id', $freshLead->id)
        ->whereNotNull('processed_at')
        ->where('deals_created_count', 0)
        ->count();
    expect($orphanCount)->toBe(1,
        'FINDING: Orphan lead not found in supplier_leads with processed_at IS NOT NULL and '.
        'deals_created_count=0. The unsold lead should rest in supplier_leads, identifiable by '.
        'processed_at IS NOT NULL + deals_created_count = 0 (no error column set).'
    );

    // ── REPORT ──────────────────────────────────────────────────────────────────
    fwrite(STDOUT, PHP_EOL.'=== SCENARIO G3 ORPHAN LEAD REPORT ==='.PHP_EOL);
    fwrite(STDOUT, 'SupplierLead id:          '.$freshLead->id.PHP_EOL);
    fwrite(STDOUT, 'processed_at:             '.($freshLead->processed_at?->toIso8601String() ?? 'NULL').PHP_EOL);
    fwrite(STDOUT, 'deals_created_count:      '.$freshLead->deals_created_count.PHP_EOL);
    fwrite(STDOUT, 'error:                    '.($freshLead->error ?? 'NULL (no error)').PHP_EOL);
    fwrite(STDOUT, 'deals created (new):      '.$newDealsCount.PHP_EOL);
    fwrite(STDOUT, 'lead_charges (new):       '.$newChargesCount.PHP_EOL);
    fwrite(STDOUT, 'balance_transactions(new):'.$newBalanceTxCount.PHP_EOL);
    fwrite(STDOUT, PHP_EOL.'WHERE the orphan lead rests:'.PHP_EOL);
    fwrite(STDOUT, '  Table:   supplier_leads'.PHP_EOL);
    fwrite(STDOUT, '  Filter:  processed_at IS NOT NULL AND deals_created_count = 0'.PHP_EOL);
    fwrite(STDOUT, '  Note:    error column is NULL (clean completion, not a failure).'.PHP_EOL);
    fwrite(STDOUT, '  Note:    NO entry in failed_webhook_jobs (job::failed() not called).'.PHP_EOL);
    fwrite(STDOUT, '=== END G3 REPORT ==='.PHP_EOL.PHP_EOL);

})->group('imitation');
