<?php

declare(strict_types=1);

namespace Kylin\Education\Services;

use Illuminate\Support\Facades\Crypt;

/**
 * 身份证号加密服务
 *
 * 依据《中华人民共和国个人信息保护法》(2021.11.1)：
 * - 第二十八条：不满十四周岁未成年人的个人信息属于敏感个人信息
 * - 第五十一条：采取加密、去标识化等安全技术措施
 *
 * 依据《未成年人保护法》第七十二条：
 * 处理不满十四周岁未成年人个人信息的，应当取得监护人的同意
 *
 * 使用方式：
 *   $encrypted = IdCardEncryptor::encrypt('110101199001011234');
 *   $original  = IdCardEncryptor::decrypt($encrypted);
 *   $masked    = IdCardEncryptor::mask('110101199001011234');
 */
final class IdCardEncryptor
{
    /**
     * 加密身份证号
     *
     * @param string $idCard 身份证号（18位）
     * @return string 加密后的密文
     * @throws \InvalidArgumentException 身份证号格式不正确
     */
    public static function encrypt(string $idCard): string
    {
        if (! self::validate($idCard)) {
            throw new \InvalidArgumentException('身份证号格式不正确');
        }

        return Crypt::encryptString($idCard);
    }

    /**
     * 解密身份证号
     *
     * @param string $encrypted 加密密文
     * @return string 解密后的身份证号，解密失败返回空字符串
     */
    public static function decrypt(string $encrypted): string
    {
        try {
            return Crypt::decryptString($encrypted);
        } catch (\Throwable) {
            return '';
        }
    }

    /**
     * 脱敏身份证号（仅保留前 6 位和后 4 位）
     *
     * 依据《个人信息保护法》第五十一条，展示时需去标识化。
     *
     * @param string $idCard 身份证号
     * @return string 脱敏后的身份证号，如 110101****1234
     */
    public static function mask(string $idCard): string
    {
        if (mb_strlen($idCard) !== 18) {
            return '****';
        }

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

    /**
     * 从身份证号提取出生日期
     *
     * @param string $idCard 身份证号
     * @return string|null 出生日期 Y-m-d
     */
    public static function extractBirthday(string $idCard): ?string
    {
        if (! self::validate($idCard)) {
            return null;
        }

        $year = substr($idCard, 6, 4);
        $month = substr($idCard, 10, 2);
        $day = substr($idCard, 12, 2);

        return "{$year}-{$month}-{$day}";
    }

    /**
     * 从身份证号提取性别（1=男，2=女）
     *
     * @param string $idCard 身份证号
     * @return string|null 'male'|'female'
     */
    public static function extractGender(string $idCard): ?string
    {
        if (! self::validate($idCard)) {
            return null;
        }

        $genderCode = (int) $idCard[16];
        return $genderCode % 2 === 0 ? 'female' : 'male';
    }

    /**
     * 计算年龄
     *
     * @param string $idCard 身份证号
     * @return int|null 年龄
     */
    public static function calculateAge(string $idCard): ?int
    {
        $birthday = self::extractBirthday($idCard);
        if ($birthday === null) {
            return null;
        }

        return \Carbon\Carbon::parse($birthday)->age;
    }

    /**
     * 判断是否为未成年人（<14岁）
     *
     * 依据《未成年人保护法》第七十二条，
     * 不满 14 周岁为未成年人，个人信息处理需监护人同意。
     *
     * @param string $idCard 身份证号
     * @return bool
     */
    public static function isMinor(string $idCard): bool
    {
        $age = self::calculateAge($idCard);
        if ($age === null) {
            return false;
        }

        $threshold = config('kylin-education.student.minor_age_threshold', 14);
        return $age < $threshold;
    }

    /**
     * 验证身份证号格式
     *
     * 依据 GB 11643-1999《公民身份号码》标准，
     * 验证 18 位身份证号的校验码。
     *
     * @param string $idCard 身份证号
     * @return bool
     */
    public static function validate(string $idCard): bool
    {
        // 长度检查
        if (mb_strlen($idCard) !== 18) {
            return false;
        }

        // 前 17 位必须为数字
        if (! preg_match('/^\d{17}$/', substr($idCard, 0, 17))) {
            return false;
        }

        // 第 18 位为数字或 X
        if (! preg_match('/^[\dXx]$/', $idCard[17])) {
            return false;
        }

        // 校验码验证
        $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];
        }

        $checkCode = $checkCodes[$sum % 11];

        return strtoupper($idCard[17]) === $checkCode;
    }
}