<?php

namespace App\Services;

use App\Facade\TableName;
use App\Game\Services\OuroGameService;
use App\Http\helper\NumConfig;
use App\Models\PrivateMail;
use Carbon\Carbon;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use App\Services\VipService;

/**
 * Superball Activity: recharge + turnover task, balls, lucky number, prize pool.
 * All amounts in internal units (NUM_VALUE) where needed for DB/score.
 */
class SuperballActivityService
{
    public const TIER_MAX = 'SSS';
    public const MULTIPLIER_MIN = 1.0;
    public const MULTIPLIER_MAX = 3.0;
    public const MULTIPLIER_STEP = 0.5;
    public const LUCKY_REWARD_PER_BALL = 10; // display unit per matching ball

    /**
     * Get full activity info: yesterday data, today data, tiers, user task, multiplier.
     */
    public function getInfo(int $userId): array
    {
        $today = Carbon::today()->format('Y-m-d');
        $yesterday = Carbon::yesterday()->format('Y-m-d');

        $tiers = $this->getTierConfig();
        $yesterdayDaily = $this->getOrCreateDaily($yesterday);
        $todayDaily = $this->getOrCreateDaily($today);
        $userTask = $this->getUserTask($userId, $today);
        $multiplierRow = $this->getUserMultiplier($userId);

        // VIP 等级与每日免费球数（赠送球数 = VIP 等级）
        $vipLevel = $this->getUserVipLevel($userId);
        $level = VipService::getVipByField('VIP', $vipLevel);
        $vipFreeBalls = $level ? ($level->SuperballNum ?? 0) : 0;
        if ($vipFreeBalls == 0 && $userTask === null) {
            try {
                $this->selectTier($userId, 'E');
            } catch (QueryException $e) {
                if (stripos($e->getMessage(), 'duplicate key') === false) {
                    throw $e;
                }
            }
            $userTask = $this->getUserTask($userId, $today);
        }

        $rechargeToday = $this->getUserRechargeForDate($userId, $today);
        $turnoverToday = $this->getUserTotalBetForDate($userId, $today);
        $turnoverProgress = (int)$turnoverToday;

        $tierConfig = $userTask ? $this->getTierConfigByTier($userTask->tier) : null;
        $rechargeRequired = $tierConfig ? (int)$tierConfig->recharge_required : 0;
        $turnoverRequired = $tierConfig ? (int)$tierConfig->turnover_required : 0;
        $rechargeDisplay = $rechargeToday;
        $turnoverDisplay = $turnoverProgress / NumConfig::NUM_VALUE;
        $taskCompleted = $tierConfig && $rechargeDisplay >= $rechargeRequired && $turnoverDisplay >= $turnoverRequired;
        if ($userTask && $userTask->status == 1) {
            $taskCompleted = $userTask->complete;
        }
        $canClaim = $taskCompleted && $userTask && (int)$userTask->status == 0;
        $canUpgrade = $userTask && $userTask->tier !== self::TIER_MAX && $taskCompleted;
        // 能升级自动升
        if ($canUpgrade && $userTask->status != 1) {
            $tierConfigs = $this->getTierConfig();
            $up = null;
            foreach (array_reverse($tierConfigs) as $c) {
                // 已经是最大的档位，直接升级
                if ($c['tier'] == self::TIER_MAX
                    && $c['recharge_required'] < $rechargeToday && $c['turnover_required'] < $turnoverDisplay) {
                    $up = $c;
                    break;
                }
                if ($c['recharge_required'] < $rechargeToday && $c['turnover_required'] < $turnoverDisplay) {
                    continue;
                }
                $up = $c;
                break;
            }
            $res = $this->upgradeTier($userId, $up['tier']);
            if ($res['success']) {
                $tierConfig = $this->getTierConfigByTier($res['new_tier']);
                $userTask = $this->getUserTask($userId, $today);
                $rechargeRequired = $tierConfig ? (int)$tierConfig->recharge_required : 0;
                $turnoverRequired = $tierConfig ? (int)$tierConfig->turnover_required : 0;
                $taskCompleted = $tierConfig && $rechargeDisplay >= $rechargeRequired && $turnoverDisplay >= $turnoverRequired;
                $canClaim = $taskCompleted && $userTask && (int)$userTask->status === 0;
            }
        }
        $rechargeDisplay = min($rechargeDisplay, $tierConfig->recharge_required ?? 0);
        $turnoverDisplay = min($turnoverDisplay, $tierConfig->turnover_required ?? 0);

        $yesterdayBalls = $this->getUserBalls($userId, $yesterday);
        $yesterdayLucky = (int)($yesterdayDaily->lucky_number ?? 0);
        $yesterdayPrizeLog = $this->getUserPrizeLog($userId, $yesterday);
        $yesterdayBasePerBall = 0;
        if ($yesterdayDaily->total_balls > 0 && $yesterdayDaily->pool_amount > 0) {
            $yesterdayBasePerBall = (int)($yesterdayDaily->pool_amount / $yesterdayDaily->total_balls);
        }
        $yesterdayMyPrize = $yesterdayPrizeLog ? (int)$yesterdayPrizeLog->total_amount : 0;
        $yesterdayMultiplier = $yesterdayPrizeLog ? (float)$yesterdayPrizeLog->multiplier : 1.0;
        $canClaimYesterday = false;
        $yesterdayPendingPrize = 0;
        if (!$yesterdayPrizeLog && count($yesterdayBalls) > 0) {
            $canClaimYesterday = true;
            $yesterdayPendingPrize = $this->calculateYesterdayPrizeForUser($userId, $yesterday);
        }
        if (count($yesterdayBalls) > 0) {
            $daily = DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $yesterday)->first();
            $hitCount = $baseAmount = 0;
            if ($daily) {
                $basePerBall = (int)($daily->pool_amount / $daily->total_balls);
                $hitCount = count(array_filter($yesterdayBalls, function ($item) use ($yesterdayLucky) {
                    return $item->number == $yesterdayLucky;
                }));
            }
            $multiplier = DB::table('agent.dbo.superball_user_multiplier')->where('user_id', $userId)
                ->value('multiplier') ?: 1.0;
            self::sendOpenRewardMailOnce($userId, count($yesterdayBalls), $yesterdayLucky, $multiplier, $hitCount, $basePerBall/NumConfig::NUM_VALUE);
        }
        // 用户昨日未领取球
        $yesterdayNotClaimedBallCount = $this->getNotClaimBallByUserIdDate($userId, $yesterday);

        $todayBasePerBall = 0;
        if ($todayDaily->total_balls > 0 && $todayDaily->pool_amount > 0) {
            $todayBasePerBall = (int)($todayDaily->pool_amount / $todayDaily->total_balls);
        }

        $todayBalls = $this->getUserBalls($userId, $today);
        $todayMyBallsList = array_map(function ($b) {
            return ['ball_index' => (int)$b->ball_index, 'number' => (int)$b->number];
        }, $todayBalls);
        $todayNumberCounts = [];
        foreach ($todayBalls as $b) {
            $n = (int)$b->number;
            $todayNumberCounts[$n] = ($todayNumberCounts[$n] ?? 0) + 1;
        }

        // 0 点到 1 点前，前端展示的今日整体数据全部置为 0（不影响实际统计与任务进度）
        $hour = (int)Carbon::now()->format('G'); // 0-23
        if ($hour < 1) {
            $todayDisplayPoolAmount = 0;
            $todayDisplayCompleted = 0;
            $todayDisplayTotalBalls = 0;
            $todayDisplayBasePerBall = 0;
            $todayDisplayMyBalls = $todayMyBallsList;
            $todayDisplayNumberCounts = $todayNumberCounts;
        } else {
            $todayDisplayPoolAmount = (int)$todayDaily->pool_amount;
            $todayDisplayCompleted = (int)($todayDaily->completed_count ?? 0);
            $todayDisplayTotalBalls = (int)($todayDaily->total_balls ?? 0);
            $todayDisplayBasePerBall = $todayBasePerBall;
            $todayDisplayMyBalls = $todayMyBallsList;
            $todayDisplayNumberCounts = $todayNumberCounts;
        }

        $last7Lucky = $this->getLast7DaysLuckyNumbersPrivate();

        $data = [
            'yesterday' => [
                'pool_amount' => (int)$yesterdayDaily->pool_amount,
                'pool_amount_display' => (int)$yesterdayDaily->pool_amount / NumConfig::NUM_VALUE,
                'base_reward_per_ball' => $yesterdayBasePerBall,
                'base_reward_per_ball_display' => $yesterdayBasePerBall / NumConfig::NUM_VALUE,
                'lucky_number' => $yesterdayLucky,
                'completed_count' => (int)($yesterdayDaily->completed_count ?? 0),
                'total_balls' => (int)($yesterdayDaily->total_balls ?? 0),
                'my_balls' => $yesterdayBalls,
                'my_balls_with_lucky' => array_map(function ($b) use ($yesterdayLucky) {
                    return ['number' => (int)$b->number, 'is_lucky' => (int)$b->number === $yesterdayLucky];
                }, $yesterdayBalls),
                'my_prize' => $yesterdayMyPrize,
                'my_prize_display' => $yesterdayMyPrize / NumConfig::NUM_VALUE,
                'my_multiplier' => $yesterdayMultiplier,
                'can_claim_yesterday' => $canClaimYesterday,
                'pending_prize' => $yesterdayPendingPrize,
                'pending_prize_display' => $yesterdayPendingPrize / NumConfig::NUM_VALUE,
                'yesterday_not_claimed_ball_count' => $yesterdayNotClaimedBallCount,
            ],
            'today' => [
                'pool_amount' => $todayDisplayPoolAmount,
                'pool_amount_display' => $todayDisplayPoolAmount / NumConfig::NUM_VALUE,
                'completed_count' => $todayDisplayCompleted,
                'total_balls' => $todayDisplayTotalBalls,
                'base_reward_per_ball' => $todayDisplayBasePerBall,
                'base_reward_per_ball_display' => $todayDisplayBasePerBall / NumConfig::NUM_VALUE,
                'my_balls' => $todayDisplayMyBalls,
                'number_counts' => $todayDisplayNumberCounts,
            ],
            'tiers' => $tiers,
            'user_task' => $userTask ? [
                'tier' => $userTask->tier,
                'recharge_required' => $rechargeRequired,
                'turnover_required' => $turnoverRequired,
                'recharge_progress' => $rechargeDisplay,
                'turnover_progress' => $turnoverDisplay,
                'task_completed' => (bool) $taskCompleted,
                'status' => (int)$userTask->status,
                'can_claim' => $canClaim,
                'can_upgrade' => $canUpgrade,
                'ball_count' => $tierConfig ? (int)$tierConfig->ball_count : 0,
            ] : null,
            'multiplier' => [
                'value' => (float)($multiplierRow->multiplier ?? 1.0),
                'consecutive_days' => (int)($multiplierRow->consecutive_days ?? 0),
                'min' => self::MULTIPLIER_MIN,
                'max' => self::MULTIPLIER_MAX,
                'step' => self::MULTIPLIER_STEP,
            ],
            'vip' => [
                'level' => $vipLevel,
                'daily_free_balls' => $vipFreeBalls,
            ],
            'lucky_reward_per_ball' => self::LUCKY_REWARD_PER_BALL,
            'lucky_numbers_7_days' => $last7Lucky,
            'can_sumbit' => array_sum(array_keys($todayNumberCounts)) < $vipFreeBalls + ($taskCompleted ? $tierConfig->ball_count : 0)
        ];

        return $data;
    }

    /**
     * 获取用户 VIP 等级（用于每日赠送球数计算）
     */
    protected function getUserVipLevel(int $userId): int
    {
        if ($userId <= 0) {
            return 0;
        }

        // 从 YN_VIPAccount 读取累计充值金额，交由 VipService 计算 VIP 等级
        $userRecharge = (int)DB::table('QPAccountsDB.dbo.YN_VIPAccount')
            ->where('UserID', $userId)
            ->value('Recharge');

        return (int)VipService::calculateVipLevel($userId, $userRecharge);
    }

    /**
     * Select task tier for today (with optional confirm). Creates or updates user task.
     * @return array success: ['success' => true] | failure: ['success' => false, 'message' => ['key', 'fallback']]
     */
    public function selectTier(int $userId, string $tier): array
    {
        $today = Carbon::today()->format('Y-m-d');
        $config = $this->getTierConfigByTier($tier);
        if (!$config) {
            return ['success' => false, 'message' => ['web.superball.activity_not_found', 'Activity not found']];
        }

        $existing = $this->getUserTask($userId, $today);
        if ($existing) {
            return ['success' => false, 'message' => ['web.superball.tier_already_selected', 'Already selected tier for today']];
        }

        DB::connection('write')->table(TableName::agent() . 'superball_user_task')->insert([
            'user_id' => $userId,
            'task_date' => $today,
            'tier' => $tier,
            'total_bet_snapshot' => 0,
            'status' => 0,
            'created_at' => now()->format('Y-m-d H:i:s'),
            'updated_at' => now()->format('Y-m-d H:i:s'),
        ]);
        return ['success' => true];
    }

    /**
     * Upgrade to higher tier (keep progress). Only allowed when task completed and not A.
     * @return array success: ['success' => true] | failure: ['success' => false, 'message' => [...]]
     */
    public function upgradeTier(int $userId, string $newTier): array
    {
        $today = Carbon::today()->format('Y-m-d');
        $task = $this->getUserTask($userId, $today);
        if (!$task) {
            return ['success' => false, 'message' => ['web.superball.no_task_today', 'No task for today']];
        }
        if ((int)$task->status === 1) {
            return ['success' => false, 'message' => ['web.superball.already_claimed', 'Already claimed']];
        }

        $tierOrder = ['E' => 1, 'D' => 2, 'C' => 3, 'B' => 4, 'A' => 5, 'S' => 6, 'SS' => 7, 'SSS' => 8];
        $currentOrder = $tierOrder[$task->tier] ?? 0;
        $newOrder = $tierOrder[$newTier] ?? 0;
        if ($newOrder <= $currentOrder) {
            return ['success' => false, 'message' => ['web.superball.cannot_downgrade', 'Can only upgrade to higher tier']];
        }

        $rechargeToday = $this->getUserRechargeForDate($userId, $today);
        $turnoverToday = $this->getUserTotalBetForDate($userId, $today);
        $newConfig = $this->getTierConfigByTier($task->tier);
        $rechargeOk = $rechargeToday >= (int)$newConfig->recharge_required;
        $turnoverOk = ($turnoverToday / NumConfig::NUM_VALUE) >= (int)$newConfig->turnover_required;

//        var_dump($rechargeToday,$turnoverToday,$newConfig);
        if (!$rechargeOk || !$turnoverOk) {
            return ['success' => false, 'message' => ['web.superball.task_not_completed', 'Task not completed']];
        }

        DB::connection('write')->table(TableName::agent() . 'superball_user_task')
            ->where('user_id', $userId)
            ->where('task_date', $today)
            ->update(['tier' => $newTier, 'updated_at' => now()->format('Y-m-d H:i:s')]);
        return ['success' => true, 'user_id' => $userId, 'new_tier' => $newTier, 'task_date' => $today];
    }

    /**
     * Claim reward: grant balls, update daily total_balls/completed_count, update multiplier, mark task claimed.
     * @return array success: ['ball_count' => n, 'message' => ...] | failure: ['success' => false, 'message' => [...]]
     */
    public function claimReward(int $userId): array
    {
        $today = Carbon::today()->format('Y-m-d');
        $task = $this->getUserTask($userId, $today);
        $vipLevel = $this->getUserVipLevel($userId);
        $level = VipService::getVipByField('VIP', $vipLevel);
        $vipFreeBalls = $level ? ($level->SuperballNum ?? 0) : 0;
        if (!$task) {
            // 昨日未领取奖励
            $yesterday = Carbon::yesterday()->format('Y-m-d');
            $yesterdayBallCount = $this->getNotClaimBallByUserIdDate($userId, $yesterday);
            if ($yesterdayBallCount > 0) {
                $vipFreeBalls += $yesterdayBallCount;
            }
            $this->claimIfHasVipReward($userId, $vipFreeBalls);
            $this->selectTier($userId, 'E');
            return [
                'ball_count' => $vipFreeBalls,
                'base_ball_count' => 0,
                'vip_free_balls' => $vipFreeBalls,
                'message' => 'Claim success, please select numbers for your balls',
            ];
        }
        if ((int)$task->status === 1) {
            return ['success' => false, 'message' => ['web.superball.already_claimed', 'Already claimed']];
        }

        $tierConfig = $this->getTierConfigByTier($task->tier);
        if (!$tierConfig) {
            return ['success' => false, 'message' => ['web.superball.activity_not_found', 'Activity not found']];
        }
        $rechargeToday = $this->getUserRechargeForDate($userId, $today);
        $turnoverToday = $this->getUserTotalBetForDate($userId, $today);
        $rechargeOk = ($rechargeToday) >= (int)$tierConfig->recharge_required;
        $turnoverOk = ($turnoverToday / NumConfig::NUM_VALUE) >= (int)$tierConfig->turnover_required;
        $complete = 1;
        if (!$rechargeOk || !$turnoverOk) {
            // 本档没完成
            $complete = 0;
        }
        // 所有任务累加
        $ballCount = 0;
        if ($complete) {
            $ballCount = $tierConfig->ball_count;
        } else if ($tierConfig->tier !== 'E') { // 没完成获取上一级奖励
            $idx = $tierConfig->sort_index + 1;
            $configs = $this->getTierConfig();
            foreach ($configs as $config) {
                if ($config['sort_index'] == $idx) {
                    $ballCount += $config['ball_count'];
                }
            }
        }

        if ($ballCount < 1) {
            return ['success' => false, 'message' => ['web.superball.task_not_completed', 'Task not completed']];
        }

        DB::connection('write')->transaction(function () use ($userId, $today, $task, $ballCount, $complete) {
            DB::connection('write')->table(TableName::agent() . 'superball_user_task')
                ->where('user_id', $userId)
                ->where('task_date', $today)
                ->update(['status' => 1, 'complete' => $complete, 'updated_at' => now()->format('Y-m-d H:i:s')]);
            $this->getOrCreateDaily($today);
            DB::connection('write')->table(TableName::agent() . 'superball_daily')
                ->where('pool_date', $today)
                ->update([
                    'total_balls' => DB::raw('total_balls+' . $ballCount),
                    'completed_count' => DB::raw('completed_count+1'),
                    'updated_at' => now()->format('Y-m-d H:i:s'),
                ]);

            $this->updateUserMultiplier($userId, $today);
        });

        return [
            'ball_count' => $ballCount,
            'base_ball_count' => $ballCount,
            'vip_free_balls' => $vipFreeBalls,
            'message' => 'Claim success, please select numbers for your balls',
        ];
    }

    public function claimIfHasVipReward(int $userId, $vipFreeBalls): bool
    {
        $key = sprintf('claim_vip_reward_%s_%s', $userId, date('Ymd'));
        if (Redis::exists($key)) {
            return false;
        }
        $today = Carbon::today()->format('Y-m-d');
        $this->getOrCreateDaily($today);
        DB::connection('write')->table(TableName::agent() . 'superball_daily')
            ->where('pool_date', $today)
            ->update([
                'total_balls' => DB::raw('total_balls+' . $vipFreeBalls),
                'updated_at' => now()->format('Y-m-d H:i:s'),
            ]);
        Redis::set($key, $vipFreeBalls);
        Redis::expire($key, 86400);
        return true;
    }

    /**
     * Submit numbers for all balls (0-9 per ball). Must have claimed and not yet submitted.
     * @return array success: ['success' => true] | failure: ['success' => false, 'message' => [...]]
     */
    public function submitNumbers(int $userId, array $numbers): array
    {
        $key = sprintf('claim_vip_reward_%s_%s', $userId, date('Ymd'));
        $vipFreeBalls = (int)Redis::get($key);
        $vipLevel = $this->getUserVipLevel($userId);
        $level = VipService::getVipByField('VIP', $vipLevel);
        $vipFreeBalls2 = $level ? ($level->SuperballNum ?? 0) : 0;
        $vipFreeBalls = max($vipFreeBalls, $vipFreeBalls2);
        $today = Carbon::today()->format('Y-m-d');
        $task = $this->getUserTask($userId, $today);

        $ballCount = 0;
        $tierConfig = $this->getTierConfigByTier($task->tier);
        if ($task && $task->status == 1) {
            if ($task->complete == 1) {
                $ballCount = $tierConfig->ball_count;
            } else if ($tierConfig->tier !== 'E') { // 没完成获取上一级奖励
                $idx = $tierConfig->sort_index + 1;
                $configs = $this->getTierConfig();
                foreach ($configs as $config) {
                    if ($config['sort_index'] == $idx) {
                        $ballCount += $config['ball_count'];
                    }
                }
            }
        }

        $existing = DB::connection('write')->table(TableName::agent() . 'superball_user_balls')
            ->where('user_id', $userId)
            ->where('ball_date', $today)
            ->count();
        // 与领取时保持一致：基础任务球数 + 每日 VIP 免费球数
        $ballCount = $ballCount + $vipFreeBalls;
        if ($ballCount < 1) {
            return ['success' => false, 'message' => ['web.superball.number_count_mismatch', 'no ball']];
        }
        if (count($numbers) + $existing > $ballCount) {
            $remain = $ballCount - $existing;
            if ($remain < 1) {
                return ['success' => false, 'message' => ['web.superball.number_count_mismatch', 'Number count mismatch']];
            }
            $numbers = array_slice($numbers, 0, $remain);
        }

        DB::connection('write')->transaction(function () use ($userId, $today, $numbers) {
            // 写入用户今日所有球号
            foreach ($numbers as $index => $num) {
                $n = (int)$num;
                if ($n < 0 || $n > 9) {
                    throw new \RuntimeException('Number must be 0-9');
                }
                DB::connection('write')->table(TableName::agent() . 'superball_user_balls')->insert([
                    'user_id' => $userId,
                    'ball_date' => $today,
                    'ball_index' => $index + 1,
                    'number' => $n,
                    'created_at' => now()->format('Y-m-d H:i:s'),
                ]);
            }

            // 若用户选择的号码等于当日 lucky_number，则把 superball_daily.lucky_count 累加
            $daily = DB::connection('write')
                ->table(TableName::agent() . 'superball_daily')
                ->where('pool_date', $today)
                ->first();

            if ($daily) {
                $luckyNumber = (int)$daily->lucky_number;
                $matched = 0;
                foreach ($numbers as $num) {
                    if ((int)$num === $luckyNumber) {
                        $matched++;
                    }
                }
                if ($matched > 0) {
                    DB::connection('write')
                        ->table(TableName::agent() . 'superball_daily')
                        ->where('pool_date', $today)
                        ->update([
                            'lucky_count' => DB::raw("lucky_count + {$matched}"),
                            'updated_at' => now()->format('Y-m-d H:i:s'),
                        ]);
                }
            }
        });

        return ['success' => true];
    }

    /**
     * Get user's balls for a date (for number selection page or display).
     */
    public function getMyBalls(int $userId, string $date): array
    {
        $rows = DB::table(TableName::agent() . 'superball_user_balls')
            ->where('user_id', $userId)
            ->where('ball_date', $date)
            ->orderBy('ball_index')
            ->get();
        return array_map(function ($r) {
            return ['ball_index' => (int)$r->ball_index, 'number' => (int)$r->number];
        }, $rows->all());
    }

    /**
     * User claims yesterday's reward (next-day claim, no auto distribution).
     * Formula: base = pool / total_balls * ball_count, lucky = 10 * matched_balls (display), total = base * multiplier + lucky.
     * @return array success: data with total_amount etc. | failure: ['success' => false, 'message' => [...]]
     */
    public function claimYesterdayReward(int $userId): array
    {
        $yesterday = Carbon::yesterday()->format('Y-m-d');
        $existing = $this->getUserPrizeLog($userId, $yesterday);
        if ($existing) {
            return ['success' => false, 'message' => ['web.superball.yesterday_already_claimed', 'Yesterday reward already claimed']];
        }

        $balls = $this->getUserBalls($userId, $yesterday);
        if (count($balls) === 0) {
            return ['success' => false, 'message' => ['web.superball.no_balls_yesterday', 'No balls for yesterday, nothing to claim']];
        }

        $daily = DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $yesterday)->first();
        if (!$daily || (int)$daily->total_balls <= 0) {
            return ['success' => false, 'message' => ['web.superball.pool_not_ready', 'Yesterday pool not ready']];
        }

        $basePerBall = (int)($daily->pool_amount / $daily->total_balls);
        $luckyNumber = (int)$daily->lucky_number;
        $multiplierRow = $this->getUserMultiplier($userId);
        $multiplier = (float)($multiplierRow->multiplier ?? 1.0);

        // 选号可重复：每个球单独比对幸运号，中奖球数 = 号码等于幸运号的球个数（同一号码可多球）
        $matched = 0;
        foreach ($balls as $b) {
            if ((int)$b->number === $luckyNumber) {
                $matched++;
            }
        }
        $baseAmount = $basePerBall * count($balls);
        $luckyAmountDisplay = self::LUCKY_REWARD_PER_BALL * $matched;
        $luckyAmountInternal = $luckyAmountDisplay * NumConfig::NUM_VALUE;
        $totalAmount = (int)round($baseAmount * $multiplier) + $luckyAmountInternal;

        DB::connection('write')->table(TableName::agent() . 'superball_prize_log')->insert([
            'user_id' => $userId,
            'settle_date' => $yesterday,
            'base_amount' => $baseAmount,
            'lucky_amount' => $luckyAmountInternal,
            'multiplier' => $multiplier,
            'total_amount' => $totalAmount,
            'created_at' => now()->format('Y-m-d H:i:s'),
        ]);
        OuroGameService::AddScore($userId, $totalAmount, 90);

        return [
            'total_amount' => $totalAmount,
            'total_amount_display' => $totalAmount / NumConfig::NUM_VALUE,
            'base_amount' => $baseAmount,
            'lucky_amount' => $luckyAmountInternal,
            'multiplier' => $multiplier,
        ];
    }

    /**
     * Calculate yesterday prize for user (for display only, no claim).
     */
    public function calculateYesterdayPrizeForUser(int $userId, string $yesterday): int
    {
        $daily = DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $yesterday)->first();
        if (!$daily || (int)$daily->total_balls <= 0) {
            return 0;
        }
        $balls = $this->getUserBalls($userId, $yesterday);
        if (count($balls) === 0) {
            return 0;
        }
        $basePerBall = (int)($daily->pool_amount / $daily->total_balls);
        $luckyNumber = (int)$daily->lucky_number;
        $multiplierRow = $this->getUserMultiplier($userId);
        $multiplier = (float)($multiplierRow->multiplier ?? 1.0);
        // 选号可重复：按球逐个比对幸运号，中奖球数 = 号码等于幸运号的球个数
        $matched = 0;
        foreach ($balls as $b) {
            if ((int)$b->number === $luckyNumber) {
                $matched++;
            }
        }
        $baseAmount = $basePerBall * count($balls);
        $luckyAmountInternal = self::LUCKY_REWARD_PER_BALL * $matched * NumConfig::NUM_VALUE;
        return (int)round($baseAmount * $multiplier) + $luckyAmountInternal;
    }

    /**
     * Get last 7 days lucky numbers (for display).
     */
    public function getLast7DaysLuckyNumbers(): array
    {
        return $this->getLast7DaysLuckyNumbersPrivate();
    }

    /**
     * Ensure daily row and lucky number for date (idempotent).
     */
    public function getOrCreateDaily(string $date): \stdClass
    {
        $row = DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $date)->first();
        if ($row) {
            return $row;
        }
        $lucky = mt_rand(0, 9);
        try {
            DB::connection('write')->table(TableName::agent() . 'superball_daily')->insert([
                'pool_date' => $date,
                'pool_amount' => 0,
                'total_balls' => 0,
                'lucky_number' => $lucky,
                'completed_count' => 0,
                'lucky_count' => 0,
                'created_at' => now()->format('Y-m-d H:i:s'),
                'updated_at' => now()->format('Y-m-d H:i:s'),
            ]);
        } catch (QueryException $e) {
            // Concurrent getOrCreateDaily: unique index IX_superball_daily_date (pool_date)
            if (stripos($e->getMessage(), 'duplicate key') === false) {
                throw $e;
            }
        }
        return DB::table(TableName::agent() . 'superball_daily')->where('pool_date', $date)->first();
    }

    /**
     * Update pool amount for a date (call from job that aggregates daily turnover).
     */
    public function updatePoolAmount(string $date, int $poolAmountInternal): void
    {
        DB::connection('write')->table(TableName::agent() . 'superball_daily')
            ->where('pool_date', $date)
            ->update(['pool_amount' => $poolAmountInternal, 'updated_at' => now()->format('Y-m-d H:i:s')]);
    }

    /**
     * 获取某天未领取的球数
     * @param $userId
     * @param $date
     * @return int
     */
    public function getNotClaimBallByUserIdDate($userId, $date): int
    {
        $cacheKey = sprintf('superball_yesterday_not_claim_%d_%s', $userId, $date);
        if (Redis::exists($cacheKey)) {
            $ball = Redis::get($cacheKey);
            return (int)$ball;
        }
        $ball = 0;
        $task = $this->getUserTask($userId, $date);
        if ($task && $task->status == 0) {
            $recharge = $this->getUserRechargeForDate($userId, $date);
            $turnover = $this->getUserTotalBetForDate($userId, $date);
            $config = $this->getTierConfigByTier($task->tier);
            if ($recharge >= $config->recharge_required && $turnover / NumConfig::NUM_VALUE >= $config->turnover_required) {
                $ball = $config->ball_count;
            }
        }
        Redis::set($cacheKey, $ball);
        Redis::expireAt($cacheKey, strtotime('today +1 day'));

        return $ball;
    }

    // --- private helpers ---

    private function getTierConfig(): array
    {
        $rows = DB::table(TableName::agent() . 'superball_tier_config')
            ->orderBy('sort_index')
            ->get();
        return array_map(function ($r) {
            return [
                'sort_index' => $r->sort_index,
                'tier' => $r->tier,
                'recharge_required' => (int)$r->recharge_required,
                'turnover_required' => (int)$r->turnover_required,
                'ball_count' => (int)$r->ball_count,
            ];
        }, $rows->all());
    }

    private function getTierConfigByTier(string $tier): ?\stdClass
    {
        return DB::table(TableName::agent() . 'superball_tier_config')->where('tier', $tier)->first();
    }

    private function getUserTask(int $userId, string $date): ?\stdClass
    {
        return DB::table(TableName::agent() . 'superball_user_task')
            ->where('user_id', $userId)
            ->where('task_date', $date)
            ->first();
    }

    private function getUserRechargeForDate(int $userId, string $date): int
    {
        $dateId = str_replace('-', '', $date);
        $row = DB::table(TableName::QPRecordDB() . 'RecordUserDataStatisticsNew')
            ->where('UserID', $userId)
            ->where('DateID', $dateId)
            ->first();
        return $row ? (int)$row->Recharge : 0;
    }

    /** 当日流水：从按日统计表取当天 TotalBet（内部单位） */
    private function getUserTotalBetForDate(int $userId, string $date): int
    {
        $dateId = str_replace('-', '', $date);
        $row = DB::table(TableName::QPRecordDB() . 'RecordUserDataStatisticsNew')
            ->where('UserID', $userId)
            ->where('DateID', $dateId)
            ->first();
        return $row && isset($row->TotalBet) ? (int)$row->TotalBet : 0;
    }

    private function getUserTotalBet(int $userId): int
    {
        $row = DB::table(TableName::QPRecordDB() . 'RecordUserTotalStatistics')
            ->where('UserID', $userId)
            ->first();
        return $row ? (int)$row->TotalBet : 0;
    }

    private function getUserBalls(int $userId, string $date): array
    {
        return DB::table(TableName::agent() . 'superball_user_balls')
            ->where('user_id', $userId)
            ->where('ball_date', $date)
            ->orderBy('ball_index')
            ->get()
            ->all();
    }

    private function getUserMultiplier(int $userId): ?\stdClass
    {
        return DB::table(TableName::agent() . 'superball_user_multiplier')->where('user_id', $userId)->first();
    }

    private function getUserPrizeLog(int $userId, string $date): ?\stdClass
    {
        return DB::table(TableName::agent() . 'superball_prize_log')
            ->where('user_id', $userId)
            ->where('settle_date', $date)
            ->first();
    }

    private function getLast7DaysLuckyNumbersPrivate(): array
    {
        $dates = [];
        for ($i = 0; $i < 7; $i++) {
            $dates[] = Carbon::today()->subDays($i)->format('Y-m-d');
        }
        $rows = DB::table(TableName::agent() . 'superball_daily')
            ->whereIn('pool_date', $dates)
            ->orderBy('pool_date', 'desc')
            ->get();
        $map = [];
        foreach ($rows as $r) {
            $map[$r->pool_date] = (int)$r->lucky_number;
        }
        return array_map(function ($d) use ($map) {
            return ['date' => $d, 'lucky_number' => $map[$d] ?? null];
        }, $dates);
    }

    private function updateUserMultiplier(int $userId, string $taskDate): void
    {
        $row = DB::connection('write')->table(TableName::agent() . 'superball_user_multiplier')
            ->where('user_id', $userId)
            ->lockForUpdate()
            ->first();

        $multiplier = self::MULTIPLIER_MIN;
        $consecutive = 1;

        if ($row) {
            $last = $row->last_task_date ? (string)$row->last_task_date : null;
            $prev = Carbon::parse($taskDate)->subDay()->format('Y-m-d');
            if ($last === $prev) {
                $consecutive = (int)$row->consecutive_days + 1;
                $multiplier = min(self::MULTIPLIER_MAX, (float)$row->multiplier + self::MULTIPLIER_STEP);
            } elseif ($last !== null && $last !== $taskDate) {
                $daysDiff = (int)Carbon::parse($taskDate)->diffInDays(Carbon::parse($last));
                if ($daysDiff > 1) {
                    $multiplier = max(self::MULTIPLIER_MIN, (float)$row->multiplier - self::MULTIPLIER_STEP);
                    $consecutive = 1;
                } else {
                    $multiplier = (float)$row->multiplier;
                    $consecutive = (int)$row->consecutive_days + 1;
                }
            }
        }

        DB::connection('write')->table(TableName::agent() . 'superball_user_multiplier')->updateOrInsert(
            ['user_id' => $userId],
            [
                'consecutive_days' => $consecutive,
                'last_task_date' => $taskDate,
                'multiplier' => $multiplier,
                'updated_at' => now()->format('Y-m-d H:i:s'),
            ]
        );
    }

    private function sendOpenRewardMailOnce($userId, $ballCount, $luckyNumber, $multiplier, $hitCount, $basePrize): void
    {
        $existsKey = sprintf('superball_open_%s_%s', $userId, date('Ymd'));
        if (Redis::exists($existsKey)) {
            return;
        }
        $totalPrize =  $ballCount * $basePrize * $multiplier + $hitCount * 10;
        PrivateMail::sendMail(
            2,
            $userId,
            'Your Lucky Ball Results!',
            "Yesterday you earned {$ballCount} balls and your lucky number was {$luckyNumber}. 
            You matched {$hitCount} balls, winning a base prize of {$basePrize} with a {$multiplier}x reward multiplier.
            Total Prize: {$ballCount}*{$basePrize}*{$multiplier}+{$hitCount}*10={$totalPrize}",
            '',
            '');
        Redis::setex($existsKey, 86400 * 2, 1);
    }
}
