<?php
declare(strict_types=1);

namespace app\api\logic;

use app\api\cache\UserCache;
use app\api\util\ApiLang;
use app\api\service\LotteryService;
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\player\DicePlayer;
use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
use app\dice\model\reward\DiceReward;
use app\dice\model\reward\DiceRewardConfig;
use plugin\saiadmin\exception\ApiException;
use support\Log;
use support\think\Cache;
use support\think\Db;

/**
 * 开始游戏 / 抽奖一局
 */
class PlayStartLogic
{
    /** 抽奖类型：付费 */
    public const LOTTERY_TYPE_PAID = 0;
    /** 抽奖类型：免费 */
    public const LOTTERY_TYPE_FREE = 1;
    /** 钱包流水类型：抽奖 */
    public const WALLET_TYPE_DRAW = 5;
    /** 对局状态：成功 */
    public const RECORD_STATUS_SUCCESS = 1;
    /** 对局状态：超时/失败 */
    public const RECORD_STATUS_TIMEOUT = 0;

    /** 开启对局最低余额 = |DiceRewardConfig 最小 real_ev + 100| */
    private const MIN_COIN_EXTRA = 100;
    /** 豹子号中大奖额外平台币（无 BIGWIN 配置时兜底） */
    private const SUPER_WIN_BONUS = 500;
    /** 可触发超级大奖的 grid_number（5=全1 10=全2 15=全3 20=全4 25=全5 30=全6） */
    private const SUPER_WIN_GRID_NUMBERS = [5, 10, 15, 20, 25, 30];
    /** 5 和 30 抽到即豹子，不参与 BIGWIN 权重判定；10/15/20/25 按 BIGWIN weight 判定是否豹子 */
    private const SUPER_WIN_ALWAYS_GRID_NUMBERS = [5, 30];

    /**
     * 执行一局游戏
     * @param int $playerId 玩家ID
     * @param int $direction 方向 0=无/顺时针 1=中奖/逆时针（前端 direction）
     * @return array 成功返回 DicePlayRecord 数据；余额不足时抛 ApiException，message 为约定文案
     */
    public function run(int $playerId, int $direction): array
    {
        $player = DicePlayer::find($playerId);
        if (!$player) {
            throw new ApiException('用户不存在');
        }

        $minEv = DiceRewardConfig::getCachedMinRealEv();
        $minCoin = abs($minEv + self::MIN_COIN_EXTRA);
        $coin = (float) $player->coin;
        if ($coin < $minCoin) {
            throw new ApiException(ApiLang::translateParams('当前玩家余额%s小于%s无法继续游戏', [$coin, $minCoin]));
        }

        $paid = (int) ($player->paid_ticket_count ?? 0);
        $free = (int) ($player->free_ticket_count ?? 0);
        if ($paid + $free <= 0) {
            throw new ApiException('抽奖券不足');
        }

        $lotteryService = LotteryService::getOrCreate($playerId);
        $ticketType = LotteryService::drawTicketType($paid, $free);
        $configType0 = DiceLotteryPoolConfig::where('name', 'default')->find();
        $configType1 = DiceLotteryPoolConfig::where('name', 'killScore')->find();
        if (!$configType0) {
            throw new ApiException('奖池配置不存在（需 name=default）');
        }

        // 玩家累计盈利：仅统计 lottery_config_id=type=0 的成功对局（中奖金额-100*局数）
        $playerQuery = DicePlayRecord::where('player_id', $playerId)
            ->where('lottery_config_id', $configType0->id)
            ->where('status', self::RECORD_STATUS_SUCCESS);
        $playerWinSum = (float) $playerQuery->sum('win_coin');
        $playerPlayCount = (int) $playerQuery->count();
        $playerProfitTotal = $playerWinSum - 100.0 * $playerPlayCount;
        $safetyLine = (int) ($configType0->safety_line ?? 0);
        $killEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1;
        // 玩家累计盈利>=安全线时杀分：无论付费/免费，都用 type=1 的 T*_weight；未达到时一律按玩家权重
        // 记录 lottery_config_id：杀分记 type=1；未杀分统一记当前池 type=0
        $usePoolWeights = $killEnabled && $playerProfitTotal >= $safetyLine && $configType1 !== null;
        $config = $usePoolWeights ? $configType1 : $configType0;

        // 按档位 T1-T5 抽取后，从 DiceReward 表按当前方向取该档位数据，再按 weight 抽取一条得到 grid_number
        $rewardInstance = DiceReward::getCachedInstance();
        $byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
        $maxTierRetry = 10;
        $chosen = null;
        $tier = null;
        for ($tierAttempt = 0; $tierAttempt < $maxTierRetry; $tierAttempt++) {
            $tier = $usePoolWeights
                ? LotteryService::drawTierByWeights($config)
                : LotteryService::drawTierByPlayerWeights($player);
            $tierRewards = $byTierDirection[$tier][$direction] ?? [];
            if (empty($tierRewards)) {
                Log::warning("档位 {$tier} 方向 {$direction} 无任何 DiceReward，重新摇取档位");
                continue;
            }
            try {
                $chosen = self::drawRewardByWeight($tierRewards);
            } catch (\RuntimeException $e) {
                if ($e->getMessage() === self::EXCEPTION_WEIGHT_ALL_ZERO) {
                    Log::warning("档位 {$tier} 下所有奖励权重均为 0，重新摇取档位");
                    continue;
                }
                throw $e;
            }
            break;
        }
        if ($chosen === null) {
            Log::error("多次摇取档位后仍无有效 DiceReward");
            throw new ApiException('暂无可用奖励配置');
        }

        $startIndex = (int) ($chosen['start_index'] ?? 0);
        $targetIndex = (int) ($chosen['end_index'] ?? 0);
        $rollNumber = (int) ($chosen['grid_number'] ?? 0);
        $realEv = (float) ($chosen['real_ev'] ?? 0);
        $isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
        $rewardWinCoin = $isTierT5 ? $realEv : (100 + $realEv);

        // 豹子判定：5/30 必豹子；10/15/20/25 按 DiceRewardConfig 中 BIGWIN 该点数的 weight 判定（0-10000，10000=100%）
        $superWinCoin = 0;
        $isWin = 0;
        $bigWinRealEv = 0.0;
        if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
            $bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
            $alwaysSuperWin = in_array($rollNumber, self::SUPER_WIN_ALWAYS_GRID_NUMBERS, true);
            $doSuperWin = $alwaysSuperWin;
            if (!$doSuperWin) {
                $bigWinWeight = 10000;
                if ($bigWinConfig !== null && isset($bigWinConfig['weight'])) {
                    $bigWinWeight = min(10000, max(0, (int) $bigWinConfig['weight']));
                    $bigWinRealEv = (float) ($bigWinConfig['real_ev'] ?? 0);
                }
                $roll = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX);
                $doSuperWin = $bigWinWeight > 0 && $roll < ($bigWinWeight / 10000.0);
            } else {
                if ($bigWinConfig !== null) {
                    $bigWinRealEv = (float) ($bigWinConfig['real_ev'] ?? 0);
                }
            }
            if ($doSuperWin) {
                $rollArray = $this->getSuperWinRollArray($rollNumber);
                $isWin = 1;
                $superWinCoin = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
                // 中 BIGWIN 豹子：不走原奖励流程，不记录原奖励，不触发 T5 再来一次，仅发放豹子奖金
                $rewardWinCoin = 0;
                $realEv = 0;
                $isTierT5 = false;
            } else {
                $rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
            }
        } else {
            $rollArray = $this->generateRollArrayFromSum($rollNumber);
        }

        Log::info(sprintf(
            '摇取点数 roll_number=%d, 方向=%d, start_index=%d, target_index=%d',
            $rollNumber,
            $direction,
            $startIndex,
            $targetIndex
        ));
        $winCoin = $superWinCoin + $rewardWinCoin; // 赢取平台币 = 中大奖 + 摇色子中奖（豹子时 rewardWinCoin 已为 0）

        $record = null;
        $configId = (int) $config->id;
        $type0ConfigId = (int) $configType0->id;
        $rewardId = ($isWin === 1 && $superWinCoin > 0) ? 0 : $targetIndex; // 中豹子不记录原奖励配置 id
        $configName = (string) ($config->name ?? '');
        $adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
        try {
            Db::transaction(function () use (
                $playerId,
                $adminId,
                $configId,
                $type0ConfigId,
                $rewardId,
                $configName,
                $ticketType,
                $winCoin,
                $superWinCoin,
                $rewardWinCoin,
                $isWin,
                $realEv,
                $bigWinRealEv,
                $direction,
                $startIndex,
                $targetIndex,
                $rollArray,
                $isTierT5,
                &$record
            ) {
                $record = DicePlayRecord::create([
                    'player_id'          => $playerId,
                    'admin_id'           => $adminId,
                    'lottery_config_id'  => $configId,
                    'lottery_type'       => $ticketType,
                    'is_win'             => $isWin,
                    'win_coin'           => $winCoin,
                    'super_win_coin'     => $superWinCoin,
                    'reward_win_coin'    => $rewardWinCoin,
                    'use_coins'          => 0,
                    'direction'          => $direction,
                    'reward_config_id'   => $rewardId,
                    'start_index'        => $startIndex,
                    'target_index'       => $targetIndex,
                    'roll_array'         => is_array($rollArray) ? json_encode($rollArray) : $rollArray,
                    'roll_number'        => is_array($rollArray) ? array_sum($rollArray) : 0,
                    'lottery_name'       => $configName,
                    'status'             => self::RECORD_STATUS_SUCCESS,
                ]);

                $p = DicePlayer::find($playerId);
                if (!$p) {
                    throw new \RuntimeException('玩家不存在');
                }
                $coinBefore = (float) $p->coin;
                $coinAfter = $coinBefore + $winCoin;
                $p->coin = $coinAfter;
                $p->total_ticket_count = max(0, (int) $p->total_ticket_count - 1);
                if ($ticketType === self::LOTTERY_TYPE_PAID) {
                    $p->paid_ticket_count = max(0, (int) $p->paid_ticket_count - 1);
                } else {
                    $p->free_ticket_count = max(0, (int) $p->free_ticket_count - 1);
                }

                // 若本局中奖档位为 T5，则额外赠送 1 次免费抽奖次数（总次数也 +1），并记录抽奖券获取记录
                if ($isTierT5) {
                    $p->free_ticket_count = (int) $p->free_ticket_count + 1;
                    $p->total_ticket_count = (int) $p->total_ticket_count + 1;

                    DicePlayerTicketRecord::create([
                        'player_id'         => $playerId,
                        'admin_id'          => $adminId,
                        'free_ticket_count' => 1,
                        'remark'            => '中奖结果为T5',
                    ]);
                }

                $p->save();

                // 玩家累计盈利累加在 type=0 彩金池上：每局按“当前中奖金额（含 BIGWIN） - 抽奖券费用 100”
                $perPlayProfit = $winCoin - 100.0;
                $addProfit = $perPlayProfit;
                try {
                    DiceLotteryPoolConfig::where('id', $type0ConfigId)->update([
                        'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . (float) $addProfit),
                    ]);
                } catch (\Throwable $e) {
                    Log::warning('彩金池盈利累加失败', [
                        'config_id' => $type0ConfigId,
                        'add_profit' => $addProfit,
                        'message'    => $e->getMessage(),
                    ]);
                }

                DicePlayerWalletRecord::create([
                    'player_id'      => $playerId,
                    'admin_id'       => $adminId,
                    'coin'           => $winCoin,
                    'type'           => self::WALLET_TYPE_DRAW,
                    'wallet_before'  => $coinBefore,
                    'wallet_after'   => $coinAfter,
                    'remark'         => '抽奖|play_record_id=' . $record->id,
                ]);
            });
        } catch (\Throwable $e) {
            if ($record === null) {
                try {
                    $record = DicePlayRecord::create([
                        'player_id'          => $playerId,
                        'admin_id'           => $adminId ?? null,
                        'lottery_config_id'  => $configId ?? 0,
                        'lottery_type'       => $ticketType,
                        'is_win'             => 0,
                        'win_coin'           => 0,
                        'super_win_coin'     => 0,
                        'reward_win_coin'    => 0,
                        'use_coins'          => 0,
                        'direction'          => $direction,
                        'reward_config_id'   => 0,
                        'start_index'        => $startIndex,
                        'target_index'       => 0,
                        'roll_array'         => '[]',
                        'roll_number'        => 0,
                        'status'             => self::RECORD_STATUS_TIMEOUT,
                    ]);
                } catch (\Throwable $_) {
                    // 表可能无 status 字段时忽略
                }
            }
            throw $e;
        }

        $updated = DicePlayer::find($playerId);
        if ($updated) {
            UserCache::setUser($playerId, $updated->hidden(['password'])->toArray());
        }

        if (!$record instanceof DicePlayRecord) {
            throw new \RuntimeException('对局记录创建失败');
        }
        $arr = $record->toArray();
        if (isset($arr['roll_array']) && is_string($arr['roll_array'])) {
            $arr['roll_array'] = json_decode($arr['roll_array'], true) ?? [];
        }
        $arr['roll_number'] = is_array($arr['roll_array'] ?? null) ? array_sum($arr['roll_array']) : 0;
        $arr['tier'] = $tier ?? '';
        // 记录完数据后返回当前玩家余额与抽奖次数
        $arr['coin'] = $updated ? (float) $updated->coin : 0;
        $arr['total_ticket_count'] = $updated ? (int) $updated->total_ticket_count : 0;
        return $arr;
    }

    /** 该组配置权重均为 0 时抛出，供调用方重试 */
    private const EXCEPTION_WEIGHT_ALL_ZERO = 'REWARD_WEIGHT_ALL_ZERO';

    /**
     * 按权重抽取一条配置：仅 weight>0 参与抽取（weight=0 不会被摇到）
     * 使用 [0, total) 浮点随机，支持最小权重 0.1%（如 weight=0.1），避免整数随机导致小权重失真
     * 全部 weight 为 0 时抛出 RuntimeException(EXCEPTION_WEIGHT_ALL_ZERO)
     */
    private static function drawRewardByWeight(array $rewards): array
    {
        if (empty($rewards)) {
            throw new \InvalidArgumentException('rewards 不能为空');
        }
        $candidateWeights = [];
        foreach ($rewards as $i => $row) {
            $w = isset($row['weight']) ? (float) $row['weight'] : 0.0;
            if ($w > 0) {
                $candidateWeights[$i] = $w;
            }
        }
        $total = (float) array_sum($candidateWeights);
        if ($total > 0) {
            $r = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX) * $total;
            $acc = 0.0;
            foreach ($candidateWeights as $i => $w) {
                $acc += $w;
                if ($r < $acc) {
                    return $rewards[$i];
                }
            }
            return $rewards[array_key_last($candidateWeights)];
        }
        throw new \RuntimeException(self::EXCEPTION_WEIGHT_ALL_ZERO);
    }

    /**
     * 根据摇取点数（5-30）生成 5 个色子数组，每个 1-6，总和为 $sum
     * @return int[] 如 [1,2,3,4,5]
     */
    private function generateRollArrayFromSum(int $sum): array
    {
        $sum = max(5, min(30, $sum));
        $arr = [1, 1, 1, 1, 1];
        $remain = $sum - 5;
        for ($i = 0; $i < $remain; $i++) {
            $candidates = array_keys(array_filter($arr, function ($v) {
                return $v < 6;
            }));
            if (empty($candidates)) {
                break;
            }
            $idx = $candidates[random_int(0, count($candidates) - 1)];
            $arr[$idx]++;
        }
        shuffle($arr);
        return array_values($arr);
    }

    /**
     * 豹子组合：5->[1,1,1,1,1]，10->[2,2,2,2,2]，15->[3,3,3,3,3]，20->[4,4,4,4,4]，25->[5,5,5,5,5]，30->[6,6,6,6,6]
     * @return int[]
     */
    private function getSuperWinRollArray(int $gridNumber): array
    {
        if ($gridNumber === 30) {
            return array_fill(0, 5, 6);
        }
        $n = (int) ($gridNumber / 5);
        $n = max(1, min(5, $n));
        return array_fill(0, 5, $n);
    }

    /**
     * 生成总和为 $sum 且非豹子的 5 个色子（1-6）；sum=5 时仅 [1,1,1,1,1] 可能，仍返回该组合
     * @return int[]
     */
    private function generateNonSuperWinRollArrayWithSum(int $sum): array
    {
        $sum = max(5, min(30, $sum));
        $super = $this->getSuperWinRollArray($sum);
        if ($sum === 5) {
            return $super;
        }
        $arr = $super;
        $maxAttempts = 20;
        for ($a = 0; $a < $maxAttempts; $a++) {
            $idx = random_int(0, count($arr) - 1);
            $j = random_int(0, count($arr) - 1);
            if ($idx === $j) {
                $j = ($j + 1) % 5;
            }
            $i = $idx;
            if ($arr[$i] >= 2 && $arr[$j] <= 5) {
                $arr[$i]--;
                $arr[$j]++;
                shuffle($arr);
                return array_values($arr);
            }
            if ($arr[$i] <= 5 && $arr[$j] >= 2) {
                $arr[$i]++;
                $arr[$j]--;
                shuffle($arr);
                return array_values($arr);
            }
        }
        return $this->generateRollArrayFromSum($sum);
    }

    /**
     * 模拟一局抽奖（不写库、不扣玩家），用于权重测试写入 dice_play_record_test
     * @param \app\dice\model\lottery_pool_config\DiceLotteryPoolConfig|null $config 奖池配置，自定义档位时可为 null
     * @param int $direction 0=顺时针 1=逆时针
     * @param int $lotteryType 0=付费 1=免费
     * @param array|null $customTierWeights 自定义档位权重 ['T1'=>x, 'T2'=>x, ...]，非空时忽略 config 的档位权重
     * @return array 可直接用于 DicePlayRecordTest::create 的字段 + tier（用于统计档位概率）
     */
    public function simulateOnePlay($config, int $direction, int $lotteryType = 0, ?array $customTierWeights = null): array
    {
        $rewardInstance = DiceReward::getCachedInstance();
        $byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
        $maxTierRetry = 10;
        $chosen = null;
        $tier = null;
        for ($tierAttempt = 0; $tierAttempt < $maxTierRetry; $tierAttempt++) {
            if ($customTierWeights !== null && $customTierWeights !== []) {
                $tier = LotteryService::drawTierByWeightsFromArray($customTierWeights);
            } else {
                if ($config === null) {
                    throw new \RuntimeException('模拟抽奖：未提供奖池配置或自定义档位权重');
                }
                $tier = LotteryService::drawTierByWeights($config);
            }
            $tierRewards = $byTierDirection[$tier][$direction] ?? [];
            if (empty($tierRewards)) {
                continue;
            }
            try {
                $chosen = self::drawRewardByWeight($tierRewards);
            } catch (\RuntimeException $e) {
                if ($e->getMessage() === self::EXCEPTION_WEIGHT_ALL_ZERO) {
                    continue;
                }
                throw $e;
            }
            break;
        }
        if ($chosen === null) {
            throw new \RuntimeException('模拟抽奖：无可用奖励配置');
        }

        $startIndex = (int) ($chosen['start_index'] ?? 0);
        $targetIndex = (int) ($chosen['end_index'] ?? 0);
        $rollNumber = (int) ($chosen['grid_number'] ?? 0);
        $realEv = (float) ($chosen['real_ev'] ?? 0);
        $isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
        $rewardWinCoin = $isTierT5 ? $realEv : (100 + $realEv);

        $superWinCoin = 0;
        $isWin = 0;
        $bigWinRealEv = 0.0;
        if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
            $bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
            $alwaysSuperWin = in_array($rollNumber, self::SUPER_WIN_ALWAYS_GRID_NUMBERS, true);
            $doSuperWin = $alwaysSuperWin;
            if (!$doSuperWin) {
                $bigWinWeight = 10000;
                if ($bigWinConfig !== null && isset($bigWinConfig['weight'])) {
                    $bigWinWeight = min(10000, max(0, (int) $bigWinConfig['weight']));
                }
                $roll = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX);
                $doSuperWin = $bigWinWeight > 0 && $roll < ($bigWinWeight / 10000.0);
            }
            if ($bigWinConfig !== null && isset($bigWinConfig['real_ev'])) {
                $bigWinRealEv = (float) $bigWinConfig['real_ev'];
            }
            if ($doSuperWin) {
                $rollArray = $this->getSuperWinRollArray($rollNumber);
                $isWin = 1;
                $superWinCoin = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
                $rewardWinCoin = 0;
            } else {
                $rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
            }
        } else {
            $rollArray = $this->generateRollArrayFromSum($rollNumber);
        }

        $winCoin = $superWinCoin + $rewardWinCoin;
        $configId = $config !== null ? (int) $config->id : 0;
        $rewardId = ($isWin === 1 && $superWinCoin > 0) ? 0 : $targetIndex;
        $configName = $config !== null ? (string) ($config->name ?? '') : '自定义';
        $costRealEv = $realEv + ($isWin === 1 ? $bigWinRealEv : 0.0);

        return [
            'player_id'          => 0,
            'admin_id'           => 0,
            'lottery_config_id'  => $configId,
            'lottery_type'       => $lotteryType,
            'is_win'             => $isWin,
            'win_coin'           => $winCoin,
            'super_win_coin'     => $superWinCoin,
            'reward_win_coin'    => $rewardWinCoin,
            'use_coins'          => 0,
            'direction'          => $direction,
            'reward_config_id'   => $rewardId,
            'start_index'        => $startIndex,
            'target_index'       => $targetIndex,
            'roll_array'         => json_encode($rollArray),
            'roll_number'        => array_sum($rollArray),
            'lottery_name'       => $configName,
            'status'             => self::RECORD_STATUS_SUCCESS,
            'tier'               => $tier,
            'roll_number_for_count' => $rollNumber,
            'real_ev'            => $realEv,
            'bigwin_real_ev'     => $isWin === 1 ? $bigWinRealEv : 0.0,
            'cost_ev'            => $costRealEv,
        ];
    }
}
