<?php

/**
 * 文件功能：百家乐结算队列任务
 *
 * 在押注截止时间到达后自动触发：
 * ① 摇三颗骰子 ② 判断大/小/豹子/庄家收割
 * ③ 遍历下注记录逐一发放赔付 ④ 广播结果 ⑤ 公屏公告
 *
 * @author  ChatRoom Laravel
 *
 * @version 1.0.0
 */

namespace App\Jobs;

use App\Enums\CurrencySource;
use App\Events\BaccaratRoundSettled;
use App\Events\MessageSent;
use App\Models\BaccaratBet;
use App\Models\BaccaratRound;
use App\Models\GameConfig;
use App\Services\BaccaratLossCoverService;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class CloseBaccaratRoundJob implements ShouldQueue
{
    use Queueable;

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

    /**
     * @param  BaccaratRound  $round  要结算的局次
     */
    public function __construct(
        public readonly BaccaratRound $round,
    ) {}

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

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

        $config = GameConfig::forGame('baccarat')?->params ?? [];

        // 乐观锁：CAS 先把状态改为 settling
        $updated = BaccaratRound::query()
            ->where('id', $round->id)
            ->where('status', 'betting')
            ->update(['status' => 'settling']);

        if (! $updated) {
            return; // 已被其他进程处理
        }

        // ── 摇骰子 ──────────────────────────────────────────────────
        $dice = [random_int(1, 6), random_int(1, 6), random_int(1, 6)];
        $total = array_sum($dice);

        // ── 判断结果 ────────────────────────────────────────────────
        $killPoints = $config['kill_points'] ?? [3, 18];
        if (! is_array($killPoints)) {
            $killPoints = explode(',', (string) $killPoints);
        }
        $killPoints = array_map('intval', $killPoints);

        $result = match (true) {
            $dice[0] === $dice[1] && $dice[1] === $dice[2] => 'triple', // 豹子（优先判断）
            in_array($total, $killPoints, true) => 'kill',   // 庄家收割
            $total >= 11 && $total <= 17 => 'big',    // 大
            default => 'small',  // 小
        };

        // ── 结算下注记录 ─────────────────────────────────────────────
        $bets = BaccaratBet::query()->where('round_id', $round->id)->where('status', 'pending')->with('user')->get();
        $totalPayout = 0;

        // 收集各用户输赢结果，用于公屏展示
        $winners = [];
        $losers = [];
        $participantSettlements = [];

        DB::transaction(function () use ($bets, $result, $config, $currency, $lossCoverService, &$totalPayout, &$winners, &$losers, &$participantSettlements) {
            foreach ($bets as $bet) {
                /** @var \App\Models\BaccaratBet $bet */
                $username = $bet->user->username ?? '匿名';

                if ($result === 'kill') {
                    // 庄家收割：全灭无退款
                    $bet->update(['status' => 'lost', 'payout' => 0]);
                    $lossCoverService->registerSettlement($bet->fresh());
                    $losers[] = "{$username}-{$bet->amount}";
                    $this->recordParticipantSettlement($participantSettlements, $bet, -$bet->amount, 0);

                    if ($username === 'AI小班长') {
                        $this->handleAiLoseStreak();
                    }

                    continue;
                }

                if ($bet->bet_type === $result) {
                    // 中奖：计算赔付（含本金返还）
                    $payout = BaccaratRound::calcPayout($bet->bet_type, $bet->amount, $config);
                    $bet->update(['status' => 'won', 'payout' => $payout]);

                    // 金币入账
                    $currency->change(
                        $bet->user,
                        'gold',
                        $payout,
                        CurrencySource::BACCARAT_WIN,
                        "百家乐 #{$this->round->id} 押 {$bet->betTypeLabel()} 中奖",
                    );
                    $totalPayout += $payout;
                    $lossCoverService->registerSettlement($bet->fresh());
                    $winners[] = "{$username}+".number_format($payout);
                    // 结算提醒展示的是本局净输赢，因此要扣除下注时已经支付的本金。
                    $this->recordParticipantSettlement($participantSettlements, $bet, $payout - $bet->amount, $payout);

                    if ($username === 'AI小班长') {
                        Redis::del('ai_baccarat_lose_streak'); // 赢了清空连输
                    }
                } else {
                    $bet->update(['status' => 'lost', 'payout' => 0]);
                    $lossCoverService->registerSettlement($bet->fresh());
                    $losers[] = "{$username}-".number_format($bet->amount);
                    $this->recordParticipantSettlement($participantSettlements, $bet, -$bet->amount, 0);

                    if ($username === 'AI小班长') {
                        $this->handleAiLoseStreak();
                    }
                }
            }
        });

        // ── 更新局次记录 ─────────────────────────────────────────────
        $round->update([
            'dice1' => $dice[0],
            'dice2' => $dice[1],
            'dice3' => $dice[2],
            'total_points' => $total,
            'result' => $result,
            'status' => 'settled',
            'settled_at' => now(),
            'total_payout' => $totalPayout,
        ]);

        $round->refresh();

        // ── 广播结算事件 ─────────────────────────────────────────────
        broadcast(new BaccaratRoundSettled($round));

        // ── 公屏公告 ─────────────────────────────────────────────────
        $this->pushResultMessage($round, $chatState, $winners, $losers);

        // ── 参与者私聊提醒 ────────────────────────────────────────────
        $this->pushParticipantToastNotifications($round, $chatState, $participantSettlements);
    }

    /**
     * 处理 AI 小班长连输逻辑
     */
    private function handleAiLoseStreak(): void
    {
        $streak = Redis::incr('ai_baccarat_lose_streak');
        if ($streak >= 10) {
            Redis::setex('ai_baccarat_timeout', 600, 'timeout'); // 连输十次，停赛10分钟
            Redis::del('ai_baccarat_lose_streak');
        }
    }

    /**
     * 汇总单个参与者本局的下注、返还与净输赢金额。
     *
     * @param  array<int, array<string, mixed>>  $participantSettlements
     */
    private function recordParticipantSettlement(array &$participantSettlements, BaccaratBet $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,
        ];

        // 同一用户若存在多条下注记录，这里统一聚合成本局总输赢。
        $existing['bet_amount'] += (int) $bet->amount;
        $existing['payout'] += $payout;
        $existing['net_change'] += $netChange;

        $participantSettlements[$userId] = $existing;
    }

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

        $roomId = 1;
        $roundResultLabel = $round->resultLabel();

        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);

            $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>{$roundResultLabel}</b><br>你本局净赢 <b>+{$absNetChange}</b> 金币！"
                : ($netChange < 0
                    ? "本局结果：<b>{$roundResultLabel}</b><br>你本局净输 <b>-{$absNetChange}</b> 金币。"
                    : "本局结果：<b>{$roundResultLabel}</b><br>你本局不输不赢。");

            // 写入系统私聊，确保用户刷新聊天室后仍能看到本局输赢记录。
            $msg = [
                'id' => $chatState->nextMessageId($roomId),
                'room_id' => $roomId,
                'from_user' => '系统',
                'to_user' => $username,
                'content' => "🎲 百家乐第 #{$round->id} 局已开奖，结果：{$roundResultLabel}。你本局下注 {$betAmountText} 金币，{$summaryText}；当前金币：{$freshGold} 枚。",
                'is_secret' => true,
                'font_color' => '#8b5cf6',
                '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);
        }
    }

    /**
     * 向公屏发送开奖结果系统消息（含各用户输赢情况）。
     *
     * @param  array<string>  $winners  中奖用户列表，格式 "用户名+金额"
     * @param  array<string>  $losers  未中奖用户列表，格式 "用户名-金额"
     */
    private function pushResultMessage(BaccaratRound $round, ChatStateService $chatState, array $winners = [], array $losers = []): void
    {
        $diceStr = "《{$round->dice1}》《{$round->dice2}》《{$round->dice3}》";

        $resultText = match ($round->result) {
            'big' => "🔵 大（{$round->total_points} 点）",
            'small' => "🟡 小（{$round->total_points} 点）",
            'triple' => "💥 豹子！（{$round->dice1}{$round->dice2}{$round->dice3}）",
            'kill' => "☠️ 庄家收割！（{$round->total_points} 点）全灭",
            default => '',
        };

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

        // 拼接用户输赢明细（最多显示 10 人，防止消息过长）
        $detailParts = [];
        if ($winners) {
            $detailParts[] = '🏆 中奖：'.implode('、', array_slice($winners, 0, 10)).' 💰';
        }
        if ($losers) {
            $detailParts[] = '😔 未中：'.implode('、', array_slice($losers, 0, 10));
        }

        $detail = $detailParts ? '  '.implode('  ', $detailParts) : '';

        $content = "🎲 【百家乐】第 #{$round->id} 局开奖！{$diceStr} 总点 {$round->total_points} → {$resultText}！{$payoutText}。{$detail}";

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

        // 触发微信机器人消息推送 (百家乐结果，无人参与时不推送微信群防止刷屏)
        try {
            if (! empty($winners) || ! empty($losers)) {
                $wechatService = new \App\Services\WechatBot\WechatNotificationService;
                $wechatService->notifyBaccaratResult($content);
            }
        } catch (\Exception $e) {
            \Illuminate\Support\Facades\Log::error('WechatBot baccarat notification failed', ['error' => $e->getMessage()]);
        }
    }
}
