<?php
declare(strict_types=1);

namespace app\dice\logic\reward_config_record;

use app\api\logic\PlayStartLogic;
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
use app\dice\model\play_record_test\DicePlayRecordTest;
use app\dice\model\reward_config_record\DiceRewardConfigRecord;
use app\dice\model\reward\DiceReward;
use app\dice\model\reward_config\DiceRewardConfig;
use support\Log;
use support\think\Db;

/**
 * 一键测试权重：单进程后台执行模拟摇色子，写入 dice_play_record_test 并更新 dice_reward_config_record 进度
 */
class WeightTestRunner
{
    private const BATCH_SIZE = 10;

    /**
     * 执行指定测试记录：按付费/免费、顺/逆方向交替模拟（付费顺→付费逆→免费顺→免费逆），每 10 条写入一次测试表并更新进度
     * 支持：1）lottery_config_id 有值时用奖池配置档位权重；2）无值时用记录中的 paid_tier_weights / free_tier_weights
     * @param int $recordId dice_reward_config_record.id
     */
    public function run(int $recordId): void
    {
        $record = DiceRewardConfigRecord::find($recordId);
        if (!$record) {
            Log::error("WeightTestRunner: 记录不存在 record_id={$recordId}");
            return;
        }

        $paidS = (int) ($record->paid_s_count ?? 0);
        $paidN = (int) ($record->paid_n_count ?? 0);
        $freeS = (int) ($record->free_s_count ?? 0);
        $freeN = (int) ($record->free_n_count ?? 0);
        if ($paidS + $paidN + $freeS + $freeN <= 0) {
            $sCount = (int) ($record->s_count ?? 0);
            $nCount = (int) ($record->n_count ?? 0);
            $total = $sCount + $nCount;
            if ($total <= 0) {
                $this->markFailed($recordId, '抽奖次数必须大于 0');
                return;
            }
            $paidS = $sCount;
            $paidN = $nCount;
        } else {
            $total = $paidS + $paidN + $freeS + $freeN;
        }

        $paidConfigId = (int) ($record->paid_lottery_config_id ?? 0);
        $freeConfigId = (int) ($record->free_lottery_config_id ?? 0);
        if ($paidConfigId <= 0) {
            $paidConfigId = (int) ($record->lottery_config_id ?? 0);
        }
        if ($freeConfigId <= 0) {
            $freeConfigId = (int) ($record->lottery_config_id ?? 0);
        }
        $paidConfig = $paidConfigId > 0 ? DiceLotteryPoolConfig::find($paidConfigId) : null;
        $freeConfig = $freeConfigId > 0 ? DiceLotteryPoolConfig::find($freeConfigId) : null;
        if ($paidConfigId > 0 && !$paidConfig) {
            $this->markFailed($recordId, '付费奖池配置不存在');
            return;
        }
        if ($freeConfigId > 0 && !$freeConfig) {
            $this->markFailed($recordId, '免费奖池配置不存在');
            return;
        }
        $paidTierWeights = (is_array($record->paid_tier_weights ?? null) && $record->paid_tier_weights !== [])
            ? $record->paid_tier_weights
            : null;
        $freeTierWeights = (is_array($record->free_tier_weights ?? null) && $record->free_tier_weights !== [])
            ? $record->free_tier_weights
            : null;
        if ($paidConfig === null && $paidTierWeights === null) {
            $this->markFailed($recordId, '付费未选奖池时需提供 paid_tier_weights');
            return;
        }
        if ($freeConfig === null && $freeTierWeights === null) {
            $this->markFailed($recordId, '免费未选奖池时需提供 free_tier_weights');
            return;
        }

        // 每次测试开始前清空进程内静态缓存，强制从共享缓存读取最新 BIGWIN/奖励配置，与数据库一致
        DiceRewardConfig::clearRequestInstance();
        DiceReward::clearRequestInstance();

        // 测试时按“单个虚拟玩家”累计中奖金额来判断是否触发杀分：达到 safety_line 前用自定义档位（玩家权重），达到后用奖池权重
        $paidSafetyLine = $paidConfig !== null ? (int) ($paidConfig->safety_line ?? 0) : 0;
        $freeSafetyLine = $freeConfig !== null ? (int) ($freeConfig->safety_line ?? 0) : 0;
        $paidPlayerWinTotal = 0.0;
        $freePlayerWinTotal = 0.0;

        $playLogic = new PlayStartLogic();
        $resultCounts = [];
        $tierCounts = [];
        $buffer = [];
        $done = 0;

        try {
            for ($i = 0; $i < $paidS; $i++) {
                $usePoolWeights = $paidConfig !== null && $paidPlayerWinTotal >= $paidSafetyLine && $paidSafetyLine > 0;
                $customWeights = $usePoolWeights ? null : $paidTierWeights;
                $row = $playLogic->simulateOnePlay($paidConfig, 0, 0, $customWeights);
                $this->accumulatePlayerWin($row, $paidPlayerWinTotal);
                $this->aggregate($row, $resultCounts, $tierCounts);
                $buffer[] = $this->rowForInsert($row, $recordId);
                $done++;
                $this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
            }
            for ($i = 0; $i < $paidN; $i++) {
                $usePoolWeights = $paidConfig !== null && $paidPlayerWinTotal >= $paidSafetyLine && $paidSafetyLine > 0;
                $customWeights = $usePoolWeights ? null : $paidTierWeights;
                $row = $playLogic->simulateOnePlay($paidConfig, 1, 0, $customWeights);
                $this->accumulatePlayerWin($row, $paidPlayerWinTotal);
                $this->aggregate($row, $resultCounts, $tierCounts);
                $buffer[] = $this->rowForInsert($row, $recordId);
                $done++;
                $this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
            }
            for ($i = 0; $i < $freeS; $i++) {
                $usePoolWeights = $freeConfig !== null && $freePlayerWinTotal >= $freeSafetyLine && $freeSafetyLine > 0;
                $customWeights = $usePoolWeights ? null : $freeTierWeights;
                $row = $playLogic->simulateOnePlay($freeConfig, 0, 1, $customWeights);
                $this->accumulatePlayerWin($row, $freePlayerWinTotal);
                $this->aggregate($row, $resultCounts, $tierCounts);
                $buffer[] = $this->rowForInsert($row, $recordId);
                $done++;
                $this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
            }
            for ($i = 0; $i < $freeN; $i++) {
                $usePoolWeights = $freeConfig !== null && $freePlayerWinTotal >= $freeSafetyLine && $freeSafetyLine > 0;
                $customWeights = $usePoolWeights ? null : $freeTierWeights;
                $row = $playLogic->simulateOnePlay($freeConfig, 1, 1, $customWeights);
                $this->accumulatePlayerWin($row, $freePlayerWinTotal);
                $this->aggregate($row, $resultCounts, $tierCounts);
                $buffer[] = $this->rowForInsert($row, $recordId);
                $done++;
                $this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
            }
            if (!empty($buffer)) {
                $this->insertBuffer($buffer);
                $this->updateProgress($recordId, $done, $resultCounts, $tierCounts);
            }
            // 平台赚取金额：通过关联 DicePlayRecordTest（reward_config_record_id）统计
            $this->markSuccess($recordId, $resultCounts, $tierCounts);
        } catch (\Throwable $e) {
            Log::error('WeightTestRunner exception: ' . $e->getMessage(), ['record_id' => $recordId, 'trace' => $e->getTraceAsString()]);
            $this->markFailed($recordId, $e->getMessage());
        }
    }

    /** 累加单个虚拟玩家在测试过程中的中奖金额（win_coin） */
    private function accumulatePlayerWin(array $row, float &$runningWinTotal): void
    {
        if (!isset($row['win_coin'])) {
            return;
        }
        $runningWinTotal += (float) $row['win_coin'];
    }

    private function aggregate(array $row, array &$resultCounts, array &$tierCounts): void
    {
        $grid = (int) ($row['roll_number_for_count'] ?? $row['roll_number'] ?? 0);
        if ($grid >= 5 && $grid <= 30) {
            $resultCounts[$grid] = ($resultCounts[$grid] ?? 0) + 1;
        }
        $tier = (string) ($row['tier'] ?? '');
        if ($tier !== '') {
            $tierCounts[$tier] = ($tierCounts[$tier] ?? 0) + 1;
        }
    }

    private function rowForInsert(array $row, int $rewardConfigRecordId): array
    {
        $out = [
            'reward_config_record_id' => $rewardConfigRecordId,
        ];
        $keys = [
            'player_id', 'admin_id', 'lottery_config_id', 'lottery_type', 'is_win', 'win_coin',
            'super_win_coin', 'reward_win_coin', 'use_coins', 'direction', 'reward_config_id',
            'start_index', 'target_index', 'roll_array', 'roll_number', 'lottery_name', 'status',
        ];
        foreach ($keys as $k) {
            if (array_key_exists($k, $row)) {
                $out[$k] = $row[$k];
            }
        }
        return $out;
    }

    private function flushIfNeeded(array &$buffer, int $recordId, int $done, int $total, array $resultCounts, array $tierCounts): void
    {
        if (count($buffer) < self::BATCH_SIZE) {
            return;
        }
        $this->insertBuffer($buffer);
        $buffer = [];
        $this->updateProgress($recordId, $done, $resultCounts, $tierCounts);
    }

    private function insertBuffer(array $rows): void
    {
        if (empty($rows)) {
            return;
        }
        foreach ($rows as $row) {
            DicePlayRecordTest::create($row);
        }
    }

    private function updateProgress(int $recordId, int $overPlayCount, array $resultCounts, array $tierCounts): void
    {
        $record = DiceRewardConfigRecord::find($recordId);
        if ($record) {
            $record->over_play_count = $overPlayCount;
            $record->result_counts = $resultCounts;
            $record->tier_counts = $tierCounts;
            $record->save();
        }
    }

    /**
     * 标记测试成功并记录平台总盈利 platform_profit
     * 通过关联 DicePlayRecordTest（reward_config_record_id）统计：付费(lottery_type=0)次数×100 - win_coin 求和
     */
    private function markSuccess(int $recordId, array $resultCounts, array $tierCounts): void
    {
        $record = DiceRewardConfigRecord::find($recordId);
        if ($record) {
            // 平台盈利通过关联测试记录统计
            $platformProfit = DiceRewardConfigRecord::computePlatformProfitFromRelated($recordId);
            // 落点统计也通过关联测试记录重新统计，避免模拟过程异常导致为空
            $dbResultCounts = DiceRewardConfigRecord::computeResultCountsFromRelated($recordId);

            $record->status = DiceRewardConfigRecord::STATUS_SUCCESS;
            $record->result_counts = !empty($dbResultCounts) ? $dbResultCounts : $resultCounts;
            $record->tier_counts = $tierCounts;
            $record->remark = null;
            $record->platform_profit = $platformProfit;
            $record->save();
        }
    }

    private function markFailed(int $recordId, string $message): void
    {
        DiceRewardConfigRecord::where('id', $recordId)->update([
            'status'   => DiceRewardConfigRecord::STATUS_FAIL,
            'remark'   => mb_substr($message, 0, 500),
            'platform_profit' => null,
        ]);
    }
}
