<?php

namespace Application\Service\Extension\Identity;

use Application\Common\StatusCode;
use Application\Service\DB\Admin\Role;
use Application\Service\Exceptions\InvalidArgumentException;
use Application\Service\Extension\Helper\StringHelper;
use Application\Service\Extension\Laminas;
use ArrayIterator;
use Laminas\Cache\Exception\ExceptionInterface;
use Laminas\Http\Header\HeaderInterface;
use Laminas\Http\Headers;
use Laminas\Json\Json;
use Swoole\Coroutine;

class Identity
{
    /**
     * 用户数据
     * @var array
     */
    protected array $identityData = [];

    /**
     * @var int|mixed|null
     */
    protected int $expire;

    /**
     * 小程序token过期时间
     * @var int|mixed
     */
    protected int $miniExpire;

    /**
     * pcToken过期时间
     * @var string
     */
    protected string $tokenKey;

    /**
     * 假id,调试接口用得到
     * @var mixed
     */
    protected $fakeId;

    /**
     * 是否是debug模式
     * @var bool
     */
    protected bool $isDebugMode = false;

    /**
     * debugMode token 可用时长
     * @var int
     */
    private int $debugExpire = 0;

    /**
     * @var array|null
     */
    private static ?array $identityContext = [];

    /**
     * 当前所属平台
     * @var string
     */
    public string $currentPlatform = '';

    /** @var string token存储key */
    public const IDENTITY_KEY = 'token:';

    /** @var string PC平台 */
    public const PLATFORM_PC = 'PC';

    // 现在pc real 和 小程序用的都是这个平台标识
    /** @var string  小程序平台*/
    public const PLATFORM_MINI_PROGRAM = 'MINI';

    /** @var string 小程序受试者 */
    public const PLATFORM_MINI_PROGRAM_PATIENT = 'MINI_PATIENT';

    /** @var string 小程序REAL端 */
    public const PLATFORM_REAL = 'MINI_REAL';

    /** @var string 受试者H5端 */
    public const PLATFORM_H5_PROGRAM_PATIENT = 'H5_PATIENT';

    /** @var string webH5 端 */
    public const PLATFORM_H5_WEB = 'H5_WEB';

    public const MASKING_ADMIN_USER_CACHE_KEY = 'MaskingAdminUser'; // 脱敏字段管理员缓存的键名

    public function __construct($container)
    {
        $config = $container->get('config')['identity'];
        $this->expire = $config['expire'];
        $this->miniExpire = $config['miniExpire'];
        $this->tokenKey = $config['tokenKey'];
        $this->debugExpire = $config['debugExpire'];
        $this->fakeId = $config['fakeId'] instanceof \Closure ? $config['fakeId']() : $config['fakeId'] ;
    }

    /**
     * 生成pc token
     * @param string $id 用户id
     * @param string $platform 平台
     * @return array
     */
    public function generateTokenByUserId(string $id, string $platform = 'PC'): array
    {
        $user = Laminas::$serviceManager->adminUser->isShowMaskingField(true)->fetchOne([
            'where' => [
                'id' => $id
            ]
        ]);

        return $this->generateIdentityData($user, $platform);
    }

    /**
     * 生成小程序token
     * @param string $mobile 用户手机号
     * @param string $platform 平台
     * @return array
     */
    public function generateTokenByMobile(string $mobile, string $platform = 'PC'): array
    {
        $user = Laminas::$serviceManager->adminUser->isShowMaskingField(true)->fetchOne([
            'where' => [
                'mobile' => $mobile,
                'is_del' => 0
            ]
        ]);

        if (!$user) {
            throw new InvalidArgumentException("没有匹配的账户信息，请联系客服后重试!", StatusCode::E_FIELD_VALIDATOR['code']);
        }

        if ($platform === self::PLATFORM_PC) {
            $data = $this->generateIdentityData($user, $platform);
        } elseif ($platform === self::PLATFORM_MINI_PROGRAM) {
            // 受试者小程序登录
            // 0 是受试者 这是反着来的
            if ($user['patient_status'] == 0) {
                $platform = self::PLATFORM_MINI_PROGRAM_PATIENT;
                if ($generateData = $this->generateMiniData($user, $platform)) {
                    $data[] = $generateData;
                }
            }

            if ($user['real_status'] == 0) {
                $platform = self::PLATFORM_MINI_PROGRAM;
                if ($generateData = $this->generateMiniData($user, $platform)) {
                    $data[] = $generateData;
                }
            }
        }

        $this->currentPlatform = $platform;
        return $data;
    }

    /**
     * 生成用户数据
     * @param array $data
     * @param string $platform
     * @return array
     * @throws ExceptionInterface
     * @throws \RedisException
     */
    protected function generateIdentityData(array $data, string $platform): array
    {
        $token = $this->generateTokenString();
        $data['token'] = $token;

        if($platform == self::PLATFORM_H5_PROGRAM_PATIENT) {
            $data['group'] = -1;
            $data['platform'] = $platform;
        }else {
            // 对用户组进行处理
            $data['group'] = Laminas::$serviceManager->adminUser::getGroup($data);
            //        $data['platform'] = $platform;
            $data['platform'] = $data['group'] == 1 ? self::PLATFORM_MINI_PROGRAM : $platform;
        }

        // 将需要的信息存到token中
        Laminas::$serviceManager->redisExtend->setDatabase(6)->getRedisInstance()->setex(self::IDENTITY_KEY . $token, $this->expire, json_encode($data));
        // 记录登录日志
        $this->generateLoginLog($data);
        // 更新脱敏字段管理员缓存信息
        $this->syncMaskingAdminCache();

        return $data;
    }

    private function getItemInfo($id)
    {
        return Laminas::$serviceManager->itemInfo->fetchOne(['where' => ['id' => $id]]);
    }

    /**
     * 生成小程序用户数据
     * @param array $data
     * @param string $platform
     * @return array
     * @throws ExceptionInterface
     * @throws \RedisException
     */
    protected function generateMiniData(array $data, string $platform): array
    {
        if ($platform === self::PLATFORM_MINI_PROGRAM_PATIENT) {
            $patientData = Laminas::$serviceManager->patient->fetchOne([
                'where' => [
                    'patient_tel' => $data['mobile'],
                    'is_del' => 0
                ]
            ]);

            if (!$patientData) {
                return [];
            }
            $data['contact_way_id'] = '';
            !empty($patientData['id']) && $data['contact_way_id'] = Laminas::$serviceManager->signatoryPatient->patientId_to_contactWayId($patientData['id'])?:'';
            $data['patient_id'] = $patientData['id'];
            $data['item_id'] = $patientData['item_id'];
            $data['itemsig_id'] = $patientData['itemsig_id'];
            $data['patient_number'] = $patientData['patient_number'];
            //受试者人员名称
            $data['realname'] = $patientData['patient_name']?:'';
            $sig = Laminas::$serviceManager->itemSignatory->fetchOne([
                'where' => [
                    'id' => $patientData['itemsig_id']
                ]
            ])['signatory_id'] ?: 0;

            $data['itemsig_name'] = Laminas::$serviceManager->signatoryInfo->fetchOne([
                'where' => [
                    'id' => $sig
                ]
            ])['info_name'] ?? '出错了~~';
            $info = $this->getItemInfo($patientData['item_id']);
            $data['item_name'] = $info['name'] ?? '';

            $data['fid'] = $this->getItemInfo($info['fid'])['id'] ?? 0;
        }

        $token = $this->generateTokenString();
        $data['token'] = $token;
        // 对用户组进行处理
//        $data['group'] = Laminas::$serviceManager->adminUser::getGroup($data);
        $data['platform'] = $platform;

        // 登录时间
        $data['login_at'] = time();

        // 将需要的信息存到token中
        Laminas::$serviceManager->redisExtend->setDatabase(6)->getRedisInstance()->setex(self::IDENTITY_KEY . $token, $this->expire, json_encode($data));
        // 记录登录日志
        $this->generateLoginLog($data);
        // 更新脱敏字段管理员缓存信息
        $this->syncMaskingAdminCache();

        return $data;
    }

    /**
     * 获取登录所需要的字段内容
     * @param string $id 用户id
     * @return array
     */
    public function getLoginInfo(string $id): array
    {
        $user = $this->generateTokenByUserId($id);
        return [
            'group' => $user['group'],
            'id' => $user['id'],
            'nickname' => $user['nickname'],
            'realname' => $user['realname'],
            'avatar' => $user['avatar'],
            'token' => $user['token']
        ];
    }

    /**
     * 注销指定用户token
     * @return bool
     */
    public function discard(): bool
    {
        $token = $this->getToken();

        if ($token) {
            $userData = $this->getIdentityData();
            Laminas::$serviceManager->redisExtend->setDatabase(6)->getRedisInstance()->del(self::IDENTITY_KEY . $token);
            // 记录登出日志
            $this->generateLoginLog($userData, '退出登录');
        }

        return true;
    }

    /**
     * 获取用户所有信息
     * 不传token参数,之前会抛出一个401的异常, 现在将返回null
     * @return array|mixed
     */
    public function getIdentityData()
    {
        if (php_sapi_name() == 'cli' && class_exists(Coroutine::class) && Coroutine::getCid() == -1) {
            return [];
        }
        $token = $this->fakeId ? $this->getFakeToken() : $this->getToken();

        if (!$token) {
            throw new UnauthorizedHttpException('请重新登录。');
        }

        $tokenKey = self::IDENTITY_KEY . $token;

        if (!$this->_getIdentityData()) {
            // 未获取到token
            $userInfo = $this->tokenExist($tokenKey);
            if (!$userInfo) {
                throw new UnauthorizedHttpException('登录超时。');
            }

            $this->_setIdentity($userInfo);

            // 是否开启系统默认token，默认token永久有效，不需要更新过期时间
            $defaultToken = Laminas::$serviceManager->config['defaultToken'] ?? false;
            if(!($defaultToken['is_open'] and $defaultToken['default_token'] === $token)) $this->setTokenExpire($tokenKey);
        }

        return $this->_getIdentityData();
    }

    /**
     * 兼容swoole协程写法
     */
    private function _getIdentityData()
    {
        $cid = class_exists(Coroutine::class) ? Coroutine::getCid() : -1;
        if ($cid > -1) {
            return self::$identityContext[$cid] ?? null;
        } else {
            return $this->identityData; 
        }
    }

    /**
     * 兼容swoole写法
     * @param $userInfo
     */
    private function _setIdentity($userInfo)
    {
        $cid = class_exists(Coroutine::class) ? Coroutine::getCid() : -1;

        $data = json_decode($userInfo, true);
        if ($cid > -1) {
            self::$identityContext[$cid] = $data;
            \Co\defer(function () use ($cid) {
                unset(self::$identityContext[$cid]);
            });
        } else {
            $this->identityData = $data;
        }
    }

    /**
     * 延长token过期时间
     * @param string $tokenKey token
     */
    private function setTokenExpire(string $tokenKey)
    {
        Laminas::$serviceManager->redisExtend->setDatabase(6)->getRedisInstance()->expire($tokenKey, $this->getExpireTokenVal($this->getPlatform()));
    }

    /**
     * 获取指定平台token延长时间
     * @param string $platform token所属平台
     * @return float|int|mixed|void
     */
    private function getExpireTokenVal(string $platform)
    {
        if ($this->isDebugMode === true) {
            return $this->debugExpire;
        }

        if ($platform == self::PLATFORM_PC) {
            return $this->expire;
        } elseif ($platform == self::PLATFORM_MINI_PROGRAM) {
            return $this->miniExpire;
        } elseif ($platform == self::PLATFORM_MINI_PROGRAM_PATIENT) {
            return $this->miniExpire;
        } elseif ($platform == self::PLATFORM_REAL) {
            return $this->expire;
        } elseif ($platform == self::PLATFORM_H5_PROGRAM_PATIENT) {
            return $this->expire;
        } elseif ($platform == self::PLATFORM_H5_WEB) {
            return $this->expire;
        }
    }

    /**
     * 获取请求头数据
     * @return ArrayIterator|bool|HeaderInterface|Headers
     */
    private function getRequestHeaders()
    {
        return Laminas::$app->getMvcEvent()->getRequest()->getHeaders();
    }

    /**
     * 生成token字符串
     * @return string
     */
    private function generateTokenString(): string
    {
        return $this->fakeId ?: StringHelper::generateHashValue();
    }

    /**
     * 获取用户id
     * @return int|null
     */
    public function getId(): ?int
    {
        return isset($this->getIdentityData()['id']) ? intval($this->getIdentityData()['id']) : null;
    }

    /**
     * 获取头像
     * @return mixed|null
     */
    public function getAvatar()
    {
        return $this->getIdentityData()['avatar'] ?? null;
    }

    /**
     * 获取手机号
     * @return mixed|null
     */
    public function getMobile()
    {
        return $this->getIdentityData()['mobile'] ?? null;
    }

    /**
     * 获取openid
     * @return mixed|null
     */
    public function getOpenid()
    { 
        return $this->getIdentityData()['openid'] ?? null;
    }

    /**
     * 获取用户组
     * @return int|null
     */
    public function getGroup(): ?int
    {
        return isset($this->getIdentityData()['group']) ? intval($this->getIdentityData()['group']) : null;
    }

    /**
     * 获取昵称
     * @return string|null
     */
    public function getNickname(): ?string
    {
        return $this->getIdentityData()['nickname'] ?? null;
    }

    /**
     * 获取真实姓名
     * @return string|null
     */
    public function getRealName(): ?string
    {
        return $this->getIdentityData()['realname'] ?? null;
    }

    /**
     * 获取请求token
     * @return string
     */
    public function getToken(): ?string
    {
        try {
            // cli
            if (php_sapi_name() == 'cli') {
                if (class_exists(Coroutine::class) && Coroutine::getCid() > 0) {
                    return Laminas::getToken();
                }

                return Laminas::$token;
            }

            $token = null;

            // cgi

            if ($this->getRequestHeaders()->get($this->tokenKey) !== false) {
                $token = $this->getRequestHeaders()->get($this->tokenKey)->getFieldValue();
            } elseif (isset($_COOKIE['token'])) {
                // 用作查看图片登录验证
                // http://lzy.kingdone.com/common/showPic?path=upload/2023-01-06/9363b77c6e1c0f0_1672969326.jpg
                $token = $_COOKIE['token'];
            }

            if (!$token) {
                return null;
            }

            // 设置debugMode
            if (stripos($token, '_debug') !== false) {
                $this->isDebugMode = true;
                $token = str_replace('_debug', '', $token);
            }

            return $token;
        } catch (\Exception $e) {
            return null;
        }
    }

    /**
     * 验证token是否存在
     * @param string $tokenKey
     * @return bool|mixed|string
     */
    private function tokenExist(string $tokenKey)
    {
        if(php_sapi_name() === 'cli') {
            return Laminas::$serviceManager->redisExtend->setDatabase(6)->getRedisInstance()->get($tokenKey);
        }

        return Laminas::$serviceManager->redisExtend->setDatabase(6)->getRedisInstance()->get($tokenKey);
    }

    /**
     * @return string
     */
    public function getFakeToken(): string
    {
        if (!$this->tokenExist(self::IDENTITY_KEY . $this->fakeId)) {
            $this->generateTokenByUserId($this->fakeId);
        }

        return $this->fakeId;
    }

    /**
     * 获取所属平台
     * @return mixed|null
     */
    public function getPlatform()
    {
        return $this->getIdentityData()['platform'] ?? null;
    }

    /**
     * @note 验证当前登录人是否是脱敏字段管理员
     * @return bool
     * @throws ExceptionInterface
     */
    public function isValidMaskingAdmin(): bool
    {
        $token = self::IDENTITY_KEY . $this->getToken();
        if ( !$this->tokenExist($token) ) return false;

        $redisConfig = Laminas::$serviceManager->config['caches']['Redis']['adapter']['options'];

        $maskingAdminUserList = Laminas::$serviceManager->redisExtend->setNamespace($redisConfig['namespace'])->getItem(self::MASKING_ADMIN_USER_CACHE_KEY);
        if($maskingAdminUserList) {
            $maskingAdminUserList = Json::decode($maskingAdminUserList, Json::TYPE_ARRAY);
        }

        return in_array($this->getId(),  $maskingAdminUserList ?: []);
    }
    /**
     * @note 记录登录日志
     * @param array $loginUserData
     * @param string $remark
     * @return void
     */
    private function generateLoginLog(array &$loginUserData, string $remark = '登录') {
        Laminas::$serviceManager->logLogin->save([
            'user_id' => $loginUserData['id'],
            'user_name' => $loginUserData['realname'] ?? null,
            'create_time' => time(),
            'type' => $loginUserData['platform'],
            'ip' => $_SERVER['REMOTE_ADDR'],
            'remark' => $remark,
        ]);
    }

    /**
     * @note 更新脱敏字段管理员缓存信息
     * @return void
     * @throws ExceptionInterface
     */
    private function syncMaskingAdminCache() {
        Laminas::$serviceManager->userRoleRelation->syncMaskingAdminCache();
    }
}
