<?php

declare(strict_types=1);

namespace App\Services\Supplier;

use App\Services\Supplier\Dto\SupplierProjectDto;
use Carbon\Carbon;
use Illuminate\Support\Collection;

/**
 * Pure function: формула заказа у поставщика на (источник × субъект).
 *
 * Заказ группы eligible-клиентов:
 *
 *   order = max(наибольший_лимит, ceil(Σ_лимитов / 3))
 *
 *     ceil(Σ/3) — ёмкость шаринга (лид продаётся ≤3 раз клиентам Лидерры).
 *     наиб      — крупнейший клиент должен иметь шанс добрать.
 *
 * Этот `order` затем ДЕЛИТСЯ между площадками B1/B2/B3 через distributeForPlatform()
 * так, чтобы Σ per-platform лимитов == order. Портал НЕ делит сам: проверено вживую
 * 2026-05-21 (listProjects) — каждый B-проект честно набирает до своего лимита
 * независимо, поэтому одинаковый лимит на 3 площадках = заказ ×3 (переплата).
 * Plan 3 R6 («портал делит, verified 15→5») оказался ложным — split восстановлен.
 *
 * `allocate()` оставлен с прежней сигнатурой для временной совместимости
 * c SyncSupplierProjectsJob — внутри использует computeOrder, возвращает
 * DTO с одинаковым limit на любую platform/signalType.
 *
 * Workdays и regions — союзы (deduplicated, sorted) активных Лидерра-проектов,
 * eligible на targetDate (фильтр по weekday в Europe/Moscow).
 *
 * Spec: docs/superpowers/specs/2026-05-20-project-migration-redesign-design.md §4.5.
 */
final class SupplierQuotaAllocator
{
    /**
     * @param  Collection<int, \stdClass>  $activeLiderraProjects  объекты с полями daily_limit, workdays, regions
     */
    public static function allocate(
        string $platform,
        string $signalType,
        string $uniqueKey,
        Collection $activeLiderraProjects,
        Carbon $targetDate,
    ): ?SupplierProjectDto {
        $targetWeekday = $targetDate->copy()->timezone('Europe/Moscow')->isoWeekday();

        $eligibleProjects = $activeLiderraProjects->filter(
            fn (\stdClass $p) => in_array($targetWeekday, (array) $p->workdays, true)
        );

        if ($eligibleProjects->isEmpty()) {
            return null;
        }

        $totalQuota = (int) $eligibleProjects->sum('daily_limit');
        $workdaysUnion = self::unionInts($eligibleProjects->pluck('workdays'));
        $regionsUnion = self::unionInts($eligibleProjects->pluck('regions'));

        $platformLimit = self::computeOrder(
            $eligibleProjects->pluck('daily_limit')->map(fn ($v) => (int) $v)->all()
        );

        return new SupplierProjectDto(
            platform: $platform,
            signalType: $signalType,
            uniqueKey: $uniqueKey,
            limit: $platformLimit,
            workdays: $workdaysUnion,
            regions: $regionsUnion,
            regionsReverse: false,
            status: 'active',
        );
    }

    /**
     * Заказ у поставщика на (источник × субъект): max(наибольший лимит, ceil(Σ/3)).
     *
     *   ceil(Σ/3) — ёмкость шаринга (лид продаётся ≤3 раз).
     *   наиб      — крупнейший клиент должен иметь шанс добрать.
     *
     * Возвращает заказ ГРУППЫ; деление между B1/B2/B3 — distributeForPlatform().
     *
     * @param  array<int, int>  $dailyLimits  лимиты eligible-сегодня клиентов группы
     */
    public static function computeOrder(array $dailyLimits): int
    {
        if ($dailyLimits === []) {
            return 0;
        }

        $sum = array_sum($dailyLimits);
        $max = max($dailyLimits);

        return max($max, (int) ceil($sum / 3));
    }

    /**
     * Делит групповой заказ между площадками так, чтобы СУММА per-platform лимитов == order.
     *
     * Largest-remainder: каждой площадке floor(order/N), затем по +1 первым (order mod N)
     * площадкам в порядке списка. Сумма всегда точно равна order — ни переплаты, ни недобора.
     *
     * Восстанавливает поведение, удалённое в Plan 3 R6 (ошибочное допущение «портал делит сам»).
     * Портал НЕ делит — каждый B-проект набирает до своего лимита независимо; одинаковый
     * лимит на N площадках = заказ ×N (переплата). Verified live 2026-05-21.
     *
     * @param  list<string>  $platforms  площадки в каноническом порядке (B1<B2<B3)
     * @return array<string, int> [platform => лимит этой площадки]
     */
    public static function distributeForPlatform(int $order, array $platforms): array
    {
        $count = count($platforms);
        if ($count === 0) {
            return [];
        }

        $order = max(0, $order);
        $base = intdiv($order, $count);
        $remainder = $order % $count;

        $shares = [];
        $i = 0;
        foreach ($platforms as $platform) {
            $shares[$platform] = $base + ($i < $remainder ? 1 : 0);
            $i++;
        }

        return $shares;
    }

    /**
     * @param  Collection<int, mixed>  $arrays
     * @return array<int, int>
     */
    private static function unionInts(Collection $arrays): array
    {
        return $arrays
            ->flatMap(fn ($a) => (array) $a)
            ->map(fn ($v) => (int) $v)
            ->unique()
            ->sort()
            ->values()
            ->all();
    }
}
