<?php
declare(strict_types=1);

namespace app\dice\logic\reward_config_record;

use app\api\logic\PlayStartLogic;
use app\dice\helper\AdminScopeHelper;
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 进度
 * 抽奖规则与 PlayStartLogic 一致：
 * - 付费未杀分：按模拟玩家档位权重抽档，lottery_config_id 记 default
 * - 付费杀分：测试内杀分开启且模拟池盈利 >= test_safety_line 后切 killScore
 * - 免费券：name=free 奖池（无则 default），排除 5/30 豹子
 */
class WeightTestRunner
{
    private const BATCH_SIZE = 10;

    /** 本次测试所属渠道（与 dice_reward_config_record.dept_id 一致） */
    private int $runDeptId = 0;
    /** 测试记录写库白名单字段 */
    private const PLAY_RECORD_TEST_COLUMNS = [
        'reward_config_record_id',
        'dept_id',
        'admin_id',
        'lottery_config_id',
        'lottery_type',
        'is_win',
        'win_coin',
        'super_win_coin',
        'reward_win_coin',
        'direction',
        'reward_tier',
        'ante',
        'paid_amount',
        'start_index',
        'target_index',
        'roll_array',
        'roll_number',
        'status',
    ];

    /**
     * 执行指定测试记录：按付费次数模拟，若命中 T5 则链式插入免费局（同方向同底注）
     * @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;
        }

        $ante = is_numeric($record->ante ?? null) ? intval($record->ante) : 1;
        $paidS = (int) ($record->paid_s_count ?? 0);
        $paidN = (int) ($record->paid_n_count ?? 0);
        $total = $paidS + $paidN;
        if ($total <= 0) {
            $this->markFailed($recordId, '抽奖次数必须大于 0');
            return;
        }

        $this->runDeptId = $this->resolveRunDeptId($recordId, $record);
        $deptId = $this->runDeptId;
        DiceReward::setRequestDeptId($deptId);
        DiceRewardConfig::clearRequestInstance();

        $configType0 = DiceLotteryPoolConfig::findByNameForDept('default', $deptId);
        $configKill = DiceLotteryPoolConfig::findByNameForDept('killScore', $deptId);
        $configFree = DiceLotteryPoolConfig::findByNameForDept('free', $deptId);
        if (!$configType0) {
            $this->markFailed($recordId, '彩金池配置 name=default 不存在（当前渠道）');
            return;
        }

        $paidTierWeightsCustom = (is_array($record->paid_tier_weights ?? null) && $record->paid_tier_weights !== [])
            ? $record->paid_tier_weights
            : null;
        $freeTierWeightsCustom = (is_array($record->free_tier_weights ?? null) && $record->free_tier_weights !== [])
            ? $record->free_tier_weights
            : null;

        $paidPoolConfigId = (int) ($record->paid_lottery_config_id ?? 0);
        $freePoolConfigId = (int) ($record->free_lottery_config_id ?? 0);

        $paidPoolConfig = $paidPoolConfigId > 0 ? DiceLotteryPoolConfig::find($paidPoolConfigId) : $configType0;
        if (!$paidPoolConfig || AdminScopeHelper::normalizeRecordDeptId($paidPoolConfig->dept_id ?? null) !== $deptId) {
            $paidPoolConfig = $configType0;
        }
        $freePoolConfig = $freePoolConfigId > 0 ? DiceLotteryPoolConfig::find($freePoolConfigId) : $configFree;
        if (!$freePoolConfig || AdminScopeHelper::normalizeRecordDeptId($freePoolConfig->dept_id ?? null) !== $deptId) {
            $freePoolConfig = $configFree ?: $configType0;
        }

        if ($paidTierWeightsCustom !== null && array_sum($paidTierWeightsCustom) <= 0) {
            $this->markFailed($recordId, 'paid_tier_weights（玩家权重）之和必须大于 0');
            return;
        }
        if ($freeTierWeightsCustom !== null && array_sum($freeTierWeightsCustom) <= 0) {
            $this->markFailed($recordId, 'free_tier_weights（免费玩家权重）之和必须大于 0');
            return;
        }

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

        $killModeEnabled = (int) ($record->kill_mode_enabled ?? 0) === 1;
        $testSafetyLine = (int) ($record->test_safety_line ?? 5000);
        if ($testSafetyLine < 0) {
            $testSafetyLine = 0;
        }

        // 彩金池累计盈利：从 default.profit_amount 起步并在测试内逐局累加，与杀分判定值一致
        $poolProfitTotal = (float) ($configType0->profit_amount ?? 0);

        // 付费未杀分时的模拟玩家档位权重（自定义 > 快照 > 兜底奖池）
        $paidPlayerWeights = $paidTierWeightsCustom ?? $this->resolveTierWeightsSnapshot($record, 'paid');

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

        try {
            $this->runChainFreeMode(
                $recordId,
                $deptId,
                $playLogic,
                $paidS,
                $paidN,
                $ante,
                $configType0,
                $paidPoolConfig,
                $freePoolConfig,
                $configKill,
                $paidPlayerWeights,
                $freeTierWeightsCustom,
                $killModeEnabled,
                $testSafetyLine,
                $poolProfitTotal,
                $resultCounts,
                $tierCounts,
                $buffer,
                $done
            );
            if (!empty($buffer)) {
                $this->insertBuffer($buffer);
                // 链式/非链式：运行中均不写入 total_play_count，仅在 markSuccess 落库实际总次数
                $this->updateProgress($recordId, $done, $resultCounts, $tierCounts, null);
            }
            // 平台赚取金额：通过关联 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());
        } finally {
            DiceReward::clearRequestInstance();
            DiceRewardConfig::clearRequestInstance();
        }
    }

    /**
     * 付费次数仅由配置决定；付费抽到「再来一次」则在队列末尾插入一条免费抽奖（同方向、同底注），可链式触发
     */
    private function runChainFreeMode(
        int $recordId,
        int $deptId,
        PlayStartLogic $playLogic,
        int $paidS,
        int $paidN,
        int $ante,
        $defaultPoolConfig,
        $paidPoolConfig,
        $freePoolConfig,
        $killPoolConfig,
        ?array $paidPlayerWeights,
        ?array $freeTierWeightsCustom,
        bool $killModeEnabled,
        int $testSafetyLine,
        float &$poolProfitTotal,
        array &$resultCounts,
        array &$tierCounts,
        array &$buffer,
        int &$done
    ): void {
        $queue = [];
        for ($i = 0; $i < $paidS; $i++) {
            $queue[] = ['paid', 0, $ante];
        }
        for ($i = 0; $i < $paidN; $i++) {
            $queue[] = ['paid', 1, $ante];
        }
        $qi = 0;
        while ($qi < count($queue)) {
            $item = $queue[$qi];
            $isPaid = $item[0] === 'paid';
            $dir = $item[1];
            $playAnte = $item[2];
            $lotteryType = $isPaid ? 0 : 1;

            if ($isPaid) {
                $useKillForPaid = $killModeEnabled
                    && $poolProfitTotal >= $testSafetyLine
                    && $killPoolConfig !== null;
                if ($useKillForPaid) {
                    $cfg = $killPoolConfig;
                    $customWeights = null;
                } else {
                    // 付费未杀分：模拟玩家档位权重，lottery_config_id 记 default（与 PlayStartLogic 一致）
                    $cfg = $defaultPoolConfig;
                    $customWeights = $paidPlayerWeights;
                    if ($customWeights === null) {
                        $cfg = $paidPoolConfig;
                        $customWeights = null;
                    }
                }
            } else {
                $cfg = $freePoolConfig;
                $customWeights = $freeTierWeightsCustom;
            }

            $row = $playLogic->simulateOnePlay($cfg, $dir, $lotteryType, $playAnte, $customWeights, $deptId);
            $winCoin = (float) ($row['win_coin'] ?? 0);
            $paidAmount = (float) ($row['paid_amount'] ?? 0);
            $perPlayProfit = $isPaid ? ($winCoin - $paidAmount) : $winCoin;
            $poolProfitTotal += round($perPlayProfit, 2);
            $this->aggregate($row, $resultCounts, $tierCounts);
            $buffer[] = $this->rowForInsert($row, $recordId, $deptId);
            $done++;

            if (!empty($row['grants_free_ticket'])) {
                $queue[] = ['free', $dir, $playAnte];
            }

            $this->flushIfNeeded($buffer, $recordId, $done, count($queue), $resultCounts, $tierCounts, null);

            $qi++;
        }
    }

    /**
     * 从 tier_weights_snapshot 读取付费/免费档位权重快照
     */
    private function resolveTierWeightsSnapshot(DiceRewardConfigRecord $record, string $side): ?array
    {
        $snap = $record->tier_weights_snapshot ?? null;
        if (! is_array($snap)) {
            return null;
        }
        $weights = $snap[$side] ?? null;
        if (! is_array($weights) || $weights === []) {
            return null;
        }

        return $weights;
    }

    /**
     * 解析本次测试渠道：优先读库字段，避免 ORM 字段缓存未含 dept_id 时读不到
     */
    private function resolveRunDeptId(int $recordId, DiceRewardConfigRecord $record): int
    {
        $recordTable = (new DiceRewardConfigRecord())->getTable();
        $fromDb = Db::table($recordTable)->where('id', $recordId)->value('dept_id');
        $deptId = AdminScopeHelper::normalizeRecordDeptId($fromDb);
        if (! AdminScopeHelper::isTemplateDeptId($deptId)) {
            return $deptId;
        }

        $deptId = AdminScopeHelper::normalizeRecordDeptId($record->dept_id ?? null);
        if (! AdminScopeHelper::isTemplateDeptId($deptId)) {
            return $deptId;
        }

        return $this->resolveDeptIdFromRecordPools($record, $deptId);
    }

    /**
     * 历史记录 dept_id=0 时，从关联奖池配置反推并回写
     */
    private function resolveDeptIdFromRecordPools(DiceRewardConfigRecord $record, int $fallbackDeptId): int
    {
        foreach (
            [
                (int) ($record->paid_lottery_config_id ?? 0),
                (int) ($record->free_lottery_config_id ?? 0),
                (int) ($record->lottery_config_id ?? 0),
            ] as $poolId
        ) {
            if ($poolId <= 0) {
                continue;
            }
            $pool = DiceLotteryPoolConfig::find($poolId);
            if (! $pool) {
                continue;
            }
            $poolDeptId = AdminScopeHelper::normalizeRecordDeptId($pool->dept_id ?? null);
            if (! AdminScopeHelper::isTemplateDeptId($poolDeptId)) {
                if (AdminScopeHelper::normalizeRecordDeptId($record->dept_id ?? null) !== $poolDeptId) {
                    $record->dept_id = $poolDeptId;
                    $record->save();
                }
                return $poolDeptId;
            }
        }

        return $fallbackDeptId;
    }

    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, int $deptId): array
    {
        $bindDeptId = ! AdminScopeHelper::isTemplateDeptId($this->runDeptId)
            ? $this->runDeptId
            : $deptId;
        $out = [
            'reward_config_record_id' => $rewardConfigRecordId,
            'dept_id'                 => $bindDeptId,
        ];
        $keys = [
            'admin_id', 'lottery_config_id', 'lottery_type', 'is_win', 'win_coin',
            'super_win_coin', 'reward_win_coin', 'direction', 'reward_tier',
            'ante', 'paid_amount',
            'start_index', 'target_index', 'roll_array', 'roll_number', '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, ?int $recordTotalPlayCount = null): void
    {
        if (count($buffer) < self::BATCH_SIZE) {
            return;
        }
        $this->insertBuffer($buffer);
        array_splice($buffer, 0, count($buffer));
        $this->updateProgress($recordId, $done, $resultCounts, $tierCounts, $recordTotalPlayCount);
    }

    private function insertBuffer(array $rows): void
    {
        if (empty($rows)) {
            return;
        }
        $table = (new DicePlayRecordTest())->getTable();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $payload = [];
            foreach (self::PLAY_RECORD_TEST_COLUMNS as $column) {
                if (array_key_exists($column, $row)) {
                    $payload[$column] = $row[$column];
                }
            }
            if (! AdminScopeHelper::isTemplateDeptId($this->runDeptId)) {
                $payload['dept_id'] = $this->runDeptId;
            }
            if (!array_key_exists('create_time', $payload) || $payload['create_time'] === null || $payload['create_time'] === '') {
                $payload['create_time'] = date('Y-m-d H:i:s');
            }
            if (!array_key_exists('update_time', $payload) || $payload['update_time'] === null || $payload['update_time'] === '') {
                $payload['update_time'] = $payload['create_time'];
            }
            if ($payload === []) {
                continue;
            }
            // strict(false)：表结构新增 dept_id 后，避免连接层字段缓存未刷新导致插入被丢弃
            Db::table($table)->strict(false)->insert($payload);
        }
    }

    /**
     * 将本批测试明细 dept_id 与主记录对齐（修复历史 worker 未写入 dept_id 的情况）
     */
    private function syncPlayRecordTestDeptId(int $recordId, int $deptId): void
    {
        if (AdminScopeHelper::isTemplateDeptId($deptId)) {
            return;
        }
        DicePlayRecordTest::where('reward_config_record_id', $recordId)
            ->where(function ($query) {
                $query->whereNull('dept_id')->whereOr('dept_id', AdminScopeHelper::DEFAULT_TEMPLATE_DEPT);
            })
            ->update(['dept_id' => $deptId]);
    }

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

    /**
     * 标记测试成功并记录平台总盈利 platform_profit
     * 通过关联 DicePlayRecordTest（reward_config_record_id）统计：付费金额 paid_amount 求和 - win_coin 求和
     */
    private function markSuccess(int $recordId, array $resultCounts, array $tierCounts): void
    {
        $record = DiceRewardConfigRecord::find($recordId);
        if ($record) {
            $deptId = AdminScopeHelper::normalizeRecordDeptId($record->dept_id ?? null);
            if (AdminScopeHelper::isTemplateDeptId($deptId) && ! AdminScopeHelper::isTemplateDeptId($this->runDeptId)) {
                $deptId = $this->runDeptId;
                $record->dept_id = $deptId;
            }
            $this->syncPlayRecordTestDeptId($recordId, $deptId);

            // 平台盈利通过关联测试记录统计
            $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->play_again_count = DiceRewardConfigRecord::computePlayAgainCountFromRelated($recordId);
            $actualCount = (int) DicePlayRecordTest::where('reward_config_record_id', $recordId)->count();
            $record->total_play_count = $actualCount;
            $record->test_count = $actualCount;
            $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,
        ]);
    }
}
