<?php
declare(strict_types=1);

namespace app\api\controller\v1;

use app\api\logic\UserLogic;
use app\api\util\ReturnCode;
use app\dice\model\game\DiceGame;
use app\dice\model\player\DicePlayer;
use app\dice\model\play_record\DicePlayRecord;
use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
use support\think\Db;
use app\api\controller\BaseController;
use support\Request;
use support\Response;
use app\api\cache\UserCache;

/**
 * 平台 v1 游戏接口
 * 请求头：auth-token
 */
class GameController extends BaseController
{
    /** 游戏记录 / 钱包流水 / 中奖券记录等拉取类接口的条数上限（与对接文档一致） */
    private const PLAYER_PULL_RECORD_MAX_LIMIT = 2000;

    private const PLAYER_PULL_RECORD_DEFAULT_LIMIT = 20;

    /** 拉取类流水仅允许查询「当前时间起向前」的最大天数（含起止） */
    private const PLAYER_PULL_RECORD_MAX_RANGE_DAYS = 7;

    private const GAME_PUBLIC_FIELDS = [
        'provider',
        'provider_code',
        'game_code',
        'game_key',
        'game_type',
        'logo',
        'game_url',
        'hall_url',
        'status',
        'sort',
    ];

    /** getPlayerInfo 仅查询需返回的列，减轻 IO（敏感/内部字段不入库查询） */
    private const PLAYER_INFO_DB_FIELDS = [
        'id', 'username', 'phone', 'uid', 'name', 'status', 'coin', 'is_up', 'admin_id',
        'total_ticket_count', 'paid_ticket_count', 'free_ticket_count', 'free_ticket',
        'total_win_coin',
        'create_time', 'update_time',
    ];

    /**
     * 获取游戏列表
     * POST 参数：lang（可选，zh/en，默认 zh）
     * 当前返回启用中的游戏列表
     */
    public function getGameList(Request $request): Response
    {
        $lang = $this->resolveLang($request->post('lang', 'zh'));
        $games = $this->buildPublicGameList($lang, $this->agentDeptId($request));
        return $this->success([
            'game_list' => $games,
        ]);
    }

    /**
     * 获取游戏大厅信息（脱敏）
     * POST 参数：lang（可选，zh/en，默认 zh）
     */
    public function getGameHall(Request $request): Response
    {
        $lang = $this->resolveLang($request->post('lang', 'zh'));
        $games = $this->buildPublicGameList($lang, $this->agentDeptId($request));
        $hallUrl = '';
        if (!empty($games)) {
            $hallUrl = $games[0]['hall_url'] ?? '';
        }
        return $this->success([
            'provider' => 'Dicey Fun',
            'provider_code' => 'DF',
            'hall_url' => $hallUrl,
            'game_list' => $games,
        ]);
    }

    /**
     * 获取游戏地址
     * 根据 username 创建登录 token（JWT），拼接游戏地址返回
     */
    public function getGameUrl(Request $request): Response
    {
        $username = trim((string) ($request->post('username', '')));
        $time = trim((string) ($request->post('time', '')));

        if ($username === '') {
            return $this->fail('username is required', ReturnCode::PARAMS_ERROR);
        }
        if ($time === '') {
            $time = (string) time();
        }

        $deptId = $this->agentDeptId($request);
        $adminId = $this->agentAdminId($request);
        $adminIdsInTopDept = UserLogic::getAdminIdsByAgentIdTopDept(trim((string) ($request->agent_id ?? '')));

        $lang = trim((string) ($request->post('lang', 'zh')));
        $lang = in_array($lang, ['en', 'zh'], true) ? $lang : 'zh';

        try {
            $logic = new UserLogic();
            // 平台 v1 已通过 api-key + auth-token 双重校验，此处不再做 password 校验
            $result = $logic->loginByUsername($username, '', $lang, 0.0, $time, $adminId, $adminIdsInTopDept, $deptId, true);
        } catch (\plugin\saiadmin\exception\ApiException $e) {
            return $this->fail($e->getMessage(), ReturnCode::PARAMS_ERROR);
        }

        $gameUrlBase = rtrim(config('api.game_url', 'dice-game.h55555game.top'), '/');
        $tokenInUrl = str_replace('%3D', '=', urlencode($result['token']));
        $url = $gameUrlBase . '/?token=' . $tokenInUrl . '&lang=' . $lang;

        return $this->success([
            'url' => $url,
        ]);
    }

    /**
     * 获取用户信息
     * 参数：username（POST JSON / 表单 / Query 均可，input 合并读取降低偶发空参）
     * 返回 DicePlayer 中非敏感信息；短期 Redis 快照 + 窄字段查询降低延迟
     */
    public function getPlayerInfo(Request $request): Response
    {
        $usernameRaw = $request->input('username', '');
        $username = is_string($usernameRaw) ? trim($usernameRaw) : '';
        $deptId = $this->agentDeptId($request);

        if ($username === '') {
            return $this->fail('username is required', ReturnCode::PARAMS_ERROR);
        }

        $cached = UserCache::getPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username));
        if ($cached !== null) {
            return $this->success($cached);
        }

        try {
            $logic = new UserLogic();
            $player = $logic->findOrCreatePlayerByUsername(
                $username,
                $this->agentAdminId($request),
                $deptId > 0 ? $deptId : null
            );
        } catch (\plugin\saiadmin\exception\ApiException $e) {
            return $this->fail($e->getMessage(), ReturnCode::PARAMS_ERROR);
        }

        $player = DicePlayer::field(self::PLAYER_INFO_DB_FIELDS)->where('id', (int) $player->id)->find();
        if (!$player) {
            return $this->fail('User not found', ReturnCode::PARAMS_ERROR);
        }

        $hidden = ['password', 'lottery_config_id', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight', 'delete_time'];
        $info = $player->hidden($hidden)->toArray();
        UserCache::setPlayerInfoSnapshotByUsername($this->scopedUsername($deptId, $username), $info);

        return $this->success($info);
    }

    /**
     * 解析拉取类流水接口的 limit（与 getPlayerGameRecord / getPlayerWalletRecord / getPlayerTicketRecord 共用）
     */
    private function resolvePullRecordLimit(Request $request): int
    {
        $limit = (int) $request->post('limit', self::PLAYER_PULL_RECORD_DEFAULT_LIMIT);
        if ($limit < 1 || $limit > self::PLAYER_PULL_RECORD_MAX_LIMIT) {
            $limit = self::PLAYER_PULL_RECORD_DEFAULT_LIMIT;
        }

        return $limit;
    }

    /**
     * 将 start_create_time / end_create_time 规范为「最近 7 天内」且跨度不超过 7 天的闭区间。
     *
     * @return array{ok: true, start: string, end: string}|array{ok: false, message: string}
     */
    private function resolvePullRecordTimeWindow(string $startRaw, string $endRaw): array
    {
        $nowTs = time();
        $weekAgoTs = strtotime('-' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $nowTs);
        if ($weekAgoTs === false) {
            $weekAgoTs = $nowTs - self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
        }
        $nowStr = date('Y-m-d H:i:s', $nowTs);
        $weekAgoStr = date('Y-m-d H:i:s', $weekAgoTs);

        if ($startRaw === '' && $endRaw === '') {
            return ['ok' => true, 'start' => $weekAgoStr, 'end' => $nowStr];
        }

        $startTs = $startRaw === '' ? null : strtotime($startRaw);
        $endTs = $endRaw === '' ? null : strtotime($endRaw);

        if ($startRaw !== '' && $startTs === false) {
            return ['ok' => false, 'message' => 'Invalid start_create_time'];
        }
        if ($endRaw !== '' && $endTs === false) {
            return ['ok' => false, 'message' => 'Invalid end_create_time'];
        }

        if ($startRaw === '' && $endRaw !== '' && $endTs !== null) {
            $endTs = min($endTs, $nowTs);
            if ($endTs < $weekAgoTs) {
                return ['ok' => false, 'message' => 'end_create_time must be within the last 7 days'];
            }
            $startTs = strtotime('-' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $endTs);
            if ($startTs === false) {
                $startTs = $endTs - self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
            }
            if ($startTs < $weekAgoTs) {
                $startTs = $weekAgoTs;
            }

            return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)];
        }

        if ($startRaw !== '' && $startTs !== null && $endRaw === '') {
            if ($startTs > $nowTs) {
                return ['ok' => false, 'message' => 'start_create_time cannot be in the future'];
            }
            if ($startTs < $weekAgoTs) {
                return ['ok' => false, 'message' => 'start_create_time cannot be earlier than 7 days ago'];
            }
            $endTs = strtotime('+' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $startTs);
            if ($endTs === false) {
                $endTs = $startTs + self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
            }
            $endTs = min($endTs, $nowTs);

            return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)];
        }

        if ($startTs !== null && $endTs !== null) {
            if ($endTs > $nowTs) {
                $endTs = $nowTs;
            }
            if ($startTs > $endTs) {
                return ['ok' => false, 'message' => 'start_create_time cannot be after end_create_time'];
            }
            if ($startTs < $weekAgoTs) {
                return ['ok' => false, 'message' => 'start_create_time cannot be earlier than 7 days ago'];
            }
            if ($endTs < $weekAgoTs) {
                return ['ok' => false, 'message' => 'end_create_time must be within the last 7 days'];
            }
            $maxEndForStart = strtotime('+' . self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS . ' days', $startTs);
            if ($maxEndForStart === false) {
                $maxEndForStart = $startTs + self::PLAYER_PULL_RECORD_MAX_RANGE_DAYS * 86400;
            }
            if ($endTs > $maxEndForStart) {
                return ['ok' => false, 'message' => 'Time range cannot exceed 7 days'];
            }

            return ['ok' => true, 'start' => date('Y-m-d H:i:s', $startTs), 'end' => date('Y-m-d H:i:s', $endTs)];
        }

        return ['ok' => false, 'message' => 'Invalid time parameters'];
    }

    /**
     * 获取游戏记录
     * POST 参数：username（非必填）, start_create_time, end_create_time（可选，须在「最近 7 天」内且跨度不超过 7 天；均不传则默认最近 7 天）, limit（非必填，返回条数上限）
     * 返回 DicePlayRecord 中非敏感信息
     */
    public function getPlayerGameRecord(Request $request): Response
    {
        $username = trim((string) ($request->post('username', '')));
        $deptId = $this->agentDeptId($request);
        $startCreateTime = trim((string) ($request->post('start_create_time', '')));
        $endCreateTime = trim((string) ($request->post('end_create_time', '')));
        $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
        if (!$window['ok']) {
            return $this->fail($window['message'], ReturnCode::PARAMS_ERROR);
        }
        $limit = $this->resolvePullRecordLimit($request);

        $query = DicePlayRecord::where('dept_id', $deptId)->order('id', 'desc');

        if ($username !== '') {
            $player = $this->findPlayerByUsername($username, $deptId);
            if (!$player) {
                return $this->success([]);
            }
            $query->where('player_id', (int) $player->id);
        }

        $query->where('create_time', '>=', $window['start']);
        $query->where('create_time', '<=', $window['end']);

        $list = $query->limit($limit)->select()->toArray();
        $playerIds = array_unique(array_column($list, 'player_id'));
        if (!empty($playerIds)) {
            $players = DicePlayer::whereIn('id', $playerIds)->where('dept_id', $deptId)->field('id,username,phone')->select()->toArray();
            $playerMap = [];
            foreach ($players as $p) {
                $playerMap[(int) ($p['id'] ?? 0)] = $p;
            }
            foreach ($list as &$item) {
                $item['dice_player'] = $playerMap[(int) ($item['player_id'] ?? 0)] ?? null;
            }
        }

        return $this->success($list);
    }

    /**
     * 获取钱包流水
     * POST 参数：username（非必填）, start_create_time, end_create_time（可选，规则同 getPlayerGameRecord）, limit（非必填）
     * 返回 DicePlayerWalletRecord 中非敏感信息
     */
    public function getPlayerWalletRecord(Request $request): Response
    {
        $username = trim((string) ($request->post('username', '')));
        $deptId = $this->agentDeptId($request);
        $startCreateTime = trim((string) ($request->post('start_create_time', '')));
        $endCreateTime = trim((string) ($request->post('end_create_time', '')));
        $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
        if (!$window['ok']) {
            return $this->fail($window['message'], ReturnCode::PARAMS_ERROR);
        }
        $limit = $this->resolvePullRecordLimit($request);

        $query = DicePlayerWalletRecord::where('dept_id', $deptId)->order('id', 'desc');

        if ($username !== '') {
            $player = $this->findPlayerByUsername($username, $deptId);
            if (!$player) {
                return $this->success([]);
            }
            $query->where('player_id', (int) $player->id);
        }

        $query->where('create_time', '>=', $window['start']);
        $query->where('create_time', '<=', $window['end']);

        $list = $query->with(['dicePlayer' => function ($q) {
            $q->field('id,username,phone');
        }])->limit($limit)->select()->toArray();

        return $this->success($list);
    }

    /**
     * 获取用户中奖券获取记录
     * POST 参数：username（非必填）, start_create_time, end_create_time（可选，规则同 getPlayerGameRecord）, limit（非必填）
     * 返回 DicePlayerTicketRecord 中非敏感信息
     */
    public function getPlayerTicketRecord(Request $request): Response
    {
        $username = trim((string) ($request->post('username', '')));
        $deptId = $this->agentDeptId($request);
        $startCreateTime = trim((string) ($request->post('start_create_time', '')));
        $endCreateTime = trim((string) ($request->post('end_create_time', '')));
        $window = $this->resolvePullRecordTimeWindow($startCreateTime, $endCreateTime);
        if (!$window['ok']) {
            return $this->fail($window['message'], ReturnCode::PARAMS_ERROR);
        }
        $limit = $this->resolvePullRecordLimit($request);

        $query = DicePlayerTicketRecord::where('dept_id', $deptId)->order('id', 'desc');

        if ($username !== '') {
            $player = $this->findPlayerByUsername($username, $deptId);
            if (!$player) {
                return $this->success([]);
            }
            $query->where('player_id', (int) $player->id);
        }

        $query->where('create_time', '>=', $window['start']);
        $query->where('create_time', '<=', $window['end']);

        $list = $query->with(['dicePlayer' => function ($q) {
            $q->field('id,username,phone');
        }])->limit($limit)->select()->toArray();

        return $this->success($list);
    }

    /**
     * 钱包转入转出
     * POST 参数：username（必填）, coin（转入>0 或 转出<0）
     * 创建 DicePlayerWalletRecord，type: 0=充值(coin>0), 1=提现(coin<0)
     * 返回创建的记录
     */
    public function setPlayerWallet(Request $request): Response
    {
        $username = trim((string) ($request->post('username', '')));
        $deptId = $this->agentDeptId($request);
        $coin = $request->post('coin');

        if ($username === '') {
            return $this->fail('username is required', ReturnCode::PARAMS_ERROR);
        }
        if ($coin === null || $coin === '') {
            return $this->fail('coin is required', ReturnCode::PARAMS_ERROR);
        }

        $coinVal = (float) $coin;
        if ($coinVal === 0.0) {
            return $this->fail('coin cannot be 0', ReturnCode::PARAMS_ERROR);
        }

        $player = $this->findPlayerByUsername($username, $deptId);
        if (!$player) {
            return $this->fail('User not found', ReturnCode::PARAMS_ERROR);
        }

        $walletBefore = (float) ($player->coin ?? 0);
        $walletAfter = $walletBefore + $coinVal;

        if ($coinVal < 0 && $walletBefore < -$coinVal) {
            return $this->fail('Insufficient balance to transfer', ReturnCode::BUSINESS_ERROR);
        }

        $type = $coinVal > 0 ? 0 : 1;
        $remark = $coinVal > 0 ? '充值' : '提现';

        try {
            Db::startTrans();
            $player->coin = $walletAfter;
            $player->save();

            $adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
            $record = DicePlayerWalletRecord::create([
                'dept_id' => $deptId,
                'player_id' => (int) $player->id,
                'admin_id' => $adminId,
                'coin' => $coinVal,
                'type' => $type,
                'wallet_before' => $walletBefore,
                'wallet_after' => $walletAfter,
                'total_ticket_count' => 0,
                'paid_ticket_count' => 0,
                'free_ticket_count' => 0,
                'remark' => $remark,
                'user_id' => 0,
            ]);
            Db::commit();
        } catch (\Throwable $e) {
            Db::rollback();
            return $this->fail('Operation failed: ' . $e->getMessage(), ReturnCode::SERVER_ERROR);
        }

        // 出于安全：删除该玩家相关缓存，后续 API 调用按需重建
        UserCache::deleteUser($player->id);
        if ($player->username !== '') {
            UserCache::deletePlayerByUsername($player->username);
            UserCache::deletePlayerByUsername($this->scopedUsername($deptId, (string) $player->username));
        }

        $recordArr = $record->toArray();
        $recordArr['dice_player'] = ['id' => (int) $player->id, 'username' => $player->username ?? '', 'phone' => $player->phone ?? ''];
        return $this->success($recordArr);
    }

    private function resolveLang($lang): string
    {
        if (!is_string($lang)) {
            return 'zh';
        }
        $langValue = strtolower(trim($lang));
        if (!in_array($langValue, ['zh', 'en'], true)) {
            return 'zh';
        }
        return $langValue;
    }

    private function buildPublicGameList(string $lang, int $deptId): array
    {
        $rows = DiceGame::where('status', 1)
            ->where('dept_id', $deptId)
            ->order('sort', 'asc')
            ->order('id', 'asc')
            ->field(array_merge(self::GAME_PUBLIC_FIELDS, ['game_name', 'game_name_en']))
            ->select()
            ->toArray();
        if (empty($rows)) {
            return [];
        }

        $games = [];
        foreach ($rows as $row) {
            $game = [];
            foreach (self::GAME_PUBLIC_FIELDS as $fieldName) {
                $game[$fieldName] = $row[$fieldName] ?? '';
            }
            $gameNameEn = $row['game_name_en'] ?? '';
            $game['game_name'] = $lang === 'en' && $gameNameEn !== '' ? $gameNameEn : ($row['game_name'] ?? '');
            $games[] = $game;
        }
        return $games;
    }

    private function agentDeptId(Request $request): int
    {
        return (int) ($request->agent_dept_id ?? 0);
    }

    private function agentAdminId(Request $request): ?int
    {
        $adminId = (int) ($request->agent_admin_id ?? 0);
        return $adminId > 0 ? $adminId : null;
    }

    private function scopedUsername(int $deptId, string $username): string
    {
        return $deptId . ':' . $username;
    }

    private function findPlayerByUsername(string $username, int $deptId): ?DicePlayer
    {
        $player = DicePlayer::where('username', $username)->where('dept_id', $deptId)->find();
        return $player ?: null;
    }
}
