<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\Project;
use App\Models\SupplierProject;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use InvalidArgumentException;

/**
 * Подбор eligible Лидерра-проектов для входящего лида (sharing-model §6).
 *
 * Алгоритм:
 *   1. SELECT projects WHERE supplier_b{1,2,3}_project_id = $supplier->id (по platform).
 *   2. Фильтр: is_active=true.
 *   3. Workdays: (delivery_days_mask & today_bit) <> 0, today_bit = 1 << (ISO_DOW - 1).
 *   4. delivered_today < COALESCE(effective_daily_limit_today, daily_limit_target).
 *   5. tenants.balance_leads > 0 OR tenants.balance_rub > 0 (через WHERE EXISTS;
 *      Plan 4 Task 4: dual-balance — rub-only tenant ДОЛЖЕН пройти, LedgerService
 *      сам резолвит prepaid/rub и кидает InsufficientBalanceException, если оба = 0).
 *   6. Region match через PhonePrefixService::phoneMatchesRegions (в PHP, не в SQL —
 *      district-bit резолвится по 3/4-значному коду в PHP-словаре).
 *   7. Сортировка: created_at ASC, id ASC (детерминированно — spec §6 step 4).
 *
 * Plan 3 Task 3: запрос идёт через connection `pgsql_supplier` (BYPASSRLS-роль
 * crm_supplier_worker). Это закрывает WARN #2 — в sharing-flow tenant ещё не
 * определён, SELECT обходит RLS-фильтрацию и видит проекты ВСЕХ tenant'ов
 * параллельно. WHERE-фильтры (is_active, FK на supplier_project, workdays, лимиты,
 * balance) сохраняются как defense-in-depth.
 *
 * Spec: docs/superpowers/specs/2026-05-10-supplier-integration-design.md §6 +
 *       docs/superpowers/specs/2026-05-11-plan3-supplier-sync-design.md §1.
 */
class LeadRouter
{
    public function __construct(
        private readonly PhonePrefixService $phonePrefix,
    ) {}

    /**
     * @return Collection<int, Project>
     */
    public function matchEligibleProjects(SupplierProject $supplierProject, string $phone): Collection
    {
        $fkColumn = match ($supplierProject->platform) {
            'B1' => 'supplier_b1_project_id',
            'B2' => 'supplier_b2_project_id',
            'B3' => 'supplier_b3_project_id',
            // Unreachable per CHECK chk_supplier_projects_platform; defensive for static analysis.
            default => throw new InvalidArgumentException(
                "Unknown supplier platform: {$supplierProject->platform}"
            ),
        };

        // МСК-aligned ISO day-of-week: Plan 2 Task 9 reset cron also runs at 00:00 МСК,
        // so workday-mask check must use same timezone to avoid off-by-one near midnight.
        $todayBit = 1 << (Carbon::now('Europe/Moscow')->isoWeekday() - 1);

        /** @var Collection<int, Project> $candidates */
        $candidates = Project::on('pgsql_supplier')
            ->where($fkColumn, $supplierProject->id)
            ->where('is_active', true)
            ->whereRaw('(delivery_days_mask & ?) <> 0', [$todayBit])
            ->whereRaw(
                'delivered_today < COALESCE(effective_daily_limit_today, daily_limit_target)'
            )
            ->whereExists(function ($q): void {
                // Plan 4 Task 4: dual-balance — допускаем rub-only tenant'ов.
                // LedgerService::chargeForDelivery сам выбирает prepaid (balance_leads--)
                // или rub (balance_rub -= tier_price) и кидает InsufficientBalanceException,
                // если ОБА = 0. До Plan 4 фильтр был строгий balance_leads > 0 (prepaid only).
                $q->selectRaw('1')
                    ->from('tenants')
                    ->whereColumn('tenants.id', 'projects.tenant_id')
                    ->where(function ($qq): void {
                        $qq->where('tenants.balance_leads', '>', 0)
                            ->orWhere('tenants.balance_rub', '>', 0);
                    });
            })
            ->orderBy('created_at')
            ->orderBy('id')
            ->get();

        return $candidates->filter(
            fn (Project $p): bool => $this->phonePrefix->phoneMatchesRegions(
                $phone,
                (int) $p->region_mask,
                (string) $p->region_mode,
            )
        )->values();
    }
}
