<?php
declare(strict_types=1);

namespace app\api\logic;

use app\dice\model\player\DicePlayer;
use app\api\cache\UserCache;
use plugin\saiadmin\app\model\system\SystemUser;
use plugin\saiadmin\exception\ApiException;
use Tinywan\Jwt\JwtToken;

/**
 * API 用户登录/注册逻辑（基于 DicePlayer 表）
 * 手机号格式限制：+60（马来西亚）
 */
class UserLogic
{
    /** 手机号正则：+60 开头，后跟 9–10 位数字（马来西亚） */
    private const PHONE_REGEX = '/^\+60\d{9,10}$/';

    /** 与 DicePlayerLogic 保持一致的密码盐，用于登录校验与注册写入 */
    private const PASSWORD_SALT = 'dice_player_salt_2024';

    /** 状态：正常 */
    private const STATUS_NORMAL = 1;

    /**
     * 手机号格式校验：+60 开头
     */
    public static function validatePhone(string $phone): void
    {
        if (!preg_match(self::PHONE_REGEX, $phone)) {
            throw new ApiException('Invalid phone format, only +60 Malaysia numbers supported (e.g. +60123456789)');
        }
    }

    /**
     * 与 DicePlayerLogic 一致的密码加密：md5(salt . password)
     */
    private function hashPassword(string $password): string
    {
        return md5(self::PASSWORD_SALT . $password);
    }

    /**
     * 根据 agent_id 获取同渠道下的所有管理员 ID 列表
     * 用于 getGameUrl 接口判断 DicePlayer 是否属于该渠道，同渠道下不重复创建玩家
     *
     * @param string $agentId 代理标识（sa_system_user.agent_id）
     * @return int[] 管理员 ID 列表，空数组表示未找到或无法解析
     */
    public static function getAdminIdsByAgentIdTopDept(string $agentId): array
    {
        $agentId = trim($agentId);
        if ($agentId === '') {
            return [];
        }
        $admin = SystemUser::where('agent_id', $agentId)->find();
        if (!$admin) {
            return [];
        }
        $deptId = $admin->dept_id ?? null;
        if ($deptId === null || $deptId === '') {
            return [(int) $admin->id];
        }
        $deptId = (int) $deptId;
        $adminIds = SystemUser::where('dept_id', $deptId)->column('id');
        return array_map('intval', $adminIds ?: [(int) $admin->id]);
    }

    /**
     * 按用户名查找玩家；不存在则创建并绑定渠道/管理员（供 getPlayerInfo 等接口）
     *
     * @param int|null $adminId 关联后台管理员 ID（sa_system_user.id）
     * @param int|null $deptId  所属渠道 ID
     */
    public function findOrCreatePlayerByUsername(string $username, ?int $adminId = null, ?int $deptId = null): DicePlayer
    {
        $username = trim($username);
        if ($username === '') {
            throw new ApiException('username is required');
        }

        $query = DicePlayer::where('username', $username);
        if ($deptId !== null && $deptId > 0) {
            $query->where('dept_id', $deptId);
        }
        $player = $query->find();
        if ($player) {
            if ((int) ($player->status ?? 1) === 0) {
                throw new ApiException('Account is disabled');
            }
            return $player;
        }

        $player = new DicePlayer();
        $player->username = $username;
        $player->phone = $username;
        $player->password = $this->hashPassword('123456');
        $player->status = self::STATUS_NORMAL;
        $player->coin = 0;
        if ($deptId !== null && $deptId > 0) {
            $player->dept_id = $deptId;
        }
        if ($adminId !== null && $adminId > 0) {
            $player->admin_id = $adminId;
            if ($deptId === null || $deptId <= 0) {
                $adminUser = SystemUser::find($adminId);
                if ($adminUser && !empty($adminUser->dept_id)) {
                    $player->dept_id = $adminUser->dept_id;
                }
            }
        }
        $player->save();

        return $player;
    }

    /**
     * 登录（JSON：username, password, lang, coin, time）
     * 存在则校验密码并更新 coin（累加）；不存在则创建用户并写入 coin。
     * 将会话写入 Redis，返回 token 与前端连接地址。
     *
     * @param int|null $adminId 创建新用户时关联的后台管理员ID（sa_system_user.id），可选
     * @param int[]|null $adminIdsInTopDept 当前管理员顶级部门下的所有管理员ID，用于按部门范围查找玩家；为空时退化为仅按 username 查找
     */
    public function loginByUsername(string $username, string $password, string $lang, float $coin, string $time, ?int $adminId = null, ?array $adminIdsInTopDept = null, ?int $deptId = null, bool $skipPasswordValidation = false): array
    {
        $username = trim($username);
        if ($username === '') {
            throw new ApiException('username is required');
        }

        $query = DicePlayer::where('username', $username);
        if ($deptId !== null && $deptId > 0) {
            $query->where('dept_id', $deptId);
        }
        if ($adminIdsInTopDept !== null && !empty($adminIdsInTopDept)) {
            $query->whereIn('admin_id', $adminIdsInTopDept);
        }
        $player = $query->find();
        if ($player) {
            if ((int) ($player->status ?? 1) === 0) {
                throw new ApiException('Account is disabled and cannot log in');
            }
            if (!$skipPasswordValidation) {
                $hashed = $this->hashPassword($password);
                if ($player->password !== $hashed) {
                    throw new ApiException('Wrong password');
                }
            }
            $currentCoin = (float) $player->coin;
            $player->coin = $currentCoin + $coin;
            $player->save();
        } else {
            $player = new DicePlayer();
            $player->username = $username;
            $player->phone = $username;
            $player->password = $this->hashPassword($password);
            $player->status = self::STATUS_NORMAL;
            $player->coin = $coin;
            if ($deptId !== null && $deptId > 0) {
                $player->dept_id = $deptId;
            }
            if ($adminId !== null && $adminId > 0) {
                $player->admin_id = $adminId;
                $adminUser = SystemUser::find($adminId);
                if (($deptId === null || $deptId <= 0) && $adminUser && !empty($adminUser->dept_id)) {
                    $player->dept_id = $adminUser->dept_id;
                }
            }
            $player->save();
        }

        $exp = (int) config('api.session_expire', 604800);
        $tokenResult = JwtToken::generateToken([
            'id' => (int) $player->id,
            'username' => $username,
            'plat' => 'api_login',
            'access_exp' => $exp,
        ]);
        $token = $tokenResult['access_token'];
        UserCache::setSessionByUsername($username, $token);
        UserCache::setCurrentUserToken((int) $player->id, $token);

        $userArr = $player->hidden(['password', 'lottery_config_id', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight'])->toArray();
        UserCache::setUser((int) $player->id, $userArr);
        UserCache::setPlayerByUsername($username, $userArr);

        $baseUrl = rtrim(config('api.login_url_base', 'https://127.0.0.1:6777'), '/');
        $lang = strtolower(trim($lang));
        if ($lang !== 'en') {
            $lang = 'zh';
        }
        $tokenInUrl = str_replace('%3D', '=', urlencode($token));
        $url = $baseUrl . '?token=' . $tokenInUrl . '&lang=' . $lang;

        return [
            'url' => $url,
            'token' => $token,
            'lang' => $lang,
            'user_id' => (int) $player->id,
            'user' => $userArr,
        ];
    }

    /**
     * 从 JWT 中解析 username（仅解码 payload，不校验签名与过期，用于退出时清除会话）
     */
    public static function getUsernameFromJwtPayload(string $token): ?string
    {
        $parts = explode('.', $token);
        if (count($parts) !== 3) {
            return null;
        }
        $payload = base64_decode(strtr($parts[1], '-_', '+/'), true);
        if ($payload === false) {
            return null;
        }
        $data = json_decode($payload, true);
        if (!is_array($data)) {
            return null;
        }
        $extend = $data['extend'] ?? $data;
        $username = $extend['username'] ?? null;
        return $username !== null ? trim((string) $username) : null;
    }

    /**
     * 从 Redis 获取用户信息（key = base64(user_id)），未命中则查 DicePlayer 并回写缓存
     */
    public static function getCachedUser(int $userId): array
    {
        $cached = UserCache::getUser($userId);
        if (!empty($cached)) {
            return $cached;
        }
        $user = DicePlayer::find($userId);
        if (!$user) {
            return [];
        }
        $arr = $user->hidden(['password'])->toArray();
        UserCache::setUser($userId, $arr);
        return $arr;
    }
}
