<?php

namespace VulnTitan\Services;

use WP_Error;

class CaptchaService
{
    protected const CONTEXT_LOGIN = 'login';
    protected const CONTEXT_REGISTER = 'register';
    protected const CONTEXT_LOSTPASSWORD = 'lostpassword';
    protected const CONTEXT_COMMENT = 'comment';
    protected static bool $booted = false;
    protected static bool $renderedWidget = false;
    protected static array $commentRequestFieldOverrides = [];

    public static function boot(): void
    {
        if (self::$booted) {
            return;
        }

        self::$booted = true;

        add_action('login_enqueue_scripts', [__CLASS__, 'enqueueLoginScripts']);
        add_action('wp_enqueue_scripts', [__CLASS__, 'enqueueFrontendScripts']);
        add_action('login_form', [__CLASS__, 'renderLoginWidget']);
        add_action('register_form', [__CLASS__, 'renderRegisterWidget']);
        add_action('lostpassword_form', [__CLASS__, 'renderLostPasswordWidget']);
        add_action('comment_form_after_fields', [__CLASS__, 'renderCommentWidget']);
        add_action('comment_form_logged_in_after', [__CLASS__, 'renderCommentWidget']);
        add_filter('authenticate', [__CLASS__, 'verifyLoginCaptcha'], 40, 3);
        add_filter('registration_errors', [__CLASS__, 'verifyRegistrationCaptcha'], 20, 3);
        add_action('lostpassword_post', [__CLASS__, 'verifyLostPasswordCaptcha'], 10, 2);
        add_filter('preprocess_comment', [__CLASS__, 'clearCommentRequestContext'], 1);
        add_filter('rest_preprocess_comment', [__CLASS__, 'captureRestCommentContext'], 5, 2);
        add_filter('pre_comment_approved', [__CLASS__, 'verifyCommentCaptcha'], 8, 2);
    }

    public static function enqueueLoginScripts(): void
    {
        $provider = self::getProvider();
        if ($provider === 'none') {
            return;
        }

        if (!self::isContextEnabled(self::CONTEXT_LOGIN)
            && !self::isContextEnabled(self::CONTEXT_REGISTER)
            && !self::isContextEnabled(self::CONTEXT_LOSTPASSWORD)) {
            return;
        }

        self::enqueueProviderScript($provider);
    }

    public static function enqueueFrontendScripts(): void
    {
        if (!self::isContextEnabled(self::CONTEXT_COMMENT)) {
            return;
        }

        if (is_admin()) {
            return;
        }

        $provider = self::getProvider();
        if ($provider === 'none') {
            return;
        }

        self::enqueueProviderScript($provider);
    }

    public static function renderLoginWidget(): void
    {
        self::renderWidget(self::CONTEXT_LOGIN);
    }

    public static function renderRegisterWidget(): void
    {
        self::renderWidget(self::CONTEXT_REGISTER);
    }

    public static function renderLostPasswordWidget(): void
    {
        self::renderWidget(self::CONTEXT_LOSTPASSWORD);
    }

    public static function renderCommentWidget(): void
    {
        self::renderWidget(self::CONTEXT_COMMENT);
    }

    public static function verifyLoginCaptcha($user, $username, $password)
    {
        if (!self::isContextEnabled(self::CONTEXT_LOGIN) || !self::isProviderConfigured()) {
            return $user;
        }

        if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) {
            return $user;
        }

        if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'POST') {
            return $user;
        }

        if (sanitize_key((string) ($_REQUEST['action'] ?? '')) === 'vulntitan_2fa') {
            return $user;
        }

        $verification = self::verifyCurrentRequestToken();
        if ($verification['success']) {
            return $user;
        }

        return new WP_Error(
            'vulntitan_captcha_failed',
            (string) ($verification['message'] ?? __('Complete the CAPTCHA challenge to continue.', 'vulntitan'))
        );
    }

    public static function verifyRegistrationCaptcha(WP_Error $errors, $sanitizedUserLogin, $userEmail): WP_Error
    {
        if (!self::isContextEnabled(self::CONTEXT_REGISTER) || !self::isProviderConfigured()) {
            return $errors;
        }

        if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'POST') {
            return $errors;
        }

        $verification = self::verifyCurrentRequestToken();
        if ($verification['success']) {
            return $errors;
        }

        $errors->add(
            'vulntitan_captcha_failed',
            (string) ($verification['message'] ?? __('Complete the CAPTCHA challenge to continue.', 'vulntitan'))
        );

        return $errors;
    }

    public static function verifyLostPasswordCaptcha(WP_Error $errors, $userData): void
    {
        if (!self::isContextEnabled(self::CONTEXT_LOSTPASSWORD) || !self::isProviderConfigured()) {
            return;
        }

        if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'POST') {
            return;
        }

        $verification = self::verifyCurrentRequestToken();
        if ($verification['success']) {
            return;
        }

        $errors->add(
            'vulntitan_captcha_failed',
            (string) ($verification['message'] ?? __('Complete the CAPTCHA challenge to continue.', 'vulntitan'))
        );
    }

    /**
     * @param int|string|WP_Error $approved
     * @param array<string,mixed> $commentData
     * @return int|string|WP_Error
     */
    public static function verifyCommentCaptcha($approved, array $commentData)
    {
        if (is_wp_error($approved)) {
            return $approved;
        }

        if (!self::isContextEnabled(self::CONTEXT_COMMENT) || !self::isProviderConfigured()) {
            return $approved;
        }

        $verification = self::verifyCurrentRequestToken();
        if ($verification['success']) {
            return $approved;
        }

        return new WP_Error(
            'vulntitan_captcha_failed',
            (string) ($verification['message'] ?? __('Complete the CAPTCHA challenge before submitting your comment.', 'vulntitan')),
            403
        );
    }

    protected static function renderWidget(string $context): void
    {
        if (!self::isContextEnabled($context) || !self::isProviderConfigured()) {
            return;
        }

        $provider = self::getProvider();
        $siteKey = self::getSiteKey();
        if ($provider === 'none' || $siteKey === '') {
            return;
        }

        if (!self::$renderedWidget) {
            self::$renderedWidget = true;
            self::enqueueProviderScript($provider);
        }

        if ($provider === 'turnstile') {
            echo '<div class="vulntitan-captcha-wrap" style="margin:16px 0 10px;">';
            echo '<div class="cf-turnstile" data-sitekey="' . esc_attr($siteKey) . '"></div>';
            echo '</div>';
            return;
        }

        if ($provider === 'hcaptcha') {
            echo '<div class="vulntitan-captcha-wrap" style="margin:16px 0 10px;">';
            echo '<div class="h-captcha" data-sitekey="' . esc_attr($siteKey) . '"></div>';
            echo '</div>';
        }
    }

    protected static function enqueueProviderScript(string $provider): void
    {
        if ($provider === 'turnstile') {
            wp_enqueue_script(
                'vulntitan-turnstile',
                'https://challenges.cloudflare.com/turnstile/v0/api.js',
                [],
                null,
                true
            );
            wp_script_add_data('vulntitan-turnstile', 'async', true);
            wp_script_add_data('vulntitan-turnstile', 'defer', true);
            return;
        }

        if ($provider === 'hcaptcha') {
            wp_enqueue_script(
                'vulntitan-hcaptcha',
                'https://js.hcaptcha.com/1/api.js',
                [],
                null,
                true
            );
            wp_script_add_data('vulntitan-hcaptcha', 'async', true);
            wp_script_add_data('vulntitan-hcaptcha', 'defer', true);
        }
    }

    protected static function verifyCurrentRequestToken(): array
    {
        $provider = self::getProvider();
        $secretKey = self::getSecretKey();
        $token = self::readResponseToken($provider);

        if ($provider === 'none' || $secretKey === '') {
            return [
                'success' => true,
                'message' => '',
            ];
        }

        if ($token === '') {
            return [
                'success' => false,
                'message' => __('Complete the CAPTCHA challenge to continue.', 'vulntitan'),
            ];
        }

        $endpoint = $provider === 'turnstile'
            ? 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
            : 'https://hcaptcha.com/siteverify';

        $response = wp_remote_post($endpoint, [
            'timeout' => 15,
            'body' => [
                'secret' => $secretKey,
                'response' => $token,
                'remoteip' => FirewallService::detectClientIp(),
            ],
        ]);

        if (is_wp_error($response)) {
            return [
                'success' => false,
                'message' => __('CAPTCHA verification failed. Please try again.', 'vulntitan'),
            ];
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $body = json_decode((string) wp_remote_retrieve_body($response), true);

        if ($status !== 200 || !is_array($body) || empty($body['success'])) {
            return [
                'success' => false,
                'message' => __('CAPTCHA verification failed. Please try again.', 'vulntitan'),
            ];
        }

        return [
            'success' => true,
            'message' => '',
        ];
    }

    protected static function readResponseToken(string $provider): string
    {
        $field = $provider === 'turnstile' ? 'cf-turnstile-response' : 'h-captcha-response';

        if (array_key_exists($field, self::$commentRequestFieldOverrides)) {
            return trim((string) self::$commentRequestFieldOverrides[$field]);
        }

        return trim((string) wp_unslash($_POST[$field] ?? $_REQUEST[$field] ?? ''));
    }

    /**
     * @param array<string,mixed> $commentData
     * @return array<string,mixed>
     */
    public static function clearCommentRequestContext(array $commentData): array
    {
        self::$commentRequestFieldOverrides = [];

        return $commentData;
    }

    /**
     * @param array<string,mixed> $preparedComment
     * @return array<string,mixed>
     */
    public static function captureRestCommentContext(array $preparedComment, $request): array
    {
        self::$commentRequestFieldOverrides = self::extractRestCommentRequestFields($request);

        return $preparedComment;
    }

    protected static function extractRestCommentRequestFields($request): array
    {
        if (!is_object($request) || !method_exists($request, 'get_param')) {
            return [];
        }

        $fields = [];
        foreach (['cf-turnstile-response', 'h-captcha-response'] as $field) {
            $value = $request->get_param($field);
            if (is_scalar($value) || $value === null) {
                $fields[$field] = trim((string) $value);
            }
        }

        return $fields;
    }

    protected static function isContextEnabled(string $context): bool
    {
        $settings = FirewallService::getSettings();

        switch ($context) {
            case self::CONTEXT_LOGIN:
                return !empty($settings['captcha_login_enabled']);
            case self::CONTEXT_REGISTER:
                return !empty($settings['captcha_register_enabled']);
            case self::CONTEXT_LOSTPASSWORD:
                return !empty($settings['captcha_lostpassword_enabled']);
            case self::CONTEXT_COMMENT:
                return !empty($settings['captcha_comment_enabled']);
            default:
                return false;
        }
    }

    protected static function isProviderConfigured(): bool
    {
        return self::getProvider() !== 'none'
            && self::getSiteKey() !== ''
            && self::getSecretKey() !== '';
    }

    protected static function getProvider(): string
    {
        $settings = FirewallService::getSettings();

        return (string) ($settings['captcha_provider'] ?? 'none');
    }

    protected static function getSiteKey(): string
    {
        $settings = FirewallService::getSettings();

        return trim((string) ($settings['captcha_site_key'] ?? ''));
    }

    protected static function getSecretKey(): string
    {
        $settings = FirewallService::getSettings();

        return trim((string) ($settings['captcha_secret_key'] ?? ''));
    }
}
