<?php
// +----------------------------------------------------------------------
// | saiadmin [ saiadmin快速开发框架 ]
// +----------------------------------------------------------------------
namespace app\dice\logic\reward_config_record;

use app\api\util\ApiLang;
use app\dice\helper\AdminScopeHelper;
use app\dice\helper\ConfigScopeEditHelper;
use app\dice\model\ante_config\DiceAnteConfig;
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
use app\dice\model\reward\DiceReward;
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\app\model\system\SystemUser;

/**
 * 奖励配置权重测试记录逻辑层
 *
 */
class DiceRewardConfigRecordLogic extends DiceBaseLogic
{
    public function __construct()
    {
        $this->model = new DiceRewardConfigRecord();
        // 默认按主键倒序：最新数据优先展示
        $this->setOrderType('DESC');
    }

    /**
     * 分页列表，并为每条记录附加 admin_name（管理员姓名：realname 或 username）
     */
    public function getList($query): mixed
    {
        $result = parent::getList($query);
        if (!is_array($result)) {
            return $result;
        }
        $rows = $result['data'] ?? $result['records'] ?? null;
        if (!is_array($rows) || empty($rows)) {
            return $result;
        }
        $adminIds = array_unique(array_filter(array_column($rows, 'admin_id')));
        $nameMap = $this->getAdminNameMap($adminIds);
        $key = isset($result['data']) ? 'data' : 'records';
        foreach ($result[$key] as &$row) {
            $aid = isset($row['admin_id']) ? (int) $row['admin_id'] : 0;
            $row['admin_name'] = $nameMap[$aid] ?? ($aid > 0 ? '' : '—');
        }
        unset($row);
        return $result;
    }

    /**
     * 根据管理员 ID 列表获取 id => 姓名（realname 优先，否则 username）
     * @param array $adminIds
     * @return array<int, string>
     */
    private function getAdminNameMap(array $adminIds): array
    {
        if (empty($adminIds)) {
            return [];
        }
        $list = SystemUser::whereIn('id', $adminIds)->field('id,realname,username')->select()->toArray();
        $map = [];
        foreach ($list as $user) {
            $user = is_array($user) ? $user : (array) $user;
            $id = (int) ($user['id'] ?? 0);
            $name = trim((string) ($user['realname'] ?? ''));
            if ($name === '') {
                $name = trim((string) ($user['username'] ?? ''));
            }
            $map[$id] = $name !== '' ? $name : (string) $id;
        }
        return $map;
    }

    /**
     * 将测试记录导入：DiceReward（权重快照）、DiceRewardConfig（BIGWIN weight）、DiceLotteryPoolConfig（付费/免费 T1-T5）
     * @param int $recordId 测试记录 ID
     * @param int|null $paidLotteryConfigId 导入付费档位概率到的奖池（type=0），不传则用记录 paid_lottery_config_id
     * @param int|null $freeLotteryConfigId 导入免费档位概率到的奖池（type=1），不传则用记录 free_lottery_config_id
     * @param int|null $lotteryConfigId 兼容旧版：不传 paid/free 时用作统一奖池
     */
    public function importFromRecord(int $recordId, ?int $paidLotteryConfigId = null, ?int $freeLotteryConfigId = null, ?int $lotteryConfigId = null): void
    {
        $record = $this->model->find($recordId);
        if (!$record) {
            throw new ApiException('Test record not found');
        }
        $record = is_array($record) ? $record : $record->toArray();
        $configDeptId = AdminScopeHelper::normalizeRecordDeptId($record['dept_id'] ?? null);

        $snapshot = $record['weight_config_snapshot'] ?? null;
        if (is_string($snapshot)) {
            $snapshot = json_decode($snapshot, true);
        }
        if (is_array($snapshot) && !empty($snapshot)) {
            foreach ($snapshot as $item) {
                $direction = isset($item['direction']) ? (int) $item['direction'] : null;
                $gridNumber = isset($item['grid_number']) ? (int) $item['grid_number'] : 0;
                $weight = isset($item['weight']) ? (int) $item['weight'] : 1;
                $weight = max(1, min(10000, $weight));
                if (!in_array($direction, [DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE], true)) {
                    continue;
                }
                if ($gridNumber <= 0) {
                    continue;
                }
                $tier = isset($item['tier']) ? (string) $item['tier'] : '';
                if ($tier === '') {
                    // 若快照中未带 tier，则尝试按方向+点数从现有配置中取
                    $tierFromDb = DiceReward::where('direction', $direction)->where('grid_number', $gridNumber)->value('tier');
                    $tier = $tierFromDb !== null ? (string) $tierFromDb : '';
                }
                // 仅按方向 + 点数更新 DiceReward（若存在则更新，不存在才插入，避免唯一键冲突）
                $rewardQuery = DiceReward::where('direction', $direction)->where('grid_number', $gridNumber);
                ConfigScopeEditHelper::applyDeptIdWhere($rewardQuery, $configDeptId);
                $reward = $rewardQuery->find();
                if ($reward) {
                    $reward->weight = $weight;
                    // 若快照中有 tier，补齐 tier 信息
                    if ($tier !== '' && (string) $reward->tier !== $tier) {
                        $reward->tier = $tier;
                    }
                    $reward->save();
                } else {
                    $m = new DiceReward();
                    if ($tier !== '') {
                        $m->tier = $tier;
                    }
                    $m->direction = $direction;
                    $m->grid_number = $gridNumber;
                    $m->weight = $weight;
                    if (!AdminScopeHelper::isTemplateDeptId($configDeptId)) {
                        $m->dept_id = $configDeptId;
                    }
                    $m->save();
                }
            }
            DiceReward::refreshCache($configDeptId);
        }

        // 使用记录中的 bigwin_weight JSON 将 BIGWIN 概率导入到 DiceRewardConfig
        $recordBigwinWeight = $record['bigwin_weight'] ?? null;
        if (is_string($recordBigwinWeight)) {
            $decoded = json_decode($recordBigwinWeight, true);
            $recordBigwinWeight = is_array($decoded) ? $decoded : null;
        }
        if (is_array($recordBigwinWeight) && !empty($recordBigwinWeight)) {
            foreach ($recordBigwinWeight as $grid => $w) {
                $gridNumber = (int) $grid;
                $weight = (int) $w;
                if ($gridNumber <= 0) {
                    continue;
                }
                if ($weight < 0) {
                    $weight = 0;
                }
                $bigwinQuery = DiceRewardConfig::where('tier', 'BIGWIN')->where('grid_number', $gridNumber);
                ConfigScopeEditHelper::applyDeptIdWhere($bigwinQuery, $configDeptId);
                $bigwinQuery->update(['weight' => $weight]);
            }
            DiceRewardConfig::refreshCache($configDeptId);
        }

        $tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
        $tierSnapshot = $record['tier_weights_snapshot'] ?? null;
        if (is_string($tierSnapshot)) {
            $tierSnapshot = json_decode($tierSnapshot, true);
        }
        $paidWeights = $record['paid_tier_weights'] ?? null;
        if (is_string($paidWeights)) {
            $paidWeights = json_decode($paidWeights, true);
        }
        $freeWeights = $record['free_tier_weights'] ?? null;
        if (is_string($freeWeights)) {
            $freeWeights = json_decode($freeWeights, true);
        }
        $fallbackLotteryId = $lotteryConfigId > 0 ? $lotteryConfigId : (isset($record['lottery_config_id']) && (int) $record['lottery_config_id'] > 0 ? (int) $record['lottery_config_id'] : null);
        $paidTargetId = $paidLotteryConfigId > 0 ? $paidLotteryConfigId : ($fallbackLotteryId ?? (isset($record['paid_lottery_config_id']) && (int) $record['paid_lottery_config_id'] > 0 ? (int) $record['paid_lottery_config_id'] : null));
        $freeTargetId = $freeLotteryConfigId > 0 ? $freeLotteryConfigId : (isset($record['free_lottery_config_id']) && (int) $record['free_lottery_config_id'] > 0 ? (int) $record['free_lottery_config_id'] : null);

        // tier_weights_snapshot 新结构：['paid' => [...], 'free' => [...]]
        $snapshotPaid = null;
        $snapshotFree = null;
        if (is_array($tierSnapshot) && !empty($tierSnapshot)) {
            if (array_key_exists('paid', $tierSnapshot) || array_key_exists('free', $tierSnapshot)) {
                if (isset($tierSnapshot['paid']) && is_array($tierSnapshot['paid'])) {
                    $snapshotPaid = $tierSnapshot['paid'];
                }
                if (isset($tierSnapshot['free']) && is_array($tierSnapshot['free'])) {
                    $snapshotFree = $tierSnapshot['free'];
                }
            } else {
                // 兼容旧结构：直接就是一个 T1-T5 的数组，视为付费
                $snapshotPaid = $tierSnapshot;
            }
        }

        $paidData = is_array($paidWeights) && !empty($paidWeights) ? $paidWeights : $snapshotPaid;
        $freeData = is_array($freeWeights) && !empty($freeWeights) ? $freeWeights : $snapshotFree;

        if (is_array($paidData) && $paidTargetId > 0) {
            $pool = DiceLotteryPoolConfig::find($paidTargetId);
            if (!$pool) {
                throw new ApiException('Paid pool config not found');
            }
            $update = [
                't1_weight' => (int) ($paidData['T1'] ?? $paidData['t1'] ?? 0),
                't2_weight' => (int) ($paidData['T2'] ?? $paidData['t2'] ?? 0),
                't3_weight' => (int) ($paidData['T3'] ?? $paidData['t3'] ?? 0),
                't4_weight' => (int) ($paidData['T4'] ?? $paidData['t4'] ?? 0),
                't5_weight' => (int) ($paidData['T5'] ?? $paidData['t5'] ?? 0),
            ];
            DiceLotteryPoolConfig::where('id', $paidTargetId)->update($update);
        }
        if (is_array($freeData) && $freeTargetId > 0) {
            $pool = DiceLotteryPoolConfig::find($freeTargetId);
            if (!$pool) {
                throw new ApiException('Free pool config not found');
            }
            $update = [
                't1_weight' => (int) ($freeData['T1'] ?? $freeData['t1'] ?? 0),
                't2_weight' => (int) ($freeData['T2'] ?? $freeData['t2'] ?? 0),
                't3_weight' => (int) ($freeData['T3'] ?? $freeData['t3'] ?? 0),
                't4_weight' => (int) ($freeData['T4'] ?? $freeData['t4'] ?? 0),
                't5_weight' => (int) ($freeData['T5'] ?? $freeData['t5'] ?? 0),
            ];
            DiceLotteryPoolConfig::where('id', $freeTargetId)->update($update);
        }

        DiceRewardConfig::refreshCache($configDeptId);
        DiceReward::refreshCache($configDeptId);
        DiceRewardConfig::clearRequestInstance();
    }

    /**
     * 创建一键测试权重记录并返回 ID，供后台执行器写入 dice_play_record_test
     * 支持两种模式：1）选择奖池配置 lottery_config_id，档位概率取自配置；2）不选配置，使用自定义 paid_tier_weights / free_tier_weights
     * @param array|int $params 数组：lottery_config_id(可选), paid_s_count, paid_n_count
     * @param int|null $adminId 执行人
     * @return int 记录 ID
     * @throws ApiException
     */
    public function createWeightTestRecord(
        array|int $params,
        mixed $adminIdOrFreeS = null,
        ?array $adminInfo = null,
        $requestDeptId = null
    ): int {
        $adminId = null;
        if (!is_array($params)) {
            // 兼容旧版调用：createWeightTestRecord(paid_s_count, paid_n_count)
            $params = [
                'paid_s_count'  => (int) $params,
                'paid_n_count'  => (int) $adminIdOrFreeS,
            ];
        } else {
            $adminId = $adminIdOrFreeS !== null && $adminIdOrFreeS !== '' ? (int) $adminIdOrFreeS : null;
        }

        $deptId = $this->resolveWeightTestDeptId(
            $adminInfo,
            AdminScopeHelper::pickRequestDeptId($requestDeptId, is_array($params) ? $params : []),
            is_array($params) ? $params : []
        );
        $allowed = [100, 500, 1000, 5000];
        $ante = $this->resolveWeightTestAnte($params, $deptId);

        $lotteryConfigId = isset($params['lottery_config_id']) ? (int) $params['lottery_config_id'] : 0;
        $paidConfigId = isset($params['paid_lottery_config_id']) ? (int) $params['paid_lottery_config_id'] : 0;
        $freeConfigId = isset($params['free_lottery_config_id']) ? (int) $params['free_lottery_config_id'] : 0;
        if ($paidConfigId <= 0 && $lotteryConfigId > 0) {
            $paidConfigId = $lotteryConfigId;
        }
        if ($freeConfigId <= 0 && $lotteryConfigId > 0) {
            $freeConfigId = $lotteryConfigId;
        }
        $paidS = isset($params['paid_s_count']) ? (int) $params['paid_s_count'] : 0;
        $paidN = isset($params['paid_n_count']) ? (int) $params['paid_n_count'] : 0;
        $chainFreeMode = !empty($params['chain_free_mode']);
        $killModeEnabled = !empty($params['kill_mode_enabled']);
        if (array_key_exists('test_safety_line', $params) && $params['test_safety_line'] !== null && $params['test_safety_line'] !== '') {
            $testSafetyLine = (int) $params['test_safety_line'];
        } else {
            $defaultPool = DiceLotteryPoolConfig::findByNameForDept('default', $deptId);
            $testSafetyLine = (int) ($defaultPool->safety_line ?? 0);
        }
        if ($testSafetyLine < 0) {
            throw new ApiException('test_safety_line must be greater than or equal to 0');
        }

        foreach ([$paidS, $paidN] as $c) {
            if ($c !== 0 && !in_array($c, $allowed, true)) {
                throw new ApiException('Counts only support 0, 100, 500, 1000, 5000');
            }
        }
        $total = $paidS + $paidN;
        if ($total <= 0) {
            throw new ApiException('Sum of paid direction counts must be greater than 0');
        }

        $snapshot = [];
        // 档位权重快照：区分付费/免费，结构为 ['paid' => [...], 'free' => [...]]
        $tierWeightsSnapshot = [
            'paid' => null,
            'free' => null,
        ];
        $paidTierWeights = null;
        $freeTierWeights = null;

        // 来自当前渠道的 DiceReward 权重快照（按方向+点数），用于权重测试模拟
        $instance = DiceReward::getCachedInstance($deptId);
        $byTierDirection = $instance['by_tier_direction'] ?? [];
        foreach ($byTierDirection as $tier => $byDir) {
            foreach ($byDir as $dir => $rows) {
                foreach ($rows as $row) {
                    $snapshot[] = [
                        // 不再记录 DiceReward.id，只记录方向、点数和、档位与权重
                        'direction'   => (int) $dir,
                        'grid_number' => (int) ($row['grid_number'] ?? 0),
                        'tier'        => (string) ($tier ?? ''),
                        'weight'      => (int) ($row['weight'] ?? 0),
                    ];
                }
            }
        }

        // BIGWIN 概率快照从 DiceRewardConfig 读取（例如豹子号配置）
        // JSON 结构 {"grid_number": weight, ...}
        $bigwinWeights = [];
        $bigwinConfigs = DiceRewardConfig::getCachedByTier('BIGWIN', $deptId);
        foreach ($bigwinConfigs as $cfg) {
            $grid = isset($cfg['grid_number']) ? (int) $cfg['grid_number'] : 0;
            if ($grid <= 0) {
                continue;
            }
            $w = isset($cfg['weight']) ? (int) $cfg['weight'] : 0;
            $bigwinWeights[$grid] = $w;
        }

        if ($paidConfigId > 0) {
            $config = $this->findPoolConfigInDept($paidConfigId, $deptId, 'Paid pool config not found');
            $tierWeightsSnapshot['paid'] = [
                    'T1' => (int) ($config->t1_weight ?? 0),
                    'T2' => (int) ($config->t2_weight ?? 0),
                    'T3' => (int) ($config->t3_weight ?? 0),
                    'T4' => (int) ($config->t4_weight ?? 0),
                    'T5' => (int) ($config->t5_weight ?? 0),
            ];
        } else {
            $paidTierWeights = $params['paid_tier_weights'] ?? null;
            if (!is_array($paidTierWeights)) {
                throw new ApiException('When paid pool is not selected, please fill paid custom tier probabilities (T1–T5)');
            }
            $tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
            foreach ($tiers as $t) {
                $v = (int) ($paidTierWeights[$t] ?? 0);
                if ($v < 0 || $v > 100) {
                    throw new ApiException('Paid tier probability must be between 0 and 100%');
                }
                $paidTierWeights[$t] = $v;
            }
            $paidSum = array_sum(array_intersect_key($paidTierWeights, array_flip($tiers)));
            if ($paidSum > 100) {
                throw new ApiException('Paid tier probabilities (T1–T5) sum cannot exceed 100%');
            }
            $tierWeightsSnapshot['paid'] = $paidTierWeights;
        }

        if ($freeConfigId > 0) {
            $config = $this->findPoolConfigInDept($freeConfigId, $deptId, 'Free pool config not found');
            $tierWeightsSnapshot['free'] = [
                    'T1' => (int) ($config->t1_weight ?? 0),
                    'T2' => (int) ($config->t2_weight ?? 0),
                    'T3' => (int) ($config->t3_weight ?? 0),
                    'T4' => (int) ($config->t4_weight ?? 0),
                    'T5' => (int) ($config->t5_weight ?? 0),
            ];
        } else {
            $freeTierWeights = $params['free_tier_weights'] ?? null;
            if (!is_array($freeTierWeights)) {
                throw new ApiException('When free pool is not selected, please fill free custom tier probabilities (T1–T5)');
            }
            $tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
            foreach ($tiers as $t) {
                $v = (int) ($freeTierWeights[$t] ?? 0);
                if ($v < 0 || $v > 100) {
                    throw new ApiException('Free tier probability must be between 0 and 100%');
                }
                $freeTierWeights[$t] = $v;
            }
            $freeSum = array_sum(array_intersect_key($freeTierWeights, array_flip($tiers)));
            if ($freeSum > 100) {
                throw new ApiException('Free tier probabilities (T1–T5) sum cannot exceed 100%');
            }
            $tierWeightsSnapshot['free'] = $freeTierWeights;
        }

        // 兼容：若某一侧未配置，则保存为空数组，方便前端直接解构
        if (!is_array($tierWeightsSnapshot['paid'])) {
            $tierWeightsSnapshot['paid'] = [];
        }
        if (!is_array($tierWeightsSnapshot['free'])) {
            $tierWeightsSnapshot['free'] = [];
        }
        if ($chainFreeMode) {
            $tierWeightsSnapshot['chain_free_mode'] = true;
        }
        if (!empty($params['ante_random'])) {
            $tierWeightsSnapshot['ante_random'] = true;
        }

        $record = new DiceRewardConfigRecord();
        $plannedPaidSpins = $paidS + $paidN;
        $record->chain_free_mode = $chainFreeMode ? 1 : 0;
        $record->kill_mode_enabled = $killModeEnabled ? 1 : 0;
        $record->test_safety_line = $testSafetyLine;
        $record->paid_planned_spins = $plannedPaidSpins;
        // 总抽奖次数与 test_count 仅在任务成功结束时写入（见 WeightTestRunner::markSuccess）
        $record->test_count = 0;
        $record->total_play_count = 0;
        $record->weight_config_snapshot = $snapshot;
        $record->tier_weights_snapshot = $tierWeightsSnapshot;
        $record->lottery_config_id = $lotteryConfigId > 0 ? $lotteryConfigId : null;
        $record->paid_lottery_config_id = $paidConfigId > 0 ? $paidConfigId : null;
        $record->free_lottery_config_id = $freeConfigId > 0 ? $freeConfigId : null;
        $record->over_play_count = 0;
        $record->status = DiceRewardConfigRecord::STATUS_RUNNING;
        $record->remark = null;
        $record->paid_s_count = $paidS;
        $record->paid_n_count = $paidN;
        $record->play_again_count = 0;
        $record->paid_tier_weights = $paidTierWeights;
        $record->free_tier_weights = $freeTierWeights;
        $record->result_counts = [];
        $record->tier_counts = null;
        $record->bigwin_weight = $bigwinWeights ?: null;
        $record->ante = $ante;
        $record->admin_id = $adminId;
        $record->dept_id = $deptId;
        $record->create_time = date('Y-m-d H:i:s');
        $record->save();

        return (int) $record->id;
    }

    /**
     * 解析一键测试所属渠道：请求 dept_id > 奖池配置 dept_id > 渠道管理员本渠道
     */
    private function resolveWeightTestDeptId(?array $adminInfo, $requestDeptId, array $params): int
    {
        $deptId = AdminScopeHelper::resolveConfigDeptId($adminInfo, $requestDeptId);
        if (! AdminScopeHelper::isTemplateDeptId($deptId)) {
            return $deptId;
        }

        foreach (['paid_lottery_config_id', 'free_lottery_config_id', 'lottery_config_id'] as $key) {
            $poolId = isset($params[$key]) ? (int) $params[$key] : 0;
            if ($poolId <= 0) {
                continue;
            }
            $pool = DiceLotteryPoolConfig::find($poolId);
            if (! $pool) {
                continue;
            }
            $poolDeptId = AdminScopeHelper::normalizeRecordDeptId($pool->dept_id ?? null);
            if (! AdminScopeHelper::isTemplateDeptId($poolDeptId)) {
                return $poolDeptId;
            }
        }

        $scopeDeptId = AdminScopeHelper::getDeptId($adminInfo);
        if ($scopeDeptId !== null && $scopeDeptId > 0) {
            return $scopeDeptId;
        }

        return $deptId;
    }

    /**
     * 校验奖池配置属于当前渠道
     */
    private function findPoolConfigInDept(int $poolId, int $deptId, string $notFoundMsg): DiceLotteryPoolConfig
    {
        $config = DiceLotteryPoolConfig::find($poolId);
        if (!$config) {
            throw new ApiException($notFoundMsg);
        }
        $poolDeptId = AdminScopeHelper::normalizeRecordDeptId($config->dept_id ?? null);
        if ($poolDeptId !== $deptId) {
            throw new ApiException('POOL_CONFIG_NOT_IN_CHANNEL');
        }
        return $config;
    }

    /**
     * 解析一键测试底注：优先 ante_config_id，否则按 mult + 渠道校验
     */
    private function resolveWeightTestAnte(array $params, int $deptId): int
    {
        if (!empty($params['ante_random'])) {
            $anteQuery = DiceAnteConfig::field('id,mult')->order('mult', 'asc');
            ConfigScopeEditHelper::applyDeptIdWhere($anteQuery, $deptId);
            $rows = $anteQuery->select()->toArray();
            if ($rows === []) {
                throw new ApiException('No ante config in current channel');
            }
            $picked = $rows[random_int(0, count($rows) - 1)];
            $mult = (int) ($picked['mult'] ?? 0);
            if ($mult <= 0) {
                throw new ApiException('ANTE_MUST_POSITIVE');
            }
            return $mult;
        }

        $anteConfigId = isset($params['ante_config_id']) ? (int) $params['ante_config_id'] : 0;
        if ($anteConfigId > 0) {
            $config = DiceAnteConfig::find($anteConfigId);
            if (! $config) {
                throw new ApiException('ANTE_CONFIG_NOT_FOUND');
            }
            $configDeptId = AdminScopeHelper::normalizeRecordDeptId($config->dept_id ?? null);
            if ($configDeptId !== $deptId) {
                throw new ApiException('ANTE_CONFIG_NOT_IN_CHANNEL');
            }
            $mult = (int) ($config->mult ?? 0);
            if ($mult <= 0) {
                throw new ApiException('ANTE_MUST_POSITIVE');
            }
            return $mult;
        }

        $ante = isset($params['ante']) ? (int) $params['ante'] : 0;
        if ($ante <= 0) {
            throw new ApiException('ANTE_MUST_POSITIVE');
        }
        $anteQuery = DiceAnteConfig::where('mult', $ante);
        ConfigScopeEditHelper::applyDeptIdWhere($anteQuery, $deptId);
        if ($anteQuery->count() <= 0) {
            throw new ApiException(ApiLang::translateParams('ANTE_NOT_ALLOWED', [$ante]));
        }

        return $ante;
    }
}
