<?php declare(strict_types=1);
/**
 * This file is part of the SplendidBear Websites' projects.
 *
 * Copyright (c) 2026 @ www.splendidbear.org
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace App\Tests\Service;

use App\Service\RecaptchaService;
use PHPUnit\Framework\Attributes\AllowMockObjectsWithoutExpectations;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\Attributes\TestDox;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\HttpClientInterface;

/**
 * Class RecaptchaServiceTest
 *
 * @package  App\Tests\Service
 * @author   Lang <https://www.splendidbear.org>
 * @category Class
 * @license  https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License
 * @link     www.splendidbear.org
 * @since    2026. 04. 21.
 */
#[AllowMockObjectsWithoutExpectations]
#[TestDox('Recaptcha Service')]
class RecaptchaServiceTest extends TestCase
{
    private const SECRET_KEY = 'test-secret-key';

    #[Test]
    #[TestDox('Verify returns false for empty token')]
    public function verifyReturnsFalseForEmptyToken(): void
    {
        $httpClient = $this->createMock(HttpClientInterface::class);
        $logger = $this->createMock(LoggerInterface::class);

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('');

        $this->assertFalse($result);
    }

    #[Test]
    #[TestDox('Verify returns false when api returns failure')]
    public function verifyReturnsFalseWhenApiReturnsFailure(): void
    {
        $mockResponse = new MockResponse(json_encode([
            'success'     => false,
            'error-codes' => ['invalid-input-secret'],
        ], JSON_THROW_ON_ERROR));

        $httpClient = new MockHttpClient($mockResponse);
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info');

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('invalid-token');

        $this->assertFalse($result);
    }

    #[Test]
    #[TestDox('Verify returns true when api returns success and high score')]
    public function verifyReturnsTrueWhenApiReturnsSuccessAndHighScore(): void
    {
        $mockResponse = new MockResponse(json_encode([
            'success'  => true,
            'score'    => 0.8,
            'hostname' => 'test.com',
        ], JSON_THROW_ON_ERROR));

        $httpClient = new MockHttpClient($mockResponse);
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info');

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('valid-token');

        $this->assertTrue($result);
    }

    #[Test]
    #[TestDox('Verify returns false when score below threshold')]
    public function verifyReturnsFalseWhenScoreBelowThreshold(): void
    {
        $mockResponse = new MockResponse(json_encode([
            'success'  => true,
            'score'    => 0.3,
            'hostname' => 'test.com',
        ], JSON_THROW_ON_ERROR));

        $httpClient = new MockHttpClient($mockResponse);
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info');

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('low-score-token');

        $this->assertFalse($result);
    }

    #[Test]
    #[TestDox('Verify returns false when api throws exception')]
    public function verifyReturnsFalseWhenApiThrowsException(): void
    {
        $mockResponse = new MockResponse('', ['http_code' => 500]);

        $httpClient = new MockHttpClient($mockResponse);
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('error');

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('test-token');

        $this->assertFalse($result);
    }

    #[Test]
    #[TestDox('Verify includes remote ip when provided')]
    public function verifyIncludesRemoteIpWhenProvided(): void
    {
        $mockResponse = new MockResponse(json_encode([
            'success' => true,
            'score'   => 0.9,
        ], JSON_THROW_ON_ERROR));

        $httpClient = new MockHttpClient($mockResponse);
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info');

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('test-token', '192.168.1.1');

        $this->assertTrue($result);
    }

    #[Test]
    #[TestDox('Verify with score at threshold')]
    public function verifyWithScoreAtThreshold(): void
    {
        $mockResponse = new MockResponse(json_encode([
            'success' => true,
            'score'   => 0.5,
        ], JSON_THROW_ON_ERROR));

        $httpClient = new MockHttpClient($mockResponse);
        $logger = $this->createMock(LoggerInterface::class);

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('threshold-token');

        $this->assertTrue($result);
    }

    #[Test]
    #[TestDox('Verify with no score fails')]
    public function verifyWithNoScoreFails(): void
    {
        $mockResponse = new MockResponse(json_encode([
            'success' => true,
        ], JSON_THROW_ON_ERROR));

        $httpClient = new MockHttpClient($mockResponse);
        $logger = $this->createMock(LoggerInterface::class);

        $service = new RecaptchaService($httpClient, $logger, self::SECRET_KEY);

        $result = $service->verify('no-score-token');

        $this->assertFalse($result);
    }
}
