<?php

declare(strict_types=1);

use App\Models\Deal;
use App\Models\Project;
use App\Models\SupplierProject;
use App\Models\Tenant;
use App\Services\DaData\DaDataPhoneClient;
use App\Services\LeadRouter;
use Carbon\Carbon;
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;
use Tests\Support\Imitation\FakeDaDataPhoneClient;
use Tests\Support\Imitation\LeadInjector;
use Tests\Support\Imitation\SnapshotForge;

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

/**
 * Scenario D — delivery_days_mask filter verification.
 *
 * VERIFICATION test against existing production routing code.
 * Proves (or disproves) that the delivery_days_mask filter correctly excludes
 * projects from the snapshot when today's weekday bit is NOT set.
 * NOT TDD — no prod code is modified.  Differences vs plan → FINDINGS.
 *
 * Weekday-bit convention (confirmed from SnapshotProjectRoutingJob + SnapshotRebuildCommand):
 *   weekdayBit = 1 << (date->isoWeekday() - 1)
 *   isoWeekday(): Monday=1, Tuesday=2, ..., Sunday=7
 *   → Monday  = 1<<0 = 1
 *   → Tuesday = 1<<1 = 2
 *   → ...
 *   → Sunday  = 1<<6 = 64
 *   → Full week mask = 127  (bits 0-6 all set)
 *
 * SnapshotForge::activeDate() mirrors LeadRouter::activeSnapshotDate():
 *   - Before 21:00 MSK → today's date
 *   - From  21:00 MSK → tomorrow's date
 * snapshot:rebuild filters by that date's isoWeekday bit.
 *
 * Setup:
 *   - One shared SupplierProject (B1 site signal).
 *   - Two tenants / projects on that supplier.
 *   - ACTIVE client:   delivery_days_mask = 127 (all days — includes any day).
 *   - INACTIVE client: delivery_days_mask = full-week MINUS today's bit (excludes active date).
 *   - Both tenants have ample balance and no freeze.
 *   - SnapshotForge::rebuild() called AFTER masks are set.
 *   - One lead injected.
 *
 * Expected (per plan §6.2 D):
 *   - ACTIVE client receives the deal.
 *   - INACTIVE client receives ZERO deals (not in snapshot → invisible to LeadRouter).
 *
 * Subject code: Москва = 82 (порядковый, НЕ ГИБДД).
 *
 * Task 8 — Phase 1 Portal Client Imitation, Scenario D.
 * Spec: docs/superpowers/specs/2026-06-03-portal-client-imitation-phase1-design.md §6.2 D
 * Plan: docs/superpowers/plans/2026-06-03-portal-client-imitation-phase1.md §Task 8
 */

// ── SUBJECT CODE ──────────────────────────────────────────────────────────────
/** App\Support\RussianRegions::CODE_TO_NAME[82] = 'Москва' */
const D_MOSCOW_CODE = 82;

// ── SUPPLIER SIGNAL ───────────────────────────────────────────────────────────
const D_SUPPLIER_DOMAIN = 'scenario-d-delivery-days.ru';
const D_SUPPLIER_PLATFORM = 'B1';

// ── DETERMINISTIC SEED ────────────────────────────────────────────────────────
const D_SEED = 19;

// ── PHONE NUMBERS ─────────────────────────────────────────────────────────────
/** Phone resolving to Москва via FakeDaDataPhoneClient. */
const D_PHONE_1 = '79270000099';

beforeEach(function (): void {
    // Pricing tiers — required by LedgerService::chargeForDelivery.
    $this->seed(PricingTierSeeder::class);

    // Global RLS bypass for seeding phase (tenant context = 0).
    DB::statement("SELECT set_config('app.current_tenant_id', '0', true)");

    // DaData config — FakeDaDataPhoneClient bypasses HTTP entirely.
    config([
        'services.dadata.enabled' => true,
        'services.dadata.api_key' => 'fake-key',
        'services.dadata.secret' => 'fake-secret',
        'services.dadata.daily_cap_rub' => 1_000_000,
    ]);

    // Bind deterministic LeadRouter (small cap — only 2 projects, so no real lottery needed,
    // but we seed for reproducibility).
    app()->instance(LeadRouter::class, new LeadRouter(new Randomizer(new Mt19937(D_SEED))));
});

it('only delivers to the active-today client; the excluded-day client gets zero deals', function (): void {
    // ── DETERMINE ACTIVE DATE AND ITS WEEKDAY BIT ────────────────────────────
    //
    // SnapshotForge::activeDate() mirrors LeadRouter::activeSnapshotDate().
    // SnapshotRebuildCommand: weekdayBit = 1 << (date->isoWeekday() - 1).
    // isoWeekday(): Monday=1 .. Sunday=7.
    //
    // We MUST compute the bit from the active-snapshot date (not wall-clock today),
    // because after 21:00 MSK the active date flips to tomorrow.
    $activeDate = SnapshotForge::activeDate();                        // 'YYYY-MM-DD'
    $activeDateObj = Carbon::parse($activeDate, 'Europe/Moscow');
    $todayBit = 1 << ($activeDateObj->isoWeekday() - 1);          // e.g. Wednesday=4

    // ACTIVE mask: full week (includes every day, including today).
    $activeMask = 127;  // 0b1111111

    // INACTIVE mask: full week MINUS today's bit → excludes the active snapshot date.
    // snapshot:rebuild WHERE (delivery_days_mask & weekdayBit) <> 0 will skip this project.
    $inactiveMask = 127 & ~$todayBit;  // clears the bit for the active date's weekday

    // Sanity: active mask passes the filter, inactive mask does not.
    expect(($activeMask & $todayBit) !== 0)->toBeTrue();
    expect(($inactiveMask & $todayBit))->toBe(0);

    // ── ARRANGE: SHARED SUPPLIER PROJECT ─────────────────────────────────────
    $supplier = SupplierProject::factory()->create([
        'platform' => D_SUPPLIER_PLATFORM,
        'signal_type' => 'site',
        'unique_key' => D_SUPPLIER_DOMAIN,
    ]);

    // ── ARRANGE: ACTIVE TENANT / PROJECT ─────────────────────────────────────
    $tenantActive = Tenant::factory()->create([
        'balance_rub' => '9999.00',
        'frozen_by_balance_at' => null,
    ]);

    $projectActive = Project::factory()->create([
        'tenant_id' => $tenantActive->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => D_SUPPLIER_DOMAIN,
        'daily_limit_target' => 10,
        'effective_daily_limit_today' => null,
        'delivered_today' => 0,
        'delivered_in_month' => 0,
        'delivery_days_mask' => $activeMask,    // all days — included today
        'preflight_blocked_at' => null,
        'regions' => [D_MOSCOW_CODE],
    ]);

    linkProjectToSupplier($projectActive, $supplier);

    // ── ARRANGE: INACTIVE TENANT / PROJECT ───────────────────────────────────
    $tenantInactive = Tenant::factory()->create([
        'balance_rub' => '9999.00',
        'frozen_by_balance_at' => null,
    ]);

    $projectInactive = Project::factory()->create([
        'tenant_id' => $tenantInactive->id,
        'is_active' => true,
        'signal_type' => 'site',
        'signal_identifier' => D_SUPPLIER_DOMAIN,
        'daily_limit_target' => 10,
        'effective_daily_limit_today' => null,
        'delivered_today' => 0,
        'delivered_in_month' => 0,
        'delivery_days_mask' => $inactiveMask, // today's bit cleared — excluded from snapshot
        'preflight_blocked_at' => null,
        'regions' => [D_MOSCOW_CODE],
    ]);

    linkProjectToSupplier($projectInactive, $supplier);

    // ── REBUILD SNAPSHOT AFTER MASKS ARE SET ─────────────────────────────────
    // SnapshotRebuildCommand: WHERE (delivery_days_mask & weekdayBit) <> 0
    // → projectActive  IS inserted (bit set)
    // → projectInactive NOT inserted (bit cleared)
    SnapshotForge::rebuild();

    // Verify snapshot state directly: active project is in snapshot, inactive is not.
    $snapshotActiveExists = DB::connection('pgsql_supplier')
        ->table('project_routing_snapshots')
        ->where('snapshot_date', $activeDate)
        ->where('project_id', $projectActive->id)
        ->exists();

    $snapshotInactiveExists = DB::connection('pgsql_supplier')
        ->table('project_routing_snapshots')
        ->where('snapshot_date', $activeDate)
        ->where('project_id', $projectInactive->id)
        ->exists();

    expect($snapshotActiveExists)->toBeTrue(
        "FINDING: projectActive (mask={$activeMask}, bit={$todayBit}) ".
        "was expected in the snapshot for {$activeDate} but is absent. ".
        'SnapshotRebuildCommand may have a bug in delivery_days_mask filtering.'
    );

    expect($snapshotInactiveExists)->toBeFalse(
        "FINDING: projectInactive (mask={$inactiveMask}, bit={$todayBit}) ".
        "was expected to be ABSENT from the snapshot for {$activeDate} but was INSERTED. ".
        'This means the delivery_days_mask filter is not working — the inactive project '.
        'will receive leads it should not receive.'
    );

    // ── ARRANGE: FAKE DADATA CLIENT ──────────────────────────────────────────
    $fake = (new FakeDaDataPhoneClient)->stub(D_PHONE_1, qc: 0, region: 'Москва', provider: 'МТС');
    app()->instance(DaDataPhoneClient::class, $fake);

    // ── ACT: INJECT ONE LEAD ─────────────────────────────────────────────────
    (new LeadInjector)->site(
        domain: D_SUPPLIER_DOMAIN,
        phone: D_PHONE_1,
        tag: 'Москва',
        platform: D_SUPPLIER_PLATFORM,
        vid: 88_000_000_001,
    );

    // ── ASSERT: DEALS DISTRIBUTION ───────────────────────────────────────────

    // Active client MUST receive exactly 1 deal.
    $activeDeals = DB::connection('pgsql_supplier')
        ->table('deals')
        ->where('tenant_id', $tenantActive->id)
        ->count();

    // Inactive client MUST receive 0 deals (not in snapshot).
    $inactiveDeals = DB::connection('pgsql_supplier')
        ->table('deals')
        ->where('tenant_id', $tenantInactive->id)
        ->count();

    // Diagnostic output.
    fwrite(STDOUT, PHP_EOL.'=== SCENARIO D: DELIVERY DAYS FILTER REPORT ==='.PHP_EOL);
    fwrite(STDOUT, sprintf('Active date:      %s (isoWeekday=%d)%s',
        $activeDate, $activeDateObj->isoWeekday(), PHP_EOL));
    fwrite(STDOUT, sprintf('Today bit:        %d (0b%s)%s',
        $todayBit, str_pad(decbin($todayBit), 7, '0', STR_PAD_LEFT), PHP_EOL));
    fwrite(STDOUT, sprintf('Active mask:      %d (0b%s) → snapshot: %s%s',
        $activeMask, str_pad(decbin($activeMask), 7, '0', STR_PAD_LEFT),
        $snapshotActiveExists ? 'INCLUDED' : 'MISSING', PHP_EOL));
    fwrite(STDOUT, sprintf('Inactive mask:    %d (0b%s) → snapshot: %s%s',
        $inactiveMask, str_pad(decbin($inactiveMask), 7, '0', STR_PAD_LEFT),
        $snapshotInactiveExists ? 'PRESENT (BUG!)' : 'ABSENT (correct)', PHP_EOL));
    fwrite(STDOUT, sprintf('Active client deals:   %d (expected: 1)%s', $activeDeals, PHP_EOL));
    fwrite(STDOUT, sprintf('Inactive client deals: %d (expected: 0)%s', $inactiveDeals, PHP_EOL));
    fwrite(STDOUT, '=== END SCENARIO D ==='.PHP_EOL.PHP_EOL);

    expect($activeDeals)->toBe(1,
        "FINDING: Active client (mask={$activeMask}, project_id={$projectActive->id}) ".
        "expected 1 deal but got {$activeDeals}. ".
        'Check LeadRouter eligibility query or LedgerService::chargeForDelivery.'
    );

    expect($inactiveDeals)->toBe(0,
        "FINDING: Inactive client (mask={$inactiveMask}, today_bit={$todayBit}, ".
        "project_id={$projectInactive->id}) expected 0 deals but got {$inactiveDeals}. ".
        'The delivery_days_mask filter in SnapshotRebuildCommand is NOT excluding this project. '.
        "This is a correctness bug: clients with today's bit cleared MUST be absent from the snapshot."
    );

})->group('imitation');
