<?php
// +----------------------------------------------------------------------
// | saiadmin [ saiadmin快速开发框架 ]
// +----------------------------------------------------------------------
// | Author: your name
// +----------------------------------------------------------------------
namespace app\dice\logic\reward_config;

use app\dice\helper\AdminScopeHelper;
use app\dice\helper\ConfigScopeEditHelper;
use app\dice\logic\reward\DiceRewardLogic;
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
use app\dice\model\reward\DiceRewardConfig;
use app\dice\model\reward_config_record\DiceRewardConfigRecord;
use app\dice\basic\DiceBaseLogic;
use plugin\saiadmin\exception\ApiException;
use plugin\saiadmin\utils\Helper;
use support\Log;

/**
 * 奖励配置逻辑层（DiceRewardConfig）
 * weight 1-10000，各档位权重和不限制
 */
class DiceRewardConfigLogic extends DiceBaseLogic
{
    /** weight 取值范围 */
    private const WEIGHT_MIN = 1;
    private const WEIGHT_MAX = 10000;

    public function __construct()
    {
        $this->model = new DiceRewardConfig();
    }

    /**
     * 新增：保存后刷新缓存（权重已迁移至 dice_reward 表）
     */
    public function add(array $data): mixed
    {
        $result = parent::add($data);
        $deptId = AdminScopeHelper::normalizeRecordDeptId($data['dept_id'] ?? null);
        DiceRewardConfig::refreshCache($deptId);
        return $result;
    }

    /**
     * 修改：按业务 id + 渠道更新；保存后刷新该渠道缓存
     */
    public function edit($id, array $data, ?array $adminInfo = null, $requestDeptId = null): mixed
    {
        $deptId = AdminScopeHelper::resolveConfigDeptId(
            $adminInfo,
            AdminScopeHelper::pickRequestDeptId($requestDeptId, $data)
        );
        ConfigScopeEditHelper::updateByBusinessIdAndDept($this->model, (int) $id, $deptId, $data);
        DiceRewardConfig::refreshCache($deptId);
        return true;
    }

    /**
     * 为列表/分页数据中的 BIGWIN 行附加 weight（来自 DiceReward 缓存）
     */
    public function enrichBigwinWeight(array $listResult): array
    {
        $key = isset($listResult['data']) ? 'data' : (isset($listResult['records']) ? 'records' : null);
        if ($key === null || empty($listResult[$key])) {
            return $listResult;
        }
        $rewardLogic = new DiceRewardLogic();
        foreach ($listResult[$key] as $i => $row) {
            if (isset($row['tier']) && $row['tier'] === 'BIGWIN' && isset($row['grid_number'])) {
                $listResult[$key][$i]['weight'] = $rewardLogic->getBigwinWeightByGridNumber((int) $row['grid_number']);
            }
        }
        return $listResult;
    }

    /** 奖励索引必须为 26 条，id 为 0～25，点数 5～30 各出现一次 */
    private const BATCH_INDEX_COUNT = 26;
    private const INDEX_ID_MIN = 0;
    private const INDEX_ID_MAX = 25;
    private const GRID_NUMBER_MIN = 5;
    private const GRID_NUMBER_MAX = 30;

    /**
     * 校验批量更新项（奖励索引表单独立提交，可能只含非 BIGWIN 的若干条）
     * - 每项必须包含 id、grid_number；grid_number 须在 5～30，提交项内 grid_number 不能重复
     * - 若为 26 条则额外校验：id 为 0～25 各一、grid_number 为 5～30 各一
     * @return string|null 校验失败返回错误信息，通过返回 null
     */
    public function validateBatchUpdateItems(array $items): ?string
    {
        if (count($items) === 0) {
            return '提交数据不能为空';
        }
        $ids = [];
        $gridNumbers = [];
        foreach ($items as $item) {
            if (! array_key_exists('id', $item) || $item['id'] === null || $item['id'] === '') {
                return '每项必须包含 id';
            }
            $id = (int) $item['id'];
            $ids[] = $id;
            if (! array_key_exists('grid_number', $item)) {
                return '每项必须包含 grid_number';
            }
            $gn = (int) $item['grid_number'];
            if ($gn < self::GRID_NUMBER_MIN || $gn > self::GRID_NUMBER_MAX) {
                return '色子点数 grid_number 只能为 ' . self::GRID_NUMBER_MIN . '～' . self::GRID_NUMBER_MAX . '，当前存在 ' . $gn;
            }
            $gridNumbers[] = $gn;
        }
        $gridDuplicates = $this->findDuplicateValues($gridNumbers);
        if ($gridDuplicates !== []) {
            sort($gridDuplicates);
            return '色子点数在本批内不能重复，重复的点数为：' . implode('、', $gridDuplicates);
        }
        $cnt = count($items);
        if ($cnt === self::BATCH_INDEX_COUNT) {
            foreach ($ids as $id) {
                if ($id < self::INDEX_ID_MIN || $id > self::INDEX_ID_MAX) {
                    return '索引 id 只能为 ' . self::INDEX_ID_MIN . '～' . self::INDEX_ID_MAX . '，当前存在 id=' . $id;
                }
            }
            $idDuplicates = $this->findDuplicateValues($ids);
            if ($idDuplicates !== []) {
                sort($idDuplicates);
                return '索引 id 必须为 0～25 各出现一次不能重复，重复的 id 为：' . implode('、', $idDuplicates);
            }
            $requiredIds = range(self::INDEX_ID_MIN, self::INDEX_ID_MAX);
            if (array_diff($requiredIds, $ids) !== [] || array_diff($ids, $requiredIds) !== []) {
                return '索引 id 必须且只能为 0～25 各一个';
            }
            $requiredGrid = range(self::GRID_NUMBER_MIN, self::GRID_NUMBER_MAX);
            if (array_diff($requiredGrid, $gridNumbers) !== [] || array_diff($gridNumbers, $requiredGrid) !== []) {
                return '色子点数必须且只能为 5～30 各一个';
            }
        }
        return null;
    }

    /**
     * 找出数组中出现多于一次的值
     * @param array $arr
     * @return array 重复出现的值（去重）
     */
    private function findDuplicateValues(array $arr): array
    {
        $counts = array_count_values($arr);
        $duplicates = [];
        foreach ($counts as $value => $count) {
            if ($count > 1) {
                $duplicates[] = $value;
            }
        }
        return $duplicates;
    }

    /**
     * 批量更新奖励索引配置：grid_number、ui_text、real_ev、tier、remark（不含 weight，BIGWIN 权重单独接口）
     * @param array $items 每项 [id, grid_number?, ui_text?, real_ev?, tier?, remark?]
     * @param int $deptId 渠道 ID（0=默认模板）
     */
    public function batchUpdate(array $items, int $deptId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT): void
    {
        foreach ($items as $row) {
            if (! array_key_exists('id', $row) || $row['id'] === null || $row['id'] === '') {
                continue;
            }
            $id = (int) $row['id'];
            $data = [];
            foreach (['grid_number', 'ui_text', 'ui_text_en', 'real_ev', 'tier', 'remark'] as $field) {
                if (array_key_exists($field, $row)) {
                    $data[$field] = $row[$field];
                }
            }
            if (! empty($data)) {
                $this->updateByBusinessIdAndDept($id, $deptId, $data);
            }
        }
        DiceRewardConfig::refreshCache($deptId);
    }

    /**
     * 按业务 id（0～25）与渠道更新单条配置
     */
    private function updateByBusinessIdAndDept(int $businessId, int $deptId, array $data): void
    {
        ConfigScopeEditHelper::updateByBusinessIdAndDept($this->model, $businessId, $deptId, $data);
    }

    /**
     * 校验大奖权重提交项：点数 5～30，本批内 grid_number 不能重复
     * @return string|null 校验失败返回错误信息（含重复的点数），通过返回 null
     */
    public function validateBigwinWeightItems(array $items): ?string
    {
        if (count($items) === 0) {
            return '提交数据不能为空';
        }
        $gridNumbers = [];
        foreach ($items as $row) {
            $gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
            if ($gn < self::GRID_NUMBER_MIN || $gn > self::GRID_NUMBER_MAX) {
                return '色子点数 grid_number 只能为 ' . self::GRID_NUMBER_MIN . '～' . self::GRID_NUMBER_MAX . '，当前存在 ' . $gn;
            }
            $gridNumbers[] = $gn;
        }
        $duplicates = $this->findDuplicateValues($gridNumbers);
        if ($duplicates !== []) {
            sort($duplicates);
            return '大奖权重本批内点数不能重复，重复的点数为：' . implode('、', $duplicates);
        }
        return null;
    }

    /**
     * 批量更新 BIGWIN 档位权重（仅写 dice_reward_config 表，不操作 dice_reward）
     * @param array $items 每项 [grid_number => 5-30, weight => 0-10000]
     * @param int $deptId 渠道 ID（0=默认模板）
     */
    public function batchUpdateBigwinWeight(array $items, int $deptId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT): void
    {
        $weightMin = 0;
        $weightMax = 10000;
        foreach ($items as $row) {
            $gridNumber = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
            $weight = isset($row['weight']) ? (int) $row['weight'] : 0;
            if ($gridNumber < 5 || $gridNumber > 30) {
                continue;
            }
            $weight = max($weightMin, min($weightMax, $weight));
            $query = $this->model->where('tier', 'BIGWIN')->where('grid_number', $gridNumber);
            if (AdminScopeHelper::isTemplateDeptId($deptId)) {
                $query->where(function ($q) {
                    $q->where('dept_id', AdminScopeHelper::DEFAULT_TEMPLATE_DEPT)
                        ->whereOr('dept_id', null);
                });
            } else {
                $query->where('dept_id', $deptId);
            }
            $exists = (clone $query)->find();
            if ($exists === null) {
                throw new ApiException('BIGWIN grid_number=' . $gridNumber . ' not found for current channel');
            }
            $query->update(['weight' => $weight]);
        }
        DiceRewardConfig::refreshCache($deptId);
    }

    /**
     * 删除后刷新缓存
     */
    public function destroy($ids, ?array $adminInfo = null, $requestDeptId = null): bool
    {
        $deptId = AdminScopeHelper::resolveConfigDeptId($adminInfo, $requestDeptId);
        $result = parent::destroy($ids);
        if ($result) {
            DiceRewardConfig::refreshCache($deptId);
        }
        return $result;
    }

    /**
     * 按档位分组返回奖励配置列表（仅配置，权重在 dice_reward 表；权重配比请用 DiceRewardLogic::getListGroupedByTierWithDirection）
     */
    public function getListGroupedByTier(): array
    {
        $tiers = ['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'];
        $list = $this->model->whereIn('tier', $tiers)->order('tier')->order('id')->select()->toArray();
        $grouped = [];
        foreach ($tiers as $t) {
            $grouped[$t] = [];
        }
        foreach ($list as $row) {
            $tier = isset($row['tier']) ? (string) $row['tier'] : '';
            if ($tier !== '' && isset($grouped[$tier])) {
                $grouped[$tier][] = $row;
            }
        }
        return $grouped;
    }

    /** 测试时档位权重均为 0 的异常标识 */
    private const EXCEPTION_WEIGHT_ALL_ZERO = 'REWARD_WEIGHT_ALL_ZERO';

    /**
     * 按权重抽取一条配置（与 PlayStartLogic 抽奖逻辑一致，仅 weight>0 参与）
     */
    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);
    }

    /**
     * 按档位权重数组抽取 T1-T5
     */
    private static function drawTierByWeightArray(array $tiers, array $weights): string
    {
        $total = array_sum($weights);
        if ($total <= 0) {
            return $tiers[random_int(0, count($tiers) - 1)];
        }
        $r = random_int(1, (int) $total);
        $acc = 0;
        foreach ($weights as $i => $w) {
            $acc += (int) $w;
            if ($r <= $acc) {
                return $tiers[$i];
            }
        }
        return $tiers[count($tiers) - 1];
    }

    /**
     * 运行权重配比测试：仅按当前配置在内存中模拟 N 次抽奖，统计各 grid_number 落点数量。
     * 不创建任何游玩记录（DicePlayRecord）、不扣券、不写钱包，仅用于验证权重配比效果。
     *
     * @param int $testCount 测试次数 100/500/1000/5000/10000
     * @param bool $saveRecord 是否保存到 dice_reward_config_record（测试记录表，非游玩记录）
     * @param int|null $adminId 执行人管理员ID
     * @param int|null $lotteryConfigId 奖池配置ID（DiceLotteryPoolConfig），用于设定 T1-T5 档位概率；不传则使用 type=0 的配置或均等
     * @return array{counts: array<int,int>, record_id: int|null} counts 为 grid_number=>出现次数
     */
    public function runWeightTest(int $testCount, bool $saveRecord = true, ?int $adminId = null, ?int $lotteryConfigId = null): array
    {
        $allowedCounts = [100, 500, 1000, 5000, 10000];
        if (!in_array($testCount, $allowedCounts, true)) {
            throw new ApiException('Test count only supports 100, 500, 1000, 5000, 10000');
        }

        $grouped = [];
        foreach (['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'] as $t) {
            $grouped[$t] = $this->model::getCachedByTierForDirection($t, 0);
        }
        $tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
        $tierWeights = [1, 1, 1, 1, 1];
        $config = null;
        if ($lotteryConfigId !== null && $lotteryConfigId > 0) {
            $config = DiceLotteryPoolConfig::find($lotteryConfigId);
        }
        if (!$config) {
            $config = DiceLotteryPoolConfig::where('name', 'default')->find();
        }
        if ($config) {
            $tierWeights = [
                (int) ($config->t1_weight ?? 0),
                (int) ($config->t2_weight ?? 0),
                (int) ($config->t3_weight ?? 0),
                (int) ($config->t4_weight ?? 0),
                (int) ($config->t5_weight ?? 0),
            ];
            if (array_sum($tierWeights) <= 0) {
                $tierWeights = [1, 1, 1, 1, 1];
            }
        }

        $counts = [];
        $maxRetry = 20;
        for ($i = 0; $i < $testCount; $i++) {
            $tier = self::drawTierByWeightArray($tiers, $tierWeights);
            $rewards = $grouped[$tier] ?? [];
            if (empty($rewards)) {
                continue;
            }
            $attempt = 0;
            while ($attempt < $maxRetry) {
                try {
                    $chosen = self::drawRewardByWeight($rewards);
                    $gridNumber = isset($chosen['grid_number']) ? (int) $chosen['grid_number'] : 0;
                    if ($gridNumber >= 5 && $gridNumber <= 30) {
                        $counts[$gridNumber] = ($counts[$gridNumber] ?? 0) + 1;
                    }
                    break;
                } catch (\RuntimeException $e) {
                    if ($e->getMessage() === self::EXCEPTION_WEIGHT_ALL_ZERO) {
                        $attempt++;
                        continue;
                    }
                    throw $e;
                }
            }
        }

        $snapshot = [];
        foreach ($grouped as $tierKey => $rows) {
            foreach ($rows as $row) {
                $snapshot[] = [
                    'id'          => (int) ($row['id'] ?? 0),
                    'grid_number' => (int) ($row['grid_number'] ?? 0),
                    'tier'        => (string) ($row['tier'] ?? ''),
                    'weight'      => (int) ($row['weight'] ?? 0),
                ];
            }
        }

        $tierWeightsSnapshot = [
            'T1' => $tierWeights[0] ?? 0,
            'T2' => $tierWeights[1] ?? 0,
            'T3' => $tierWeights[2] ?? 0,
            'T4' => $tierWeights[3] ?? 0,
            'T5' => $tierWeights[4] ?? 0,
        ];
        $recordId = null;
        if ($saveRecord) {
            $record = new DiceRewardConfigRecord();
            $record->test_count = $testCount;
            $record->weight_config_snapshot = $snapshot;
            $record->tier_weights_snapshot = $tierWeightsSnapshot;
            $record->lottery_config_id = $config ? (int) $config->id : null;
            $record->result_counts = $counts;
            $record->admin_id = $adminId;
            if ($adminId > 0) {
                $admin = \plugin\saiadmin\app\model\system\SystemUser::find($adminId);
                if ($admin && !empty($admin->dept_id)) {
                    $record->dept_id = $admin->dept_id;
                }
            }
            $record->create_time = date('Y-m-d H:i:s');
            $record->save();
            $recordId = (int) $record->id;
        }

        return ['counts' => $counts, 'record_id' => $recordId];
    }
}
