<?php

namespace App\Services;

use App\Models\RouletteDrop;
use App\Models\RouletteItem;
use App\Models\SiteSetting;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use RuntimeException;

class RouletteService
{
    /**
     * Spend one spin's cost and award a weighted-random prize, atomically.
     *
     * The legacy `roulette_generate` action read the donate balance,
     * computed a new value in PHP, and wrote it back as two separate
     * queries with no transaction or row lock — concurrent requests could
     * race the balance check and spin more times than the player could
     * afford. Locking the user row for the duration of the transaction
     * closes that gap.
     */
    public function spin(User $user): RouletteDrop
    {
        $cost = (int) SiteSetting::get('donate_cost', 200);

        return DB::transaction(function () use ($user, $cost) {
            /** @var User $lockedUser */
            $lockedUser = User::query()->whereKey($user->id)->lockForUpdate()->first();

            if ($lockedUser->donate_balance < $cost) {
                throw new RuntimeException('Insufficient donate balance.');
            }

            $item = $this->pickWeightedItem();

            $lockedUser->decrement('donate_balance', $cost);

            $value = $item->rollValue();

            return RouletteDrop::create([
                'user_id' => $lockedUser->id,
                'roulette_item_id' => $item->id,
                'value' => $value,
            ]);
        });
    }

    protected function pickWeightedItem(): RouletteItem
    {
        $items = RouletteItem::query()->get(['id', 'weight', 'roulette_category_id', 'name', 'image_path', 'value_min', 'value_max']);

        if ($items->isEmpty()) {
            throw new RuntimeException('No roulette items are configured.');
        }

        $totalWeight = $items->sum('weight');
        // random_int (CSPRNG) replaces the legacy mt_rand()/rand() calls
        // used to pick both the prize and its rolled value.
        $roll = random_int(1, max(1, $totalWeight));

        $cumulative = 0;
        foreach ($items as $item) {
            $cumulative += $item->weight;
            if ($roll <= $cumulative) {
                return $item;
            }
        }

        return $items->last();
    }
}
