<?php

declare(strict_types=1);

namespace App\Tests\Unit\Shared\Infrastructure\Captcha;

use App\Shared\Infrastructure\Captcha\TurnstileValidator;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;

final class TurnstileValidatorTest extends TestCase
{
    private const string SECRET_KEY = 'test-secret-key';

    #[Test]
    public function validTokenReturnsValid(): void
    {
        $httpClient = new MockHttpClient([
            new MockResponse(json_encode([
                'success' => true,
            ])),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('valid-token', '192.168.1.1');

        self::assertTrue($result->isValid);
        self::assertNull($result->errorMessage);
    }

    #[Test]
    public function invalidTokenReturnsInvalid(): void
    {
        $httpClient = new MockHttpClient([
            new MockResponse(json_encode([
                'success' => false,
                'error-codes' => ['invalid-input-response'],
            ])),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('invalid-token', '192.168.1.1');

        self::assertFalse($result->isValid);
        self::assertSame('Token invalide ou expiré', $result->errorMessage);
    }

    #[Test]
    public function expiredTokenReturnsInvalid(): void
    {
        $httpClient = new MockHttpClient([
            new MockResponse(json_encode([
                'success' => false,
                'error-codes' => ['timeout-or-duplicate'],
            ])),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('expired-token', '192.168.1.1');

        self::assertFalse($result->isValid);
        self::assertSame('Token expiré ou déjà utilisé', $result->errorMessage);
    }

    #[Test]
    public function emptyTokenReturnsInvalid(): void
    {
        $httpClient = new MockHttpClient(); // No request should be made

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('', '192.168.1.1');

        self::assertFalse($result->isValid);
        self::assertSame('Token vide', $result->errorMessage);
    }

    #[Test]
    public function apiErrorReturnsValidWhenFailOpenEnabled(): void
    {
        // Simulate API error with fail open
        $httpClient = new MockHttpClient([
            new MockResponse('', ['http_code' => 500]),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY, failOpen: true);

        $result = $validator->validate('some-token', '192.168.1.1');

        // Fail open - allow through on API errors
        self::assertTrue($result->isValid);
    }

    #[Test]
    public function apiErrorReturnsInvalidWhenFailOpenDisabled(): void
    {
        // Simulate API error with fail closed (production default)
        $httpClient = new MockHttpClient([
            new MockResponse('', ['http_code' => 500]),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY, failOpen: false);

        $result = $validator->validate('some-token', '192.168.1.1');

        // Fail closed - block on API errors
        self::assertFalse($result->isValid);
        self::assertSame('Service de vérification temporairement indisponible', $result->errorMessage);
    }

    #[Test]
    public function networkErrorReturnsValidWhenFailOpenEnabled(): void
    {
        // Simulate network error with fail open
        $httpClient = new MockHttpClient([
            new MockResponse('', ['error' => 'Network error']),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY, failOpen: true);

        $result = $validator->validate('some-token', '192.168.1.1');

        // Fail open - allow through on network errors
        self::assertTrue($result->isValid);
    }

    #[Test]
    public function networkErrorReturnsInvalidWhenFailOpenDisabled(): void
    {
        // Simulate network error with fail closed
        $httpClient = new MockHttpClient([
            new MockResponse('', ['error' => 'Network error']),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY, failOpen: false);

        $result = $validator->validate('some-token', '192.168.1.1');

        // Fail closed - block on network errors
        self::assertFalse($result->isValid);
    }

    #[Test]
    public function invalidSecretKeyReturnsInvalid(): void
    {
        $httpClient = new MockHttpClient([
            new MockResponse(json_encode([
                'success' => false,
                'error-codes' => ['invalid-input-secret'],
            ])),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('token', '192.168.1.1');

        self::assertFalse($result->isValid);
        self::assertSame('Configuration serveur invalide', $result->errorMessage);
    }

    #[Test]
    public function missingSecretKeyReturnsInvalid(): void
    {
        $httpClient = new MockHttpClient([
            new MockResponse(json_encode([
                'success' => false,
                'error-codes' => ['missing-input-secret'],
            ])),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('token', '192.168.1.1');

        self::assertFalse($result->isValid);
        self::assertSame('Configuration serveur invalide', $result->errorMessage);
    }

    #[Test]
    public function unknownErrorCodeReturnsGenericMessage(): void
    {
        $httpClient = new MockHttpClient([
            new MockResponse(json_encode([
                'success' => false,
                'error-codes' => ['unknown-error-code'],
            ])),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('token', '192.168.1.1');

        self::assertFalse($result->isValid);
        self::assertSame('Vérification échouée', $result->errorMessage);
    }

    #[Test]
    public function validationWithoutIpWorks(): void
    {
        $httpClient = new MockHttpClient([
            new MockResponse(json_encode([
                'success' => true,
            ])),
        ]);

        $validator = new TurnstileValidator($httpClient, new NullLogger(), self::SECRET_KEY);

        $result = $validator->validate('valid-token');

        self::assertTrue($result->isValid);
    }
}
