<?php

declare(strict_types=1);

namespace Kylin\RealnameGateway\Services;

/**
 * 身份证号校验器
 * - 18位含校验码验证
 * - 生日提取
 * - 年龄计算
 * - 年龄段判定（防沉迷四档）
 */
class IdCardValidator
{
    /** 年龄段：0=未满8周岁 1=8周岁以上未满16周岁 2=16周岁以上未满18周岁 3=已满18周岁 */
    public const AGE_GROUP_UNDER_8 = 0;
    public const AGE_GROUP_8_TO_16 = 1;
    public const AGE_GROUP_16_TO_18 = 2;
    public const AGE_GROUP_ADULT = 3;

    /** 加权因子 */
    private const WEIGHT = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
    /** 校验码映射 */
    private const CHECK_CODE = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];

    /**
     * 校验身份证号格式
     */
    public function validate(string $idCard): bool
    {
        $idCard = strtoupper(trim($idCard));

        // 基本长度和字符检查
        if (!preg_match('/^\d{17}[\dX]$/', $idCard)) {
            return false;
        }

        // 校验码验证
        $sum = 0;
        for ($i = 0; $i < 17; $i++) {
            $sum += (int) $idCard[$i] * self::WEIGHT[$i];
        }
        $checkCode = self::CHECK_CODE[$sum % 11];

        return $idCard[17] === $checkCode;
    }

    /**
     * 提取生日
     */
    public function extractBirthday(string $idCard): ?\DateTimeImmutable
    {
        $idCard = strtoupper(trim($idCard));
        $year = (int) substr($idCard, 6, 4);
        $month = (int) substr($idCard, 10, 2);
        $day = (int) substr($idCard, 12, 2);

        if (!checkdate($month, $day, $year)) {
            return null;
        }

        try {
            return new \DateTimeImmutable("{$year}-{$month}-{$day}");
        } catch (\Exception) {
            return null;
        }
    }

    /**
     * 计算年龄（周岁）
     */
    public function calculateAge(string $idCard, ?\DateTimeImmutable $referenceDate = null): int
    {
        $birthday = $this->extractBirthday($idCard);
        if ($birthday === null) {
            return -1;
        }

        $reference = $referenceDate ?? new \DateTimeImmutable('today');
        $diff = $birthday->diff($reference);

        return $diff->y;
    }

    /**
     * 判定年龄段
     * 返回：0=未满8周岁 1=8周岁以上未满16周岁 2=16周岁以上未满18周岁 3=已满18周岁
     */
    public function determineAgeGroup(string $idCard, ?\DateTimeImmutable $referenceDate = null): int
    {
        $age = $this->calculateAge($idCard, $referenceDate);

        if ($age < 0) {
            return self::AGE_GROUP_ADULT; // 校验失败默认成年
        }

        return match (true) {
            $age < 8 => self::AGE_GROUP_UNDER_8,
            $age < 16 => self::AGE_GROUP_8_TO_16,
            $age < 18 => self::AGE_GROUP_16_TO_18,
            default => self::AGE_GROUP_ADULT,
        };
    }

    /**
     * 获取年龄段标签
     */
    public function getAgeGroupLabel(int $ageGroup): string
    {
        return match ($ageGroup) {
            self::AGE_GROUP_UNDER_8 => '未满8周岁',
            self::AGE_GROUP_8_TO_16 => '8周岁以上未满16周岁',
            self::AGE_GROUP_16_TO_18 => '16周岁以上未满18周岁',
            self::AGE_GROUP_ADULT => '已满18周岁',
            default => '未知',
        };
    }

    /**
     * 是否未成年
     */
    public function isMinor(string $idCard, ?\DateTimeImmutable $referenceDate = null): bool
    {
        return $this->determineAgeGroup($idCard, $referenceDate) !== self::AGE_GROUP_ADULT;
    }
}
