<?php

declare(strict_types=1);

namespace App\Services\Billing;

use App\Models\PricingTier;
use Illuminate\Database\Eloquent\Collection;

/**
 * Pure: «при балансе ₽ и доставленных в этом месяце N — сколько лидов клиент
 * получит, проходя ступени pricing_tiers».
 *
 * Все мутации денег — bcmath (string-int копейки), без PHP float.
 *
 * Spec: docs/superpowers/specs/2026-05-23-billing-v2-spec-a-balance-rub-design.md §3.3.1
 */
final class BalanceToLeadsConverter
{
    /**
     * @param  Collection<int, PricingTier>  $tiers
     * @return array{
     *   leads: int,
     *   breakdown: list<array{tier_no:int, leads:int, price_rub:string}>,
     *   current_tier: array{no:int, price_rub:string, leads_left_in_tier:int}|null,
     *   next_tier: array{no:int, price_rub:string, leads_in_tier:int}|null
     * }
     */
    public function convert(string $balanceRub, int $deliveredInMonth, Collection $tiers): array
    {
        $balanceKopecks = bcmul($balanceRub, '100', 0);
        /** @var Collection<int, PricingTier> $sorted */
        $sorted = $tiers
            ->filter(fn (PricingTier $t) => (bool) $t->is_active)
            ->sortBy('tier_no')
            ->values();

        $totalLeads = 0;
        $breakdown = [];
        $cumulative = 0;
        $currentTier = null;
        $currentTierIndex = null;

        foreach ($sorted as $index => $tier) {
            $tierCap = $tier->leads_in_tier === null ? PHP_INT_MAX : (int) $tier->leads_in_tier;
            $tierEnd = $cumulative + $tierCap;

            // «Текущая ступень» — первая ступень, в которую попадает следующий лид
            // (deliveredInMonth + 1), т.е. первая где deliveredInMonth < tierEnd.
            if ($currentTier === null && $deliveredInMonth < $tierEnd) {
                $slotsLeftForInfo = $tier->leads_in_tier === null
                    ? PHP_INT_MAX
                    : max(0, $tierEnd - max($cumulative, $deliveredInMonth));
                $currentTier = [
                    'no' => (int) $tier->tier_no,
                    'price_rub' => self::kopecksToRub((int) $tier->price_per_lead_kopecks),
                    'leads_left_in_tier' => $slotsLeftForInfo,
                ];
                $currentTierIndex = $index;
            }

            // Слоты в этой ступени, доступные для новых лидов
            $slotsLeftInTier = max(0, $tierEnd - max($cumulative, $deliveredInMonth));
            if ($slotsLeftInTier <= 0) {
                $cumulative = $tierEnd;

                continue;
            }

            $priceKopecks = (int) $tier->price_per_lead_kopecks;
            if ($priceKopecks <= 0) {
                $totalLeads += $slotsLeftInTier;
                $breakdown[] = [
                    'tier_no' => (int) $tier->tier_no,
                    'leads' => $slotsLeftInTier,
                    'price_rub' => '0.00',
                ];
                if ($tier->leads_in_tier === null) {
                    break;
                }
                $cumulative = $tierEnd;

                continue;
            }

            $affordableInTier = (int) bcdiv($balanceKopecks, (string) $priceKopecks, 0);
            $take = min($slotsLeftInTier, $affordableInTier);

            if ($take > 0) {
                $totalLeads += $take;
                $breakdown[] = [
                    'tier_no' => (int) $tier->tier_no,
                    'leads' => $take,
                    'price_rub' => self::kopecksToRub($priceKopecks),
                ];
                $balanceKopecks = bcsub(
                    $balanceKopecks,
                    bcmul((string) $priceKopecks, (string) $take, 0),
                    0
                );
            }

            if ($take < $slotsLeftInTier) {
                // Balance exhausted within this tier — stop
                break;
            }

            if ($tier->leads_in_tier === null) {
                // Unlimited tier fully consumed (shouldn't happen with real balance)
                break;
            }

            $cumulative = $tierEnd;
        }

        // next_tier: the first active tier whose tier_no > current_tier, if any
        $nextTier = null;
        if ($currentTier !== null && $currentTierIndex !== null) {
            for ($i = $currentTierIndex + 1; $i < $sorted->count(); $i++) {
                /** @var PricingTier $candidate */
                $candidate = $sorted[$i];
                $nextTier = [
                    'no' => (int) $candidate->tier_no,
                    'price_rub' => self::kopecksToRub((int) $candidate->price_per_lead_kopecks),
                    'leads_in_tier' => $candidate->leads_in_tier === null ? 0 : (int) $candidate->leads_in_tier,
                ];
                break;
            }
        }

        return [
            'leads' => $totalLeads,
            'breakdown' => $breakdown,
            'current_tier' => $currentTier,
            'next_tier' => $nextTier,
        ];
    }

    private static function kopecksToRub(int $kopecks): string
    {
        return bcdiv((string) $kopecks, '100', 2);
    }
}
