<?php

/**
 * 文件功能：赛马结算队列任务
 *
 * 跑马结束后触发，按彩池赔率结算所有下注记录，
 * 中奖者获得按注池比例计算的赔付，失败者金币已在下注时扣除。
 * 结算完成后广播结果并发公屏公告。
 *
 * @author  ChatRoom Laravel
 *
 * @version 1.0.0
 */

namespace App\Jobs;

use App\Enums\CurrencySource;
use App\Events\HorseRaceSettled;
use App\Events\MessageSent;
use App\Models\GameConfig;
use App\Models\HorseBet;
use App\Models\HorseRace;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\DB;

class CloseHorseRaceJob implements ShouldQueue
{
    use Queueable;

    /**
     * 最大重试次数。
     */
    public int $tries = 3;

    /**
     * @param  HorseRace  $race  要结算的场次
     */
    public function __construct(
        public readonly HorseRace $race,
    ) {}

    /**
     * 执行结算逻辑。
     */
    public function handle(
        UserCurrencyService $currency,
        ChatStateService $chatState,
    ): void {
        $race = $this->race->fresh();

        // 防止重复结算
        if (! $race || $race->status !== 'running') {
            return;
        }

        // CAS 改状态为 settled
        $updated = HorseRace::query()
            ->where('id', $race->id)
            ->where('status', 'running')
            ->update(['status' => 'settled', 'settled_at' => now()]);

        if (! $updated) {
            return;
        }

        $race->refresh();

        $config = GameConfig::forGame('horse_racing')?->params ?? [];
        $houseTake = (int) ($config['house_take_percent'] ?? 5);
        $seedPool = (int) ($config['seed_pool'] ?? 0);
        $winnerId = (int) $race->winner_horse_id;

        // 按马匹统计各匹下注金额
        $horsePools = HorseBet::query()
            ->where('race_id', $race->id)
            ->groupBy('horse_id')
            ->selectRaw('horse_id, SUM(amount) as pool')
            ->pluck('pool', 'horse_id')
            ->toArray();

        $totalPool = array_sum($horsePools);
        $winnerPool = (int) ($horsePools[$winnerId] ?? 0);
        $distributablePool = HorseRace::calcDistributablePool($horsePools, $houseTake, $seedPool, $winnerPool);

        // 结算：遍历所有下注记录
        $bets = HorseBet::query()
            ->where('race_id', $race->id)
            ->where('status', 'pending')
            ->with('user')
            ->get();

        $totalPayout = 0;
        $participantSettlements = [];

        DB::transaction(function () use ($bets, $winnerId, $distributablePool, $winnerPool, $currency, &$totalPayout, &$participantSettlements) {
            foreach ($bets as $bet) {
                if ((int) $bet->horse_id !== $winnerId) {
                    // 未中奖（本金已在下注时扣除）
                    $bet->update(['status' => 'lost', 'payout' => 0]);
                    $this->recordParticipantSettlement($participantSettlements, $bet, -$bet->amount, 0);

                    continue;
                }

                // 中奖：按注额比例分配净注池
                if ($winnerPool > 0) {
                    $payout = (int) round($distributablePool * ($bet->amount / $winnerPool));
                } else {
                    $payout = 0;
                }

                $bet->update(['status' => 'won', 'payout' => $payout]);

                if ($payout > 0 && $bet->user) {
                    $currency->change(
                        $bet->user,
                        'gold',
                        $payout,
                        CurrencySource::HORSE_WIN,
                        "赛马 #{$this->race->id} 「{$bet->horse_id}号马」中奖",
                    );
                }

                // 结算提示需要显示本场净输赢，因此要减去下注时已支付的本金。
                $this->recordParticipantSettlement($participantSettlements, $bet, $payout - $bet->amount, $payout);
                $totalPayout += $payout;
            }
        });

        // 公屏公告
        $this->pushResultMessage($race, $chatState, $totalPayout);

        // 参与者私聊结算提醒
        $this->pushParticipantToastNotifications($race, $chatState, $participantSettlements);

        // 广播结算事件
        broadcast(new HorseRaceSettled($race));
    }

    /**
     * 汇总单个参与者本场的下注、返还与净输赢金额。
     *
     * @param  array<int, array<string, mixed>>  $participantSettlements
     */
    private function recordParticipantSettlement(array &$participantSettlements, HorseBet $bet, int $netChange, int $payout): void
    {
        if (! $bet->user) {
            return;
        }

        $userId = (int) $bet->user->id;
        $existing = $participantSettlements[$userId] ?? [
            'user' => $bet->user,
            'username' => $bet->user->username,
            'bet_amount' => 0,
            'payout' => 0,
            'net_change' => 0,
            'horse_id' => (int) $bet->horse_id,
        ];

        // 即使出现脏数据导致同一用户多笔下注，也统一汇总成本场总输赢。
        $existing['bet_amount'] += (int) $bet->amount;
        $existing['payout'] += $payout;
        $existing['net_change'] += $netChange;
        $existing['horse_id'] = (int) $bet->horse_id;

        $participantSettlements[$userId] = $existing;
    }

    /**
     * 向参与本场的用户发送私聊结算提示，并复用右下角 toast 通知。
     *
     * @param  array<int, array<string, mixed>>  $participantSettlements
     */
    private function pushParticipantToastNotifications(HorseRace $race, ChatStateService $chatState, array $participantSettlements): void
    {
        if ($participantSettlements === []) {
            return;
        }

        $roomId = 1;
        $winnerName = $this->resolveWinnerHorseName($race);

        foreach ($participantSettlements as $settlement) {
            $user = $settlement['user'];
            $username = (string) $settlement['username'];
            $betAmount = (int) $settlement['bet_amount'];
            $netChange = (int) $settlement['net_change'];
            $freshGold = (int) ($user->fresh()->jjb ?? 0);
            $horseId = (int) $settlement['horse_id'];

            $absNetChange = number_format(abs($netChange));
            $betAmountText = number_format($betAmount);
            $summaryText = $netChange > 0
                ? "净赢 {$absNetChange} 金币"
                : ($netChange < 0 ? "净输 {$absNetChange} 金币" : '不输不赢');
            $toastIcon = $netChange > 0 ? '🏇' : ($netChange < 0 ? '📉' : '🐎');
            $toastColor = $netChange > 0 ? '#10b981' : ($netChange < 0 ? '#ef4444' : '#3b82f6');
            $toastMessage = $netChange > 0
                ? "冠军：<b>{$winnerName}</b><br>你本场净赢 <b>+{$absNetChange}</b> 金币！"
                : ($netChange < 0
                    ? "冠军：<b>{$winnerName}</b><br>你本场净输 <b>-{$absNetChange}</b> 金币。"
                    : "冠军：<b>{$winnerName}</b><br>你本场不输不赢。");

            // 写入系统私聊，方便用户在聊天历史中回看本场结算结果。
            $msg = [
                'id' => $chatState->nextMessageId($roomId),
                'room_id' => $roomId,
                'from_user' => '系统',
                'to_user' => $username,
                'content' => "🏇 赛马第 #{$race->id} 场已结束，冠军：{$winnerName}。你押注 {$horseId} 号马 {$betAmountText} 金币，{$summaryText}；当前金币：{$freshGold} 枚。",
                'is_secret' => true,
                'font_color' => '#f59e0b',
                'action' => '',
                'sent_at' => now()->toDateTimeString(),
                'toast_notification' => [
                    'title' => '🏇 赛马本场结算',
                    'message' => $toastMessage,
                    'icon' => $toastIcon,
                    'color' => $toastColor,
                    'duration' => 10000,
                ],
            ];

            $chatState->pushMessage($roomId, $msg);
            broadcast(new MessageSent($roomId, $msg));
            SaveMessageJob::dispatch($msg);
        }
    }

    /**
     * 向公屏发送赛果系统消息。
     */
    private function pushResultMessage(HorseRace $race, ChatStateService $chatState, int $totalPayout): void
    {
        // 找出胜利马匹名称
        $winnerName = $this->resolveWinnerHorseName($race);

        $payoutText = $totalPayout > 0
            ? '共派发 💰'.number_format($totalPayout).' 金币'
            : '本场无人获奖';

        $content = "🏆 【赛马】第 #{$race->id} 场结束！冠军：{$winnerName}！{$payoutText}。";

        $msg = [
            'id' => $chatState->nextMessageId(1),
            'room_id' => 1,
            'from_user' => '系统传音',
            'to_user' => '大家',
            'content' => $content,
            'is_secret' => false,
            'font_color' => '#f59e0b',
            'action' => '大声宣告',
            'sent_at' => now()->toDateTimeString(),
        ];
        $chatState->pushMessage(1, $msg);
        broadcast(new MessageSent(1, $msg));
        SaveMessageJob::dispatch($msg);
    }

    /**
     * 解析冠军马匹的展示名称。
     */
    private function resolveWinnerHorseName(HorseRace $race): string
    {
        $horses = $race->horses ?? [];
        foreach ($horses as $horse) {
            if (($horse['id'] ?? 0) === (int) $race->winner_horse_id) {
                return ($horse['emoji'] ?? '').($horse['name'] ?? '');
            }
        }

        return '未知';
    }
}
