<?php

/**
 * 文件功能：双色球彩票核心服务
 *
 * 负责购票、开奖、奖级判定、奖池管理、派奖及公屏广播全流程。
 * 所有金币变动通过 UserCurrencyService::change() 执行并记录流水。
 *
 * @author  ChatRoom Laravel
 *
 * @version 1.0.0
 */

namespace App\Services;

use App\Enums\CurrencySource;
use App\Events\MessageSent;
use App\Models\GameConfig;
use App\Models\LotteryIssue;
use App\Models\LotteryPoolLog;
use App\Models\LotteryTicket;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class LotteryService
{
    public function __construct(
        private readonly UserCurrencyService $currency,
        private readonly ChatStateService $chatState,
    ) {}

    // ─── 购票 ─────────────────────────────────────────────────────────

    /**
     * 用户购买一注或多注彩票。
     *
     * @param  User  $user  购票用户
     * @param  array<array>  $numbers  每注号码：[['reds'=>[3,8,12],'blue'=>4], ...]
     * @param  bool  $quickPick  是否机选
     * @return array<LotteryTicket> 新建的购票记录列表
     *
     * @throws \RuntimeException
     */
    public function buyTickets(User $user, array $numbers, bool $quickPick = false): array
    {
        $config = GameConfig::forGame('lottery')?->params ?? [];

        if (! GameConfig::isEnabled('lottery')) {
            throw new \RuntimeException('双色球彩票游戏未开启');
        }

        $issue = LotteryIssue::currentIssue();
        if (! $issue || ! $issue->isOpen()) {
            throw new \RuntimeException('当前无正在进行的期次，或已停售');
        }

        $ticketPrice = (int) ($config['ticket_price'] ?? 100);
        $maxPerUser = (int) ($config['max_tickets_per_user'] ?? 50);
        $maxPerBuy = (int) ($config['max_tickets_per_buy'] ?? 10);
        $poolRatio = (int) ($config['pool_ratio'] ?? 70);

        // 本期已购注数
        $alreadyBought = LotteryTicket::query()
            ->where('issue_id', $issue->id)
            ->where('user_id', $user->id)
            ->count();

        $buyCount = count($numbers);

        if ($buyCount > $maxPerBuy) {
            throw new \RuntimeException("单次最多购买 {$maxPerBuy} 注");
        }
        if ($alreadyBought + $buyCount > $maxPerUser) {
            $remain = $maxPerUser - $alreadyBought;
            throw new \RuntimeException("本期单人最多 {$maxPerUser} 注，您还可购 {$remain} 注");
        }

        $totalCost = $ticketPrice * $buyCount;
        if ($user->jjb < $totalCost) {
            throw new \RuntimeException("金币不足，需要 {$totalCost} 金币");
        }

        $tickets = [];

        DB::transaction(function () use (
            $user, $numbers, $issue, $ticketPrice, $poolRatio, $quickPick, &$tickets
        ) {
            foreach ($numbers as $idx => $num) {
                // ...existing validation/DB storing logic...
                $reds = $num['reds'];
                sort($reds); // 红球排序存储，方便比对

                /** @var LotteryTicket $ticket */
                $ticket = LotteryTicket::create([
                    'issue_id' => $issue->id,
                    'user_id' => $user->id,
                    'red1' => $reds[0],
                    'red2' => $reds[1],
                    'red3' => $reds[2],
                    'blue' => $num['blue'],
                    'amount' => $ticketPrice,
                    'is_quick_pick' => $quickPick,
                    'prize_level' => 0,
                    'payout' => 0,
                ]);

                $tickets[] = $ticket;

                // 扣除购票金币并记录流水
                $this->currency->change(
                    user: $user,
                    currency: 'gold',
                    amount: -$ticketPrice,
                    source: CurrencySource::LOTTERY_BUY,
                    remark: "双色球 #{$issue->issue_no} 第".($idx + 1).'注 '.$ticket->numbersLabel(),
                );

                // 记录奖池流水（购票入池部分）
                $poolIn = (int) round($ticketPrice * $poolRatio / 100);
                $issue->increment('pool_amount', $poolIn);
                $issue->increment('total_tickets');

                LotteryPoolLog::create([
                    'issue_id' => $issue->id,
                    'change_amount' => $poolIn,
                    'reason' => 'ticket_sale',
                    'pool_after' => $issue->fresh()->pool_amount,
                    'remark' => "用户 {$user->username} 购第".($idx + 1).'注',
                    'created_at' => now(),
                ]);
            }
        });

        // 用户成功购买后，发送系统传音广播（大家都能看到他买了彩票）
        $firstTicket = $tickets[0];
        $numsStr = $firstTicket->numbersLabel();
        $moreStr = $buyCount > 1 ? "等 {$buyCount} 注号码" : '';
        $this->pushSystemMessage("🎟️ 【双色球彩票】财神爷保佑！玩家【{$user->username}】豪掷千金，购买了当前 #{$issue->issue_no} 期双色球 {$numsStr} {$moreStr}，祝 Ta 中大奖！");

        return $tickets;
    }

    /**
     * 机选号码（随机生成一注或多注）。
     *
     * @param  int  $count  生成注数
     * @return array<array> 格式：[['reds'=>[...], 'blue'=>n], ...]
     */
    public function quickPick(int $count = 1): array
    {
        $result = [];
        for ($i = 0; $i < $count; $i++) {
            $reds = [];
            while (count($reds) < 3) {
                $n = random_int(1, 12);
                if (! in_array($n, $reds, true)) {
                    $reds[] = $n;
                }
            }
            sort($reds);
            $result[] = [
                'reds' => $reds,
                'blue' => random_int(1, 6),
            ];
        }

        return $result;
    }

    // ─── 开奖 ─────────────────────────────────────────────────────────

    /**
     * 执行开奖：随机号码 → 逐票结算 → 奖池派发 → 公屏广播。
     *
     * 由 DrawLotteryJob 在每日指定时间调用。
     */
    public function draw(LotteryIssue $issue): void
    {
        // 防重复开奖
        if ($issue->status !== 'closed') {
            return;
        }

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

        // ── 生成开奖号码 ──
        $reds = [];
        while (count($reds) < 3) {
            $n = random_int(1, 12);
            if (! in_array($n, $reds, true)) {
                $reds[] = $n;
            }
        }
        sort($reds);
        $blue = random_int(1, 6);

        // 写入开奖号码
        $issue->update([
            'red1' => $reds[0],
            'red2' => $reds[1],
            'red3' => $reds[2],
            'blue' => $blue,
            'draw_at' => now(),
        ]);
        $issue->refresh();

        // ── 加载所有购票记录 ──
        $tickets = LotteryTicket::query()
            ->where('issue_id', $issue->id)
            ->with('user')
            ->get();

        $poolAmount = $issue->pool_amount;

        // ── 先扣除固定小奖（四/五等，从奖池中扣）──
        $prize4Fixed = (int) ($config['prize_4th_fixed'] ?? 150);
        $prize5Fixed = (int) ($config['prize_5th_fixed'] ?? 50);

        $prize4Tickets = $tickets->filter(fn ($t) => $issue->calcPrizeLevel($t->red1, $t->red2, $t->red3, $t->blue) === 4);
        $prize5Tickets = $tickets->filter(fn ($t) => $issue->calcPrizeLevel($t->red1, $t->red2, $t->red3, $t->blue) === 5);

        $totalFixed = ($prize4Tickets->count() * $prize4Fixed) + ($prize5Tickets->count() * $prize5Fixed);

        // 若奖池不足则按比例缩减固定奖
        $fixedScale = $totalFixed > 0 && $totalFixed > $poolAmount
            ? $poolAmount / $totalFixed
            : 1.0;

        $poolAfterFixed = max(0, $poolAmount - (int) round($totalFixed * $fixedScale));

        // ── 计算大奖（一/二/三等）可用奖池 ──
        $prize1Ratio = (int) ($config['prize_1st_ratio'] ?? 60);
        $prize2Ratio = (int) ($config['prize_2nd_ratio'] ?? 20);
        $prize3Ratio = (int) ($config['prize_3rd_ratio'] ?? 10);
        $carryRatio = (int) ($config['carry_ratio'] ?? 10);

        $pool1 = (int) round($poolAfterFixed * $prize1Ratio / 100);
        $pool2 = (int) round($poolAfterFixed * $prize2Ratio / 100);
        $pool3 = (int) round($poolAfterFixed * $prize3Ratio / 100);

        // ── 逐票结算 ──
        $totalPayout = 0;
        $winner1Names = [];
        $winner2Names = [];
        $winner3Names = [];
        $prize4Count = 0;
        $prize5Count = 0;

        // 先统计各奖级人数（用于均分）
        $count1 = $tickets->filter(fn ($t) => $issue->calcPrizeLevel($t->red1, $t->red2, $t->red3, $t->blue) === 1)->count();
        $count2 = $tickets->filter(fn ($t) => $issue->calcPrizeLevel($t->red1, $t->red2, $t->red3, $t->blue) === 2)->count();
        $count3 = $tickets->filter(fn ($t) => $issue->calcPrizeLevel($t->red1, $t->red2, $t->red3, $t->blue) === 3)->count();

        $payout1Each = $count1 > 0 ? (int) floor($pool1 / $count1) : 0;
        $payout2Each = $count2 > 0 ? (int) floor($pool2 / $count2) : 0;
        $payout3Each = $count3 > 0 ? (int) floor($pool3 / $count3) : 0;

        DB::transaction(function () use (
            $tickets, $issue, $prize4Fixed, $prize5Fixed, $fixedScale,
            $payout1Each, $payout2Each, $payout3Each,
            &$totalPayout, &$winner1Names, &$winner2Names, &$winner3Names,
            &$prize4Count, &$prize5Count
        ) {
            foreach ($tickets as $ticket) {
                $level = $issue->calcPrizeLevel($ticket->red1, $ticket->red2, $ticket->red3, $ticket->blue);
                $payout = 0;

                switch ($level) {
                    case 1:
                        $payout = $payout1Each;
                        $winner1Names[] = $ticket->user->username.'+'.\number_format($payout);
                        break;
                    case 2:
                        $payout = $payout2Each;
                        $winner2Names[] = $ticket->user->username.'+'.\number_format($payout);
                        break;
                    case 3:
                        $payout = $payout3Each;
                        $winner3Names[] = $ticket->user->username.'+'.\number_format($payout);
                        break;
                    case 4:
                        $payout = (int) round($prize4Fixed * $fixedScale);
                        $prize4Count++;
                        break;
                    case 5:
                        $payout = (int) round($prize5Fixed * $fixedScale);
                        $prize5Count++;
                        break;
                }

                $ticket->update(['prize_level' => $level, 'payout' => $payout]);

                if ($payout > 0) {
                    $totalPayout += $payout;
                    $this->currency->change(
                        user: $ticket->user,
                        currency: 'gold',
                        amount: $payout,
                        source: CurrencySource::LOTTERY_WIN,
                        remark: "双色球 #{$issue->issue_no} {$issue::prizeLevelLabel($level)}中奖，得 {$payout} 金币",
                    );
                }
            }
        });

        // ── 计算滚存金额 ──
        $usedPool = $totalPayout;
        $carryAmount = max(0, $poolAfterFixed - $usedPool + (int) round($poolAfterFixed * $carryRatio / 100));

        // 无人中一/二/三等时该部分奖池全部滚存
        $actualCarry = $poolAfterFixed - min($usedPool, $poolAfterFixed);

        // ── 更新期次记录 ──
        $noWinnerStreak = $count1 === 0 ? $issue->no_winner_streak + 1 : 0;

        $issue->update([
            'status' => 'settled',
            'payout_amount' => $totalPayout,
            'no_winner_streak' => $noWinnerStreak,
        ]);

        // ── 记录奖池派奖流水 ──
        LotteryPoolLog::create([
            'issue_id' => $issue->id,
            'change_amount' => -$totalPayout,
            'reason' => 'payout',
            'pool_after' => max(0, $poolAfterFixed - $totalPayout),
            'remark' => "开奖派奖，共 {$totalPayout} 金币",
            'created_at' => now(),
        ]);

        // ── 开启下一期（携带滚存）──
        $this->openNextIssue($issue, $actualCarry, $noWinnerStreak);

        // ── 公屏广播 ──
        $this->broadcastResult($issue, $count1, $count2, $count3, $prize4Count, $prize5Count, $winner1Names);
    }

    /**
     * 开启新一期，携带上期滚存金额。
     *
     * @param  LotteryIssue  $prevIssue  上一期
     * @param  int  $carryAmount  滚存金额
     * @param  int  $noWinnerStreak  连续无一等奖期数
     */
    private function openNextIssue(LotteryIssue $prevIssue, int $carryAmount, int $noWinnerStreak): void
    {
        $config = GameConfig::forGame('lottery')?->params ?? [];
        $drawHour = (int) ($config['draw_hour'] ?? 20);
        $drawMinute = (int) ($config['draw_minute'] ?? 0);
        $stopMinutes = (int) ($config['stop_sell_minutes'] ?? 2);

        // 下一期开奖时间（明天同一时间）
        $drawAt = now()->addDay()->setTime($drawHour, $drawMinute, 0);
        $closeAt = $drawAt->copy()->subMinutes($stopMinutes);

        // 判断是否超级期
        $superThreshold = (int) ($config['super_issue_threshold'] ?? 3);
        $isSuper = $noWinnerStreak >= $superThreshold;
        $injectAmount = 0;

        if ($isSuper) {
            $injectAmount = (int) ($config['super_issue_inject'] ?? 20000);
        }

        $newIssue = LotteryIssue::create([
            'issue_no' => LotteryIssue::nextIssueNo(),
            'status' => 'open',
            'pool_amount' => $carryAmount + $injectAmount,
            'carry_amount' => $carryAmount,
            'is_super_issue' => $isSuper,
            'no_winner_streak' => $noWinnerStreak,
            'sell_closes_at' => $closeAt,
            'draw_at' => $drawAt,
        ]);

        // 记录滚存流水
        if ($carryAmount > 0) {
            LotteryPoolLog::create([
                'issue_id' => $newIssue->id,
                'change_amount' => $carryAmount,
                'reason' => 'carry_over',
                'pool_after' => $newIssue->pool_amount,
                'remark' => "从第 #{$prevIssue->issue_no} 期滚存",
                'created_at' => now(),
            ]);
        }

        // 记录系统注入流水
        if ($injectAmount > 0) {
            LotteryPoolLog::create([
                'issue_id' => $newIssue->id,
                'change_amount' => $injectAmount,
                'reason' => 'system_inject',
                'pool_after' => $newIssue->pool_amount,
                'remark' => "超级期系统注入（连续 {$noWinnerStreak} 期无一等奖）",
                'created_at' => now(),
            ]);
        }

        // 超级期全服预热广播
        if ($isSuper) {
            $this->broadcastSuperIssue($newIssue);
        }
    }

    /**
     * 向公屏广播开奖结果。
     *
     * @param  array<string>  $winner1Names  一等奖得主列表
     */
    private function broadcastResult(
        LotteryIssue $issue,
        int $count1, int $count2, int $count3,
        int $prize4Count, int $prize5Count,
        array $winner1Names
    ): void {
        $drawNums = sprintf(
            '🔴%02d 🔴%02d 🔴%02d + 🎲%d',
            $issue->red1, $issue->red2, $issue->red3, $issue->blue
        );

        // 一等奖
        if ($count1 > 0) {
            $w1Str = implode('、', array_slice($winner1Names, 0, 5));
            $line1 = "🏆 一等奖：{$w1Str} 💰";
        } else {
            $line1 = '无一等奖！奖池滚存 → 下期累计 💰 '.number_format($issue->pool_amount).' 金币';
        }

        $details = [];
        if ($count2 > 0) {
            $details[] = "🥇 二等奖：{$count2}人";
        }
        if ($count3 > 0) {
            $details[] = "🥈 三等奖：{$count3}人";
        }
        if ($prize4Count > 0) {
            $details[] = "🥉 四等奖：{$prize4Count}人";
        }
        if ($prize5Count > 0) {
            $details[] = "🎫 五等奖：{$prize5Count}人";
        }

        $detailStr = $details ? '  '.implode(' | ', $details) : '';

        $content = "🎟️ 【双色球 第{$issue->issue_no}期 开奖】{$drawNums}  {$line1}{$detailStr}";

        $this->pushSystemMessage($content);

        // 触发微信机器人消息推送 (彩票开奖)
        try {
            $wechatService = app(\App\Services\WechatBot\WechatNotificationService::class);
            $wechatService->notifyLotteryResult($content);
        } catch (\Exception $e) {
            \Illuminate\Support\Facades\Log::error('WechatBot lottery notification failed', ['error' => $e->getMessage()]);
        }
    }

    /**
     * 超级期预热广播。
     */
    private function broadcastSuperIssue(LotteryIssue $issue): void
    {
        $pool = number_format($issue->pool_amount);
        $content = "🎊🎟️ 【双色球超级期预警】第 {$issue->issue_no} 期已连续 {$issue->no_winner_streak} 期无一等奖！"
            ."当前奖池 💰 {$pool} 金币，系统已追加注入！今日 {$issue->draw_at?->format('H:i')} 开奖，赶紧购票！";

        $this->pushSystemMessage($content);
    }

    /**
     * 向公屏发送系统消息。
     */
    private function pushSystemMessage(string $content): void
    {
        $msg = [
            'id' => $this->chatState->nextMessageId(1),
            'room_id' => 1,
            'from_user' => '系统传音',
            'to_user' => '大家',
            'content' => $content,
            'is_secret' => false,
            'font_color' => '#dc2626',
            'action' => '大声宣告',
            'sent_at' => now()->toDateTimeString(),
        ];
        $this->chatState->pushMessage(1, $msg);
        broadcast(new MessageSent(1, $msg));
        \App\Jobs\SaveMessageJob::dispatch($msg);
    }
}
