<?php

namespace App\Tests\Shared\Captcha;

use AltchaOrg\Altcha\V1\Altcha;
use AltchaOrg\Altcha\V1\Hasher\Algorithm;
use App\Config\Repository\SiteConfigRepository;
use App\Shared\Captcha\AltchaService;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;

/**
 * ALTCHA service: signed-challenge issuing, solution verification, one-time replay guard.
 */
class AltchaServiceTest extends TestCase
{
    private const KEY = 'test-hmac-key-please-change';

    private function service(bool $enabled = true, int $maxNumber = 2000, int $expire = 300, ?string $override = null): AltchaService
    {
        $configRepo = $this->createStub(SiteConfigRepository::class);
        $configRepo->method('get')->willReturn($override); // null → fall back to env flag
        return new AltchaService(self::KEY, $enabled, $maxNumber, $expire, new ArrayAdapter(), $configRepo);
    }

    /**
     * Solve a service-issued challenge the way the browser widget would, and
     * return the base64 payload the client sends back.
     */
    private function solvedPayload(array $challenge): string
    {
        $solver   = new Altcha(self::KEY);
        $solution = $solver->solveChallenge(
            $challenge['challenge'],
            $challenge['salt'],
            Algorithm::SHA256,
            (int) $challenge['maxnumber'],
        );
        self::assertNotNull($solution, 'challenge must be solvable');

        return base64_encode(json_encode([
            'algorithm' => $challenge['algorithm'],
            'challenge' => $challenge['challenge'],
            'number'    => $solution->number,
            'salt'      => $challenge['salt'],
            'signature' => $challenge['signature'],
        ]));
    }

    public function testCreateChallengeShape(): void
    {
        $c = $this->service()->createChallenge();
        self::assertSame('SHA-256', $c['algorithm']);
        self::assertArrayHasKey('challenge', $c);
        self::assertArrayHasKey('salt', $c);
        self::assertArrayHasKey('signature', $c);
        self::assertSame(2000, $c['maxnumber']);
    }

    public function testValidSolutionVerifies(): void
    {
        $svc     = $this->service();
        $payload = $this->solvedPayload($svc->createChallenge());
        self::assertTrue($svc->verifySolution($payload));
    }

    public function testReplayIsRejected(): void
    {
        $svc     = $this->service();
        $payload = $this->solvedPayload($svc->createChallenge());
        self::assertTrue($svc->verifySolution($payload), 'first use accepted');
        self::assertFalse($svc->verifySolution($payload), 'second use (replay) rejected');
    }

    public function testTamperedSignatureRejected(): void
    {
        $svc       = $this->service();
        $challenge = $svc->createChallenge();
        $challenge['signature'] = str_repeat('0', strlen($challenge['signature']));
        self::assertFalse($svc->verifySolution($this->solvedPayload($challenge)));
    }

    public function testEmptyAndGarbagePayloadsRejected(): void
    {
        $svc = $this->service();
        self::assertFalse($svc->verifySolution(''));
        self::assertFalse($svc->verifySolution('not-base64-!@#'));
        self::assertFalse($svc->verifySolution(base64_encode('{"foo":"bar"}')));
    }

    public function testEnabledFlag(): void
    {
        self::assertTrue($this->service(true)->enabled());
        self::assertFalse($this->service(false)->enabled());
    }

    public function testAdminOverrideBeatsEnv(): void
    {
        // env off ولی override پنل روشن → روشن
        self::assertTrue($this->service(false, override: '1')->enabled());
        // env on ولی override پنل خاموش → خاموش
        self::assertFalse($this->service(true, override: '0')->enabled());
        // override خالی/null → به env برمی‌گردد
        self::assertTrue($this->service(true, override: null)->enabled());
    }
}
