<?php

namespace App\Shared\Captcha;

use AltchaOrg\Altcha\V1\Altcha;
use AltchaOrg\Altcha\V1\ChallengeOptions;
use App\Config\Repository\SiteConfigRepository;
use Psr\Cache\CacheItemPoolInterface;

/**
 * ALTCHA self-hosted proof-of-work captcha.
 *
 * Challenges are HMAC-signed with the server secret and carry an embedded
 * `expires` timestamp. A solved payload is accepted at most once: its signature
 * is burned in Redis for the remaining lifetime of the challenge, so a captured
 * payload cannot be replayed.
 */
class AltchaService
{
    private readonly Altcha $altcha;

    public function __construct(
        private readonly string $hmacKey,
        private readonly bool $envEnabled,
        private readonly int $maxNumber,
        private readonly int $expireSeconds,
        private readonly CacheItemPoolInterface $altchaPool,
        private readonly SiteConfigRepository $configRepo,
    ) {
        $this->altcha = new Altcha($this->hmacKey);
    }

    /**
     * فعال بودن کپچا: override پنل ادمین (site_config) مقدم است؛ اگر ست نشده باشد،
     * به مقدار env (`ALTCHA_ENABLED`) برمی‌گردد. پس هم از env هم از پنل قابل کنترل است.
     */
    public function enabled(): bool
    {
        $override = $this->configRepo->get('altcha_enabled');
        if ($override !== null && $override !== '') {
            return $override === '1' || strtolower($override) === 'true';
        }
        return $this->envEnabled;
    }

    /**
     * Build a fresh signed challenge for the widget.
     *
     * @return array<string, string|int> keys: algorithm, challenge, maxnumber, salt, signature
     */
    public function createChallenge(): array
    {
        $challenge = $this->altcha->createChallenge(new ChallengeOptions(
            maxNumber: $this->maxNumber,
            expires: (new \DateTimeImmutable())->add(new \DateInterval('PT' . $this->expireSeconds . 'S')),
        ));

        return [
            'algorithm' => $challenge->algorithm,
            'challenge' => $challenge->challenge,
            'maxnumber' => $challenge->maxNumber,
            'salt'      => $challenge->salt,
            'signature' => $challenge->signature,
        ];
    }

    /**
     * Verify a base64 solution payload sent by the client.
     * Returns false on any invalid/expired/replayed payload.
     */
    public function verifySolution(string $payloadBase64): bool
    {
        if ($payloadBase64 === '' || !$this->altcha->verifySolution($payloadBase64, true)) {
            return false;
        }

        return $this->consumeOnce($payloadBase64);
    }

    /**
     * Atomically burn the challenge signature so it can be used only once.
     * Returns false if this signature has already been consumed.
     */
    private function consumeOnce(string $payloadBase64): bool
    {
        $decoded   = json_decode((string) base64_decode($payloadBase64, true), true);
        $signature = is_array($decoded) ? ($decoded['signature'] ?? null) : null;
        if (!is_string($signature) || $signature === '') {
            return false;
        }

        $item = $this->altchaPool->getItem('altcha_used_' . $signature);
        if ($item->isHit()) {
            return false;
        }

        $item->set(true)->expiresAfter($this->expireSeconds);
        $this->altchaPool->save($item);

        return true;
    }
}
