<?php

namespace App\Services;

use Illuminate\Support\Str;
use Illuminate\Support\Facades\Cache;

/**
 * 图形验证码：SVG 产出 + Cache 短时保存答案摘要（admin / player 登录共用渲染逻辑）。
 */
final class AdminCaptchaService
{
    public const SCOPE_ADMIN = 'admin';

    public const SCOPE_PLAYER = 'player';

    /** @var array<string, string> */
    private const PREFIXES = [
        self::SCOPE_ADMIN => 'admin_captcha:',
        self::SCOPE_PLAYER => 'player_captcha:',
    ];

    private const TTL_SECONDS = 120;

    /** 剔除易混淆字符 */
    private const CHARSET = '23456789abcdefghjkmpqrstuvwxyz';

    /**
     * @return array{captcha_key: string, image_svg: string, image_base64: string}
     */
    public function create(string $scope = self::SCOPE_ADMIN): array
    {
        $code = $this->randomCode();
        $key = (string) Str::uuid();

        Cache::put(
            $this->prefix($scope).$key,
            $this->digest($code),
            now()->addSeconds(self::TTL_SECONDS),
        );

        $svg = $this->renderSvg($code);

        return [
            'captcha_key' => $key,
            'image_svg' => $svg,
            'image_base64' => base64_encode($svg),
        ];
    }

    public function verify(?string $captchaKey, ?string $captchaInput, string $scope = self::SCOPE_ADMIN): bool
    {
        // E2E 旁路：仅在 LOTTERY_E2E=true 时放行，约定 captcha_code == LOTTERY_E2E_BYPASS
        // 视为通过。生产环境不启用，对应变量在 .env.example 留空。
        if ($captchaInput !== null && trim($captchaInput) === 'LOTTERY_E2E_BYPASS'
            && (bool) env('LOTTERY_E2E', false)) {
            // 顺便消费掉 key，避免同一 key 被多次复用
            if ($captchaKey !== null && $captchaKey !== '') {
                Cache::pull($this->prefix($scope).$captchaKey);
            }
            return true;
        }

        if ($captchaKey === null || $captchaKey === ''
            || $captchaInput === null || trim($captchaInput) === '') {
            return false;
        }

        $digest = Cache::pull($this->prefix($scope).$captchaKey);
        if ($digest === null) {
            return false;
        }

        $guess = strtolower(trim($captchaInput));

        return hash_equals($digest, $this->digest($guess));
    }

    private function prefix(string $scope): string
    {
        $prefix = self::PREFIXES[$scope] ?? null;
        if ($prefix === null) {
            throw new \InvalidArgumentException('invalid_captcha_scope');
        }

        return $prefix;
    }

    private function digest(string $normalizedCode): string
    {
        return hash_hmac(
            'sha256',
            $normalizedCode,
            (string) config('app.key'),
        );
    }

    private function randomCode(int $length = 4): string
    {
        $out = '';
        $max = strlen(self::CHARSET) - 1;
        for ($i = 0; $i < $length; $i++) {
            $out .= self::CHARSET[random_int(0, $max)];
        }

        return $out;
    }

    private function renderSvg(string $code): string
    {
        $w = 160;
        $h = 48;
        $bg = '#fafafa';

        $escape = static fn (string $c): string => htmlspecialchars($c, ENT_XML1 | ENT_QUOTES);

        $lines = '';
        for ($i = 0; $i < 4; $i++) {
            $x1 = random_int(0, $w);
            $y1 = random_int(0, $h);
            $x2 = random_int(0, $w);
            $y2 = random_int(0, $h);
            $stroke = sprintf('#%06x', random_int(0x9_00_000, 0xBF_BF_BF));
            $lines .= sprintf(
                '<line x1="%d" y1="%d" x2="%d" y2="%d" stroke="%s" stroke-width="1" opacity="0.35"/>',
                $x1,
                $y1,
                $x2,
                $y2,
                $stroke,
            );
        }

        $chars = str_split($code);
        $texts = '';
        $x = 18;
        foreach ($chars as $ch) {
            $rot = random_int(-20, 20);
            $dy = random_int(-3, 3);
            $y = 32 + $dy;
            $fill = sprintf('#%06x', random_int(0x20_20_20, 0x3F_3F_3F));
            $texts .= sprintf(
                '<text x="%d" y="%d" font-family="ui-monospace,monospace" font-size="24" font-weight="600" fill="%s" transform="rotate(%d %d %d)">%s</text>',
                $x,
                $y,
                $fill,
                $rot,
                $x,
                $y,
                $escape($ch),
            );
            $x += 34;
        }

        return sprintf(
            '<svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d"><rect width="100%%" height="100%%" fill="%s"/>%s%s</svg>',
            $w,
            $h,
            $w,
            $h,
            $bg,
            $lines,
            $texts,
        );
    }
}
