<?php

declare(strict_types=1);

namespace App\Services\Billing;

use App\Models\BalanceTransaction;
use App\Models\Tenant;

/**
 * Сервис пополнения рублёвого баланса тенанта (audit E1).
 *
 * MVP-stub: кредитует tenants.balance_rub немедленно и пишет строку
 * balance_transactions(type='topup'). Реальная оплата через платёжный
 * шлюз — post-Б-1 (требует реквизитов ООО), здесь НЕ интегрирована.
 *
 * Контракт: вызывается ВНУТРИ транзакции (middleware `tenant` оборачивает
 * HTTP-запрос в DB-транзакцию). lockForUpdate на строке tenant защищает от
 * lost-update при конкурентных topup/charge.
 *
 * balance_transactions защищена hash-chain триггером (BEFORE INSERT
 * audit_chain_hash) — log_hash заполняется автоматически. UPDATE/DELETE
 * на таблице запрещены триггером audit_block_mutation, поэтому каждое
 * пополнение — отдельная append-only строка; существующие не меняются.
 */
final class BillingTopupService
{
    public function __construct(
        private readonly ProjectBlockReleaseService $blockRelease = new ProjectBlockReleaseService,
    ) {}

    /**
     * Пополнить рублёвый баланс тенанта.
     *
     * @param  int  $tenantId  ID тенанта.
     * @param  string  $amountRub  Сумма пополнения, DECIMAL-строка («100.00»).
     * @param  int|null  $userId  Кто инициировал (NULL — системное).
     * @return BalanceTransaction Созданная append-only строка ledger'а.
     */
    public function topup(int $tenantId, string $amountRub, ?int $userId): BalanceTransaction
    {
        /** @var Tenant $tenant */
        $tenant = Tenant::query()->lockForUpdate()->findOrFail($tenantId);

        // bcadd — DECIMAL-точность, НЕ PHP float (паттерн LedgerService).
        $newBalanceRub = bcadd((string) $tenant->balance_rub, $amountRub, 2);

        // Eloquent decimal:2 cast сохраняет bcmath-строку без потери точности
        // при save() (в отличие от decrement(), который требует float|int —
        // именно поэтому LedgerService использует raw DB::table()->update();
        // здесь же присваивание уже посчитанной строки через модель безопасно).
        $tenant->balance_rub = $newBalanceRub;
        $tenant->save();

        $tx = BalanceTransaction::create([
            'tenant_id' => $tenant->id,
            'type' => BalanceTransaction::TYPE_TOPUP,
            'amount_rub' => $amountRub,
            'amount_leads' => 0,
            'balance_rub_after' => $newBalanceRub,
            'balance_leads_after' => (int) $tenant->balance_leads,
            'description' => 'Пополнение баланса',
            'user_id' => $userId,
            'created_at' => now(),
        ]);

        // E (балансовый блок): после зачисления — авто-снятие preflight_blocked_at по
        // политике «всё-или-ничего» (хватает на весь заказ → снять блок со всех + синк).
        $this->blockRelease->releaseForTenant($tenant->id);

        return $tx;
    }
}
