<?php

declare(strict_types=1);

namespace app\common\library;

use ba\Random;
use app\common\facade\Token;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider;

/**
 * 管理员谷歌验证器（TOTP）
 */
class AdminTotp
{
    public const TOKEN_TYPE_BIND   = 'admin-totp-bind';
    public const TOKEN_TYPE_VERIFY = 'admin-totp-verify';
    public const TOKEN_EXPIRE      = 300;

    private static ?TwoFactorAuth $tfa = null;

    public static function isEnabled(): bool
    {
        return (bool) config('buildadmin.admin_totp_enable', true);
    }

    private static function tfa(): TwoFactorAuth
    {
        if (self::$tfa === null) {
            $issuer = (string) get_sys_config('site_name');
            if ($issuer === '') {
                $issuer = 'BuildAdmin';
            }
            // 使用 SVG 格式，避免依赖 imagick 扩展（PNG 默认需要 ImagickImageBackEnd）
            self::$tfa = new TwoFactorAuth(new BaconQrCodeProvider(format: 'svg'), $issuer);
        }
        return self::$tfa;
    }

    public static function isBound(?string $encryptedSecret): bool
    {
        return is_string($encryptedSecret) && $encryptedSecret !== '';
    }

    public static function generateSecret(): string
    {
        return self::tfa()->createSecret();
    }

    public static function getQrDataUri(string $label, string $secret): string
    {
        return self::tfa()->getQRCodeImageAsDataUri($label, $secret);
    }

    public static function verifyCode(string $plainSecret, string $code): bool
    {
        $code = trim($code);
        if (!preg_match('/^\d{6}$/', $code)) {
            return false;
        }
        return self::tfa()->verifyCode($plainSecret, $code);
    }

    public static function encryptSecret(string $plainSecret): string
    {
        $key = substr(hash('sha256', (string) config('buildadmin.token.key')), 0, 32);
        $iv  = random_bytes(16);
        $encrypted = openssl_encrypt($plainSecret, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        if ($encrypted === false) {
            return '';
        }
        return base64_encode($iv . $encrypted);
    }

    public static function decryptSecret(string $encryptedSecret): string
    {
        if ($encryptedSecret === '') {
            return '';
        }
        $raw = base64_decode($encryptedSecret, true);
        if ($raw === false || strlen($raw) < 17) {
            return '';
        }
        $iv        = substr($raw, 0, 16);
        $encrypted = substr($raw, 16);
        $key       = substr(hash('sha256', (string) config('buildadmin.token.key')), 0, 32);
        $plain     = openssl_decrypt($encrypted, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
        return $plain === false ? '' : $plain;
    }

    public static function verifyStoredCode(string $encryptedSecret, string $code): bool
    {
        $plain = self::decryptSecret($encryptedSecret);
        if ($plain === '') {
            return false;
        }
        return self::verifyCode($plain, $code);
    }

    public static function createPendingToken(int $adminId, string $type): string
    {
        $token = Random::uuid();
        Token::set($token, $type, $adminId, self::TOKEN_EXPIRE);
        return $token;
    }

    public static function resolvePendingToken(string $token, string $type): int
    {
        $token = trim($token);
        if ($token === '') {
            return 0;
        }
        $data = Token::get($token);
        if (!$data || ($data['type'] ?? '') !== $type) {
            return 0;
        }
        Token::tokenExpirationCheck($data);
        return (int) ($data['user_id'] ?? 0);
    }

    public static function deletePendingToken(string $token): void
    {
        if ($token !== '') {
            Token::delete($token);
        }
    }
}
