<?php

namespace Kylin\AntiAddiction\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Kylin\AntiAddiction\Enums\AgeGroup;
use Kylin\AntiAddiction\Enums\AntiAddictionErrorCode;
use Kylin\AntiAddiction\Exceptions\AntiAddictionException;
use Kylin\User\Models\UserProfile;

/**
 * 实名认证服务
 *
 * 对接阿里云市场身份证二要素核验接口（cmapi00063280），验证用户姓名与身份证号的真实性。
 * 2021 新规：未实名用户禁止游戏（禁止游客模式）。
 *
 * 认证流程：
 * 1. 用户提交姓名 + 身份证号
 * 2. 参数校验（格式、长度）
 * 3. 多级缓存检查（去重 → 身份证永久缓存 → 数据库已认证）
 * 4. 调用阿里云市场 API 验证
 * 5. 验证通过后更新 UserProfile，写入生日用于年龄计算
 * 6. 缓存认证结果（永久，身份证不变则结果恒定）
 */
class RealNameVerifyService
{
    /**
     * 实名认证（入口方法）
     *
     * @param int    $userId   用户 ID
     * @param string $realName 真实姓名
     * @param string $idCard   身份证号
     * @return array{verified: bool, age_group: AgeGroup, message: string}
     *
     * @throws AntiAddictionException
     */
    public function verify(int $userId, string $realName, string $idCard): array
    {
        // 1. 参数校验
        $this->validateInput($realName, $idCard);

        // 标准化身份证号（转大写）
        $idCard = strtoupper(trim($idCard));
        $realName = trim($realName);

        // 2. 检查是否已在数据库中认证过（同一用户同一身份证）
        $profile = UserProfile::where('user_id', $userId)->first();
        if ($profile?->id_card === $idCard && $profile?->real_name === $realName) {
            Log::info('实名认证命中数据库记录', ['user_id' => $userId]);
            $ageGroup = $this->resolveAgeGroupFromIdCard($idCard);
            $this->auditVerify('cache_hit', $userId, $ageGroup, '命中数据库已认证记录');
            return [
                'verified' => true,
                'age_group' => $ageGroup,
                'message' => '已通过实名认证',
            ];
        }

        // 3. 去重检查：防止同一用户短时间内重复提交
        if ($this->isDuplicateRequest($userId)) {
            Log::info('实名认证请求去重', ['user_id' => $userId]);
            throw new AntiAddictionException(
                AntiAddictionErrorCode::RealNameVerifyFailed,
                '请勿频繁提交，稍后再试',
            );
        }

        // 4. 检查姓名+身份证号缓存（同一组合的结果永久有效）
        $cachedResult = $this->getCachedIdCardResult($realName, $idCard);
        if ($cachedResult !== null) {
            Log::info('实名认证命中缓存', ['name' => $realName, 'id_card_masked' => $this->maskIdCard($idCard)]);

            if ($cachedResult === true) {
                // 缓存中是认证成功，更新数据库并返回
                $this->saveVerifiedProfile($userId, $realName, $idCard);
                $ageGroup = $this->resolveAgeGroupFromIdCard($idCard);
                \Kylin\User\Models\User::where('id', $userId)->update(['age_group' => $ageGroup->value]);
                app(AntiAddictionService::class)->clearAgeGroupCache($userId);

                return [
                    'verified' => true,
                    'age_group' => $ageGroup,
                    'message' => '实名认证成功',
                ];
            }

            // 缓存中是认证失败
            throw new AntiAddictionException(
                AntiAddictionErrorCode::RealNameVerifyFailed,
                '身份信息核验不通过，请检查姓名和身份证号是否一致',
            );
        }

        // 5. 调用认证接口
        $verified = $this->callVerifyApi($realName, $idCard);

        if (! $verified) {
            // 缓存失败结果（防止反复调用 API 尝试）
            $this->cacheIdCardResult($realName, $idCard, false);

            $this->auditVerify('verify_failed', $userId, null, '身份信息核验不通过');

            throw new AntiAddictionException(AntiAddictionErrorCode::RealNameVerifyFailed);
        }

        // 6. 认证通过：保存到数据库 + 缓存
        $this->saveVerifiedProfile($userId, $realName, $idCard);

        // 缓存成功结果（永久）
        $this->cacheIdCardResult($realName, $idCard, true);

        $ageGroup = $this->resolveAgeGroupFromIdCard($idCard);

        // 同步更新 User 模型的 age_group
        \Kylin\User\Models\User::where('id', $userId)->update(['age_group' => $ageGroup->value]);

        // 清除年龄分组缓存
        app(AntiAddictionService::class)->clearAgeGroupCache($userId);

        $this->auditVerify('verify_success', $userId, $ageGroup, '实名认证成功');

        return [
            'verified' => true,
            'age_group' => $ageGroup,
            'message' => '实名认证成功',
        ];
    }

    /**
     * 检查用户是否已实名
     */
    public function isVerified(int $userId): bool
    {
        $profile = UserProfile::where('user_id', $userId)->first();

        return $profile !== null
            && ! empty($profile->id_card)
            && ! empty($profile->real_name)
            && $profile->birthday !== null;
    }

    // ─── 缓存相关方法 ──────────────────────────────────────

    /**
     * 请求去重检查
     * 同一用户在去重窗口内不允许重复提交
     */
    private function isDuplicateRequest(int $userId): bool
    {
        $enabled = config('kylin-anti-addiction.real_name_verify.cache.enabled', true);
        if (! $enabled) {
            return false;
        }

        $prefix = config('kylin-anti-addiction.real_name_verify.cache.prefix', 'rnv:');
        $window = config('kylin-anti-addiction.real_name_verify.cache.dedup_window', 30);
        $key = "{$prefix}dedup:{$userId}";

        return ! Cache::add($key, 1, $window);
    }

    /**
     * 获取姓名+身份证号对应的缓存结果
     * @return bool|null  true=已认证成功, false=认证失败, null=无缓存
     */
    private function getCachedIdCardResult(string $realName, string $idCard): ?bool
    {
        $enabled = config('kylin-anti-addiction.real_name_verify.cache.enabled', true);
        if (! $enabled) {
            return null;
        }

        $prefix = config('kylin-anti-addiction.real_name_verify.cache.prefix', 'rnv:');
        // 缓存键必须包含姓名，否则不同姓名+同一身份证会命中同一缓存（安全漏洞）
        $key = "{$prefix}verify:" . md5($realName . ':' . $idCard);

        $cached = Cache::get($key);

        return $cached !== null ? (bool) $cached : null;
    }

    /**
     * 缓存姓名+身份证号的认证结果
     */
    private function cacheIdCardResult(string $realName, string $idCard, bool $result): void
    {
        $enabled = config('kylin-anti-addiction.real_name_verify.cache.enabled', true);
        if (! $enabled) {
            return;
        }

        $prefix = config('kylin-anti-addiction.real_name_verify.cache.prefix', 'rnv:');
        $ttl = config('kylin-anti-addiction.real_name_verify.cache.id_card_ttl');
        // 缓存键必须包含姓名，否则不同姓名+同一身份证会命中同一缓存（安全漏洞）
        $key = "{$prefix}verify:" . md5($realName . ':' . $idCard);

        if ($ttl === null) {
            Cache::forever($key, $result ? 1 : 0);
        } else {
            Cache::put($key, $result ? 1 : 0, $ttl);
        }
    }

    // ─── API 调用 ───────────────────────────────────────────

    /**
     * 调用实名认证接口（根据配置选择 Provider）
     */
    private function callVerifyApi(string $realName, string $idCard): bool
    {
        $provider = config('kylin-anti-addiction.real_name_verify.provider', 'aliyun');

        return match ($provider) {
            'aliyun' => $this->callAliyunApi($realName, $idCard),
            'ga'     => $this->callGaApi($realName, $idCard),
            'mock'   => true,
            default  => $this->callAliyunApi($realName, $idCard),
        };
    }

    /**
     * 调用阿里云市场身份证二要素核验接口（cmapi00063280 - sxidcardV2）
     *
     * 接口地址: https://sxidcardv2.market.alicloudapi.com/idcard/check
     * 鉴权方式: APPCODE Header
     * 请求方式: GET
     * 请求参数: name(姓名), idCard(身份证号)
     * 返回字段:
     *   - code: "0" 成功
     *   - data.result: "1" 一致 / "2" 不一致
     *   - data.birthday, data.age, data.gender, data.province, data.city, data.district
     */
    private function callAliyunApi(string $realName, string $idCard): bool
    {
        try {
            $appCode = config('kylin-anti-addiction.real_name_verify.aliyun.app_code');
            $apiUrl = config('kylin-anti-addiction.real_name_verify.aliyun.api_url');
            $successCode = config('kylin-anti-addiction.real_name_verify.aliyun.success_code', '0');
            $resultMatch = config('kylin-anti-addiction.real_name_verify.aliyun.result_match', '1');

            if (empty($appCode)) {
                Log::warning('阿里云实名认证 AppCode 未配置，跳过远程调用');
                return false;
            }

            Log::info('调用阿里云实名认证接口', [
                'api_url' => $apiUrl,
                'name' => $realName,
                'id_card_masked' => $this->maskIdCard($idCard),
            ]);

            // GET 请求，参数名 idCard（大写 C）
            $response = Http::withHeaders([
                'Authorization' => "APPCODE {$appCode}",
            ])->get($apiUrl, [
                'name' => $realName,
                'idCard' => $idCard,
            ]);

            if (! $response->successful()) {
                Log::error('阿里云实名认证接口 HTTP 错误', [
                    'status' => $response->status(),
                    'body' => $response->body(),
                ]);
                return false;
            }

            $result = $response->json();
            Log::info('阿里云实名认证接口返回', [
                'code' => $result['code'] ?? null,
                'msg' => $result['msg'] ?? null,
                'result' => $result['data']['result'] ?? null,
                'result_desc' => $result['data']['resultDesc'] ?? null,
            ]);

            // 判断业务响应码：code="0" 表示成功
            $respCode = $result['code'] ?? '';
            if ((string) $respCode !== $successCode) {
                Log::warning('阿里云实名认证业务失败', [
                    'code' => $respCode,
                    'msg' => $result['msg'] ?? '未知错误',
                    'is_fee' => $result['isFee'] ?? null,
                ]);
                return false;
            }

            // 判断核验结果：data.result = 1 表示一致
            $verifyResult = $result['data']['result'] ?? '';
            $isMatch = (string) $verifyResult === $resultMatch;

            if ($isMatch) {
                Log::info('阿里云实名认证核验一致', [
                    'birthday' => $result['data']['birthday'] ?? null,
                    'age' => $result['data']['age'] ?? null,
                    'gender' => $result['data']['gender'] ?? null,
                    'area' => $result['data']['area'] ?? null,
                ]);
            } else {
                Log::warning('阿里云实名认证核验不一致', [
                    'result' => $verifyResult,
                    'description' => $result['data']['resultDesc'] ?? '',
                ]);
            }

            return $isMatch;
        } catch (\Throwable $e) {
            Log::error('阿里云实名认证接口异常', [
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            return false;
        }
    }

    /**
     * 调用公安接口（旧版兼容）
     */
    private function callGaApi(string $realName, string $idCard): bool
    {
        try {
            $appCode = config('kylin-anti-addiction.real_name_verify.ga.app_code');
            $apiUrl = config('kylin-anti-addiction.real_name_verify.ga.api_url');

            $response = Http::withHeaders([
                'Authorization' => "APPCODE {$appCode}",
                'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',
            ])->asForm()->post($apiUrl, [
                'idNo' => $idCard,
                'name' => $realName,
            ]);

            if (! $response->successful()) {
                Log::error('公安实名认证接口请求失败', [
                    'status' => $response->status(),
                    'body' => $response->body(),
                ]);
                return false;
            }

            $result = $response->json();

            return ($result['respCode'] ?? '') === '0000'
                && ($result['data']['result'] ?? '') === '1';
        } catch (\Throwable $e) {
            Log::error('公安实名认证接口异常', [
                'message' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            return false;
        }
    }

    // ─── 辅助方法 ───────────────────────────────────────────

    /**
     * 保存认证成功的用户档案
     */
    private function saveVerifiedProfile(int $userId, string $realName, string $idCard): void
    {
        $birthday = $this->extractBirthdayFromIdCard($idCard);

        UserProfile::updateOrCreate(
            ['user_id' => $userId],
            [
                'real_name' => $realName,
                'id_card' => $idCard,
                'birthday' => $birthday,
            ],
        );

        Log::info('实名认证档案已保存', [
            'user_id' => $userId,
            'name' => $realName,
            'birthday' => $birthday,
        ]);
    }

    /**
     * 从身份证号提取生日
     */
    private function extractBirthdayFromIdCard(string $idCard): string
    {
        // 18 位身份证：第 7-14 位为出生日期
        if (strlen($idCard) === 18) {
            return substr($idCard, 6, 4) . '-'
                . substr($idCard, 10, 2) . '-'
                . substr($idCard, 12, 2);
        }

        // 15 位身份证：第 7-12 位为出生日期（19xx 年）
        if (strlen($idCard) === 15) {
            return '19' . substr($idCard, 6, 2) . '-'
                . substr($idCard, 8, 2) . '-'
                . substr($idCard, 10, 2);
        }

        return '1970-01-01';
    }

    /**
     * 根据身份证号推算年龄分组
     */
    private function resolveAgeGroupFromIdCard(string $idCard): AgeGroup
    {
        $birthday = $this->extractBirthdayFromIdCard($idCard);
        $age = (int) \Carbon\Carbon::parse($birthday)->diffInYears(now());

        return AgeGroup::fromAge($age);
    }

    /**
     * 身份证号脱敏（日志使用）
     */
    private function maskIdCard(string $idCard): string
    {
        $len = strlen($idCard);
        if ($len < 10) {
            return '****';
        }

        return substr($idCard, 0, 6) . '****' . substr($idCard, -4);
    }

    /**
     * 参数校验
     *
     * @throws AntiAddictionException
     */
    private function validateInput(string $realName, string $idCard): void
    {
        if (mb_strlen($realName) < 2 || mb_strlen($realName) > 20) {
            throw new AntiAddictionException(
                AntiAddictionErrorCode::RealNameVerifyFailed,
                '姓名格式不正确（2-20 个字符）',
            );
        }

        // 18 位身份证号格式校验
        if (! preg_match('/^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/', $idCard)) {
            throw new AntiAddictionException(
                AntiAddictionErrorCode::RealNameVerifyFailed,
                '身份证号格式不正确',
            );
        }

        // 18 位校验码验证
        if (strlen($idCard) === 18 && ! $this->validateIdCardChecksum($idCard)) {
            throw new AntiAddictionException(
                AntiAddictionErrorCode::RealNameVerifyFailed,
                '身份证号校验位不正确',
            );
        }
    }

    /**
     * 18 位身份证校验码验证（ISO 7064:1983.MOD 11-2）
     */
    private function validateIdCardChecksum(string $idCard): bool
    {
        $weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
        $checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

        $sum = 0;
        for ($i = 0; $i < 17; $i++) {
            $sum += (int) $idCard[$i] * $weights[$i];
        }

        return $checkCodes[$sum % 11] === strtoupper($idCard[17]);
    }

    /**
     * 记录实名认证审计日志
     *
     * 依据《未成年人保护法》第七十五条和《网络游戏管理暂行办法》第二十条，
     * 实名认证过程需记录审计日志，便于监管部门核查。
     *
     * 审计日志中不记录完整身份证号和姓名，仅记录脱敏后的信息。
     */
    private function auditVerify(string $status, int $userId, ?AgeGroup $ageGroup, ?string $message = null): void
    {
        if (! class_exists(\Kylin\AuditLog\Models\AuditLog::class)) {
            return;
        }

        try {
            \Kylin\AuditLog\Models\AuditLog::create([
                'event' => 'realname_verify',
                'model_type' => self::class,
                'model_id' => $userId,
                'new_values' => [
                    'status' => $status,
                    'age_group' => $ageGroup?->value,
                    'message' => $message,
                ],
                'old_values' => null,
                'user_id' => auth()->id() ?? $userId,
                'ip' => request()?->ip(),
                'url' => request()?->fullUrl(),
                'tag' => 'anti-addiction',
            ]);
        } catch (\Throwable $e) {
            logger()->warning('实名认证审计日志写入失败', [
                'error' => $e->getMessage(),
                'user_id' => $userId,
                'status' => $status,
            ]);
        }
    }
}
