<?php

declare(strict_types=1);

namespace App\Shared\Infrastructure\Captcha;

use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Throwable;

/**
 * Valide les tokens Cloudflare Turnstile.
 *
 * @see https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
 * @see Story 1.4 - T8: CAPTCHA anti-bot
 */
final readonly class TurnstileValidator implements TurnstileValidatorInterface
{
    private const string VERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
    private const float TIMEOUT_SECONDS = 5.0;

    /**
     * @param bool $failOpen Si true, les erreurs API laissent passer (dev). Si false, elles bloquent (prod).
     */
    public function __construct(
        private HttpClientInterface $httpClient,
        private LoggerInterface $logger,
        private string $secretKey,
        private bool $failOpen = false,
    ) {
    }

    /**
     * Valide un token Turnstile.
     *
     * @param string      $token    Le token fourni par le widget Turnstile
     * @param string|null $remoteIp L'IP du client (optionnel, mais recommandé)
     */
    public function validate(string $token, ?string $remoteIp = null): TurnstileResult
    {
        if ($token === '') {
            return TurnstileResult::invalid('Token vide');
        }

        try {
            $formData = [
                'secret' => $this->secretKey,
                'response' => $token,
            ];

            if ($remoteIp !== null) {
                $formData['remoteip'] = $remoteIp;
            }

            $response = $this->httpClient->request('POST', self::VERIFY_URL, [
                'body' => $formData,
                'timeout' => self::TIMEOUT_SECONDS,
            ]);

            $data = $response->toArray();

            if ($data['success'] === true) {
                return TurnstileResult::valid();
            }

            // Erreurs possibles : https://developers.cloudflare.com/turnstile/get-started/server-side-validation/#error-codes
            $errorCodes = $data['error-codes'] ?? [];
            $errorMessage = $this->translateErrorCodes($errorCodes);

            $this->logger->warning('Turnstile validation failed', [
                'error_codes' => $errorCodes,
            ]);

            return TurnstileResult::invalid($errorMessage);
        } catch (Throwable $e) {
            $this->logger->error('Turnstile API error', [
                'exception' => $e->getMessage(),
                'fail_open' => $this->failOpen,
            ]);

            // Comportement configurable en cas d'erreur API
            // - failOpen=true (dev): laisse passer pour ne pas bloquer le développement
            // - failOpen=false (prod): bloque pour maintenir la sécurité
            if ($this->failOpen) {
                return TurnstileResult::valid();
            }

            return TurnstileResult::invalid('Service de vérification temporairement indisponible');
        }
    }

    /**
     * @param array<string> $errorCodes
     */
    private function translateErrorCodes(array $errorCodes): string
    {
        $translations = [
            'missing-input-secret' => 'Configuration serveur invalide',
            'invalid-input-secret' => 'Configuration serveur invalide',
            'missing-input-response' => 'Token manquant',
            'invalid-input-response' => 'Token invalide ou expiré',
            'bad-request' => 'Requête invalide',
            'timeout-or-duplicate' => 'Token expiré ou déjà utilisé',
            'internal-error' => 'Erreur serveur Cloudflare',
        ];

        foreach ($errorCodes as $code) {
            if (isset($translations[$code])) {
                return $translations[$code];
            }
        }

        return 'Vérification échouée';
    }
}
