<?php

namespace FluentForm\App\Services\Form;

use FluentForm\App\Helpers\Helper;
use FluentForm\App\Models\FormMeta;
use FluentForm\App\Modules\Form\AkismetHandler;
use FluentForm\App\Modules\Form\CleanTalkHandler;
use FluentForm\App\Modules\Form\FormDataParser;
use FluentForm\App\Modules\Form\FormFieldsParser;
use FluentForm\App\Modules\HCaptcha\HCaptcha;
use FluentForm\App\Modules\ReCaptcha\ReCaptcha;
use FluentForm\App\Modules\Turnstile\Turnstile;
use FluentForm\App\Services\FormBuilder\Components\SelectCountry;
use FluentForm\Framework\Foundation\App;
use FluentForm\Framework\Helpers\ArrayHelper as Arr;
use FluentForm\Framework\Validator\ValidationException;

class FormValidationService
{
    protected $app;
    protected $form;
    protected $formData;

    public function __construct()
    {
        $this->app = App::getInstance();
    }

    public function setForm($form)
    {
        $this->form = $form;
    }

    public function setFormData($formData)
    {
        $this->formData = $formData;
    }

    /**
     * @param $fields
     * @param $formData
     * @return bool
     * @throws ValidationException
     */
    public function validateSubmission(&$fields, &$formData)
    {
        do_action('fluentform/before_form_validation', $fields, $formData);

        $this->preventMaliciousAttacks();

        $this->validateRestrictions($fields);

        $this->validateNonce();

        $this->validateReCaptcha();
        $this->validateHCaptcha();
        $this->validateTurnstile();

        foreach ($fields as $fieldName => $field) {
            if (isset($formData[$fieldName])) {
                $element = $field['element'];

                $formData[$fieldName] = apply_filters_deprecated('fluentform_input_data_' . $element, [
                        $formData[$fieldName],
                        $field,
                        $formData,
                        $this->form
                    ],
                    FLUENTFORM_FRAMEWORK_UPGRADE,
                    'fluentform/input_data_' . $element,
                    'Use fluentform/input_data_' . $element . ' instead of fluentform_input_data_' . $element
                );
                $formData[$fieldName] = apply_filters('fluentform/input_data_' . $element, $formData[$fieldName], $field, $formData, $this->form);
            }
        }

        $originalValidations = FormFieldsParser::getValidations($this->form, $formData, $fields);

        // Fire an event so that one can hook into it to work with the rules & messages.
        $originalValidations = apply_filters_deprecated('fluentform_validations', [
                $originalValidations,
                $this->form,
                $formData
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/validations',
            'Use fluentform/validations instead of fluentform_validations.'
        );
        $validations = apply_filters('fluentform/validations', $originalValidations, $this->form, $formData);

        /*
         * Clean talk fix for now
         * They should not hook fluentform_validations and return nothing!
         * We will remove this extra check once it's done
         */
        if ($originalValidations && (!$validations || !array_filter($validations))) {
            $validations = $originalValidations;
        }

        $validator = wpFluentForm('validator')->make($formData, $validations[0], $validations[1]);

        $errors = [];
        if ($validator->validate()->fails()) {
            foreach ($validator->errors() as $attribute => $rules) {
                $position = strpos($attribute, ']');

                if ($position) {
                    $attribute = substr($attribute, 0, strpos($attribute, ']') + 1);
                }

                $errors[$attribute] = $rules;
            }
            // Fire an event so that one can hook into it to work with the errors.
            $errors = apply_filters_deprecated('fluentform_validation_error', [
                    $errors,
                    $this->form,
                    $fields,
                    $formData
                ],
                FLUENTFORM_FRAMEWORK_UPGRADE,
                'fluentform/validation_error',
                'Use fluentform/validation_error instead of fluentform_validation_error.'
            );

            $errors = $this->app->applyFilters('fluentform/validation_error', $errors, $this->form, $fields, $formData);
        }

        foreach ($fields as $fieldKey => $field) {
            $field['data_key'] = $fieldKey;
            $inputName = Arr::get($field, 'raw.attributes.name');
            $field['name'] = $inputName;
            $error = $this->validateInput($field, $formData, $this->form);
            $error = apply_filters_deprecated('fluentform_validate_input_item_' . $field['element'], [
                    $error,
                    $field,
                    $formData,
                    $fields,
                    $this->form,
                    $errors
                ],
                FLUENTFORM_FRAMEWORK_UPGRADE,
                'fluentform/validate_input_item_' . $field['element'],
                'Use fluentform/validate_input_item_' . $field['element'] . ' instead of fluentform_validate_input_item_' . $field['element']
            );

            $error = apply_filters('fluentform/validate_input_item_' . $field['element'], $error, $field, $formData, $fields, $this->form, $errors);
            if ($error) {
                if (empty($errors[$inputName])) {
                    $errors[$inputName] = [];
                }
                if (is_string($error)) {
                    $error = [fluentform_sanitize_html($error)];
                } else {
                    if (is_array($error)) {
                        foreach ($error as $rule => $message) {
                            $error[$rule] = fluentform_sanitize_html($message);
                        }
                    }
                }
                $errors[$inputName] = array_merge($error, $errors[$inputName]);
            }
        }

        $errors = apply_filters_deprecated('fluentform_validation_errors', [
                $errors,
                $formData,
                $this->form,
                $fields
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/validation_errors',
            'Use fluentform/validation_errors instead of fluentform_validation_errors.'
        );

        $errors = apply_filters('fluentform/validation_errors', $errors, $formData, $this->form, $fields);

        if ('yes' == Helper::getFormMeta($this->form->id, '_has_user_registration') && !get_current_user_id()) {
            $errors = apply_filters_deprecated('fluentform_validation_user_registration_errors', [
                    $errors,
                    $formData,
                    $this->form,
                    $fields
                ],
                FLUENTFORM_FRAMEWORK_UPGRADE,
                'fluentform/validation_user_registration_errors',
                'Use fluentform/validation_user_registration_errors instead of fluentform_validation_user_registration_errors.'
            );

            $errors = apply_filters('fluentform/validation_user_registration_errors', $errors, $formData, $this->form, $fields);
        }

        if ('yes' == Helper::getFormMeta($this->form->id, '_has_user_update') && get_current_user_id()) {
            $errors = apply_filters_deprecated('fluentform_validation_user_update_errors', [
                    $errors,
                    $formData,
                    $this->form,
                    $fields
                ],
                FLUENTFORM_FRAMEWORK_UPGRADE,
                'fluentform/validation_user_update_errors',
                'Use fluentform/validation_user_update_errors instead of fluentform_validation_user_update_errors.'
            );
            $errors = apply_filters('fluentform/validation_user_update_errors', $errors, $formData, $this->form, $fields);
        }

        if ('update' == Arr::get(Helper::getFormMeta($this->form->id, 'postFeeds'), 'post_form_type')) {
            $errors = apply_filters('fluentform/validation_post_update_errors', $errors, $formData, $this->form, $fields);
        }

        if ($errors) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
            throw new ValidationException('', 423, null,  ['errors' => $errors]);
        }

        return true;
    }

    protected function validateInput($field, $formData, $form, $fieldName = '', $inputValue = [])
    {
        return Helper::validateInput($field, $formData, $form, $fieldName, $inputValue);
    }

    /**
     * Prevents malicious attacks when the submission
     * count exceeds in an allowed interval.
     * @throws ValidationException
     */
    public function preventMaliciousAttacks()
    {
        $prevent = apply_filters('fluentform/prevent_malicious_attacks', true, $this->form->id);

        if ($prevent) {
            $maxSubmissionCount = apply_filters('fluentform/max_submission_count', 5, $this->form->id);
            $minSubmissionInterval = apply_filters('fluentform/min_submission_interval', 30, $this->form->id);

            $interval = date('Y-m-d H:i:s', strtotime(current_time('mysql')) - $minSubmissionInterval);

            $clientIp = sanitize_text_field($this->app->request->getIp());
            $submissionCount = wpFluent()->table('fluentform_submissions')
                ->where('status', '!=', 'trashed')
                ->where('ip', $clientIp ?: '0.0.0.0')
                ->where('created_at', '>=', $interval)
                ->count();

            if ($submissionCount >= $maxSubmissionCount) {
                throw new ValidationException('', 429, null,  [
                    'errors' => [
                        'restricted' => [
                            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Sanitized by fluentform_sanitize_html
                            fluentform_sanitize_html(apply_filters(
                                'fluentform/too_many_requests',
                                __('Too Many Requests.', 'fluentform'),
                                $this->form->id
                            )),
                        ],
                    ]
                ]);
            }
        }
    }

    /**
     * Validate form data based on the form restrictions settings.
     *
     * @param $fields
     * @throws ValidationException
     */
    private function validateRestrictions(&$fields)
    {
        $formSettings = FormMeta::retrieve('formSettings', $this->form->id);

        $this->form->settings = is_array($formSettings) ? $formSettings : [];

        $isAllowed = [
            'status'  => true,
            'message' => '',
        ];

        // This will check the following restriction settings.
        // 1. limitNumberOfEntries
        // 2. scheduleForm
        // 3. requireLogin
        // 4. restricted submission based on ip, country and keywords

        /* This filter is deprecated and will be removed soon */
        $isAllowed = apply_filters('fluentform_is_form_renderable', $isAllowed, $this->form);

        $isAllowed = apply_filters('fluentform/is_form_renderable', $isAllowed, $this->form);

        if (!$isAllowed['status']) {
            throw new ValidationException('', 422, null,  [
                'errors' => [
                    'restricted' => [
                        // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Sanitized by fluentform_sanitize_html
                        fluentform_sanitize_html($isAllowed['message']),
                    ],
                ],
            ]);
        }

        // Since we are here, we should now handle if the form should be allowed to submit empty.
        $restrictions = Arr::get($this->form->settings, 'restrictions.denyEmptySubmission', []);

        $this->handleDenyEmptySubmission($restrictions, $fields);

        $formRestrictions = Arr::get($this->form->settings, 'restrictions.restrictForm', []);

        $this->handleRestrictedSubmission($formRestrictions, $fields);
    }

    /**
     * Handle response when empty form submission is not allowed.
     *
     * @param array $settings
     * @param $fields
     * @throws ValidationException
     */
    private function handleDenyEmptySubmission($settings, &$fields)
    {
        // Determine whether empty form submission is allowed or not.
        if (Arr::isTrue($settings, 'enabled')) {
            // confirm this form has no required fields.
            if (!FormFieldsParser::hasRequiredFields($this->form, $fields)) {
                // Filter out the form data which doesn't have values.
                $filteredFormData = array_filter(
                // Filter out the other meta fields that aren't actual inputs.
                    array_intersect_key($this->formData, $fields)
                );
                if (!count(Helper::arrayFilterRecursive($filteredFormData))) {
                    $defaultMessage = esc_html(__('Sorry! You can\'t submit an empty form.','fluentform'));
                    $customMessage = Arr::get($settings, 'message');
                    $customMessage = fluentform_sanitize_html(apply_filters('fluentform/deny_empty_submission_message', $customMessage, $this->form));

                    throw new ValidationException('', 422, null,  [
                        'errors' => [
                            'restricted' => [
                                // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Sanitized by fluentform_sanitize_html
                                !empty($customMessage) ? fluentform_sanitize_html($customMessage) : fluentform_sanitize_html($defaultMessage),
                            ],
                        ],
                    ]);
                }
            }
        }
    }

    /**
     * Handle response when form submission is restricted based on ip, country or keywords.
     *
     * @param array $settings
     * @param $fields
     * @throws ValidationException
     */
    protected function handleRestrictedSubmission($settings, &$fields)
    {
        // Determine this restriction is enabled ot not
        if (!Arr::isTrue($settings, 'enabled')) {
            return;
        }

        $rawIp = $this->app->request->getIp();
        if (is_array($rawIp)) {
            $rawIp = Arr::get($rawIp, '0');
        }
        $ip = sanitize_text_field($rawIp);
        if ($ip) {
            $this->checkIpRestriction($settings, $ip);
        }

        $isCountryRestrictionEnabled = Arr::isTrue($settings, 'fields.country.status');
        if ($isCountryRestrictionEnabled) {
            if ($ipInfo = $this->getIpInfo($ip)) {
                $country = Arr::get($ipInfo, 'country');
            } else {
                $country = $this->getIpBasedOnCountry($ip);
            }
            $this->checkCountryRestriction($settings, $country);
        }

        $this->checkKeyWordRestriction($settings);
    }


    /**
     * Validate nonce.
     * @throws ValidationException
     */
    protected function validateNonce()
    {
        $formId = $this->form->id;
        $shouldVerifyNonce = false;
        /* This filter is deprecated and will be removed soon. */
        $shouldVerifyNonce = $this->app->applyFilters('fluentform_nonce_verify', $shouldVerifyNonce, $formId);

        $shouldVerifyNonce = $this->app->applyFilters('fluentform/nonce_verify', $shouldVerifyNonce, $formId);

        if ($shouldVerifyNonce) {
            $nonce = Arr::get($this->formData, '_fluentform_' . $formId . '_fluentformnonce');
            if (!wp_verify_nonce($nonce, 'fluentform-submit-form')) {
                $errors = apply_filters_deprecated(
                    'fluentForm_nonce_error',
                    [
                        '_fluentformnonce' => [
                            __('Nonce verification failed, please try again.', 'fluentform'),
                        ],
                    ],
                    FLUENTFORM_FRAMEWORK_UPGRADE,
                    'fluentForm/nonce_error',
                    'Use fluentForm/nonce_error instead of fluentForm_nonce_error.'
                );

                $errors = $this->app->applyFilters('fluentForm/nonce_error', $errors);
                // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
                throw new ValidationException('', 422, null, ['errors' => $errors]);
            }
        }
    }

    /** Validate Akismet Spam
     * @throws ValidationException
     */
    public function handleAkismetSpamError()
    {
        $settings = get_option('_fluentform_global_form_settings');
        if (!$settings || 'validation_failed' != Arr::get($settings, 'misc.akismet_validation')) {
            return;
        }

        $errors = [
            '_fluentformakismet' => __('Submission marked as spammed. Please try again', 'fluentform'),
        ];
        // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
        throw new ValidationException('', 422, null, ['errors' => $errors]);
    }

    /** Validate CleanTalk Spam
     * @throws ValidationException
     */
    public function handleCleanTalkSpamError()
    {
        $settings = get_option('_fluentform_global_form_settings');
        if (!$settings || 'validation_failed' != Arr::get($settings, 'misc.cleantalk_validation')) {
            return;
        }

        $errors = [
            '_fluentformcleantalk' => __('Submission marked as spammed. Please try again', 'fluentform'),
        ];
        // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
        throw new ValidationException('', 422, null, ['errors' => $errors]);
    }

    /** Validate CleanTalk Spam While Using API
     * @throws ValidationException
     */
    public function handleCleanTalkSpamErrorUsingAPi()
    {
        $cleantalkSettings = get_option('_fluentform_cleantalk_details');

        if (
            !$cleantalkSettings ||
            'validation_failed' != Arr::get($cleantalkSettings, 'validation')
        ) {
            return;
        }

        $errors = [
            '_fluentformcleantalk' => __('Submission marked as spammed. Please try again', 'fluentform'),
        ];
        // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
        throw new ValidationException('', 422, null, ['errors' => $errors]);
    }

    public function isAkismetSpam($formData, $form)
    {
         if (!AkismetHandler::isEnabled()) {
            return false;
        }
        $isSpamCheck = apply_filters_deprecated(
            'fluentform_akismet_check_spam',
            [
                true,
                $form->id,
                $formData
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/akismet_check_spam',
            'Use fluentform/akismet_check_spam instead of fluentform_akismet_check_spam.'
        );

        $isSpamCheck = apply_filters('fluentform/akismet_check_spam', $isSpamCheck, $form->id, $formData);

        if (!$isSpamCheck) {
            return false;
        }
        // Let's validate now
        $isSpam = AkismetHandler::isSpamSubmission($formData, $form);

        $isSpam = apply_filters_deprecated(
            'fluentform_akismet_spam_result',
            [
                $isSpam,
                $form->id,
                $formData
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/akismet_spam_result',
            'Use fluentform/akismet_spam_result instead of fluentform_akismet_spam_result.'
        );
        return apply_filters('fluentform/akismet_spam_result', $isSpam, $form->id, $formData);
    }

    public function isCleanTalkSpam($formData, $form)
    {
        if (!CleanTalkHandler::isEnabled()) {
            return false;
        }
        $isSpamCheck = apply_filters('fluentform/cleantalk_check_spam', true, $form->id, $formData);

        if (!$isSpamCheck) {
            return false;
        }
        $isSpam = CleanTalkHandler::isSpamSubmission($formData, $form);

        return apply_filters('fluentform/cleantalk_spam_result', $isSpam, $form->id, $formData);
    }

    public function isCleanTalkSpamUsingApi($formData, $form)
    {
        if (!CleanTalkHandler::isCleantalkActivated()) {
            return false;
        }

        $isSpamCheck = apply_filters('fluentform/cleantalk_check_spam', true, $form->id, $formData);

        if (!$isSpamCheck) {
            return false;
        }

        $isSpam = CleanTalkHandler::spamSubmissionCheckWithApi($formData, $form);

        return apply_filters('fluentform/cleantalk_spam_result', $isSpam, $form->id, $formData);
    }

    /**
     * Validate reCaptcha.
     * Uses 'fluentform/disable_captcha' filter with 'recaptcha' as the captcha type since 6.0.3
     * @throws ValidationException
     */
    private function validateReCaptcha()
    {
        // Check if autoload_captcha is enabled and if it's not recaptcha, skip validation
        if ($this->shouldSkipCaptchaValidation('recaptcha')) {
            return;
        }

        $hasAutoRecap =  apply_filters_deprecated(
            'ff_has_auto_recaptcha',
            [
                false
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/has_recaptcha',
            'Use fluentform/has_recaptcha instead of ff_has_auto_recaptcha.'
        );
        $autoInclude = apply_filters('fluentform/has_recaptcha', $hasAutoRecap);
        $disableReCaptcha = apply_filters('fluentform/disable_captcha', false, $this->form, 'recaptcha');
        
        if (!$disableReCaptcha && (FormFieldsParser::hasElement($this->form, 'recaptcha') || $autoInclude)) {
            $keys = get_option('_fluentform_reCaptcha_details');
            $token = Arr::get($this->formData, 'g-recaptcha-response');
            $version = 'v2_visible';
            if (!empty($keys['api_version'])) {
                $version = $keys['api_version'];
            }
            $isValid = ReCaptcha::validate($token, $keys['secretKey'], $version);
            
            if (!$isValid) {
                // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception message, not output
                throw new ValidationException('', 422, null, [
                    'errors' => [
                        'g-recaptcha-response' => [
                            esc_html(__('reCaptcha verification failed, please try again.', 'fluentform')),
                        ],
                    ],
                ]);
            }
        }
    }

    /**
     * Validate hCaptcha.
     *
     * @throws ValidationException
     */
    private function validateHCaptcha()
    {
        // Check if autoload_captcha is enabled and if it's not hcaptcha, skip validation
        if ($this->shouldSkipCaptchaValidation('hcaptcha')) {
            return;
        }
        $hasAutoHcap = apply_filters_deprecated(
            'ff_has_auto_hcaptcha',
            [
                false
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/has_hcaptcha',
            'Use fluentform/has_hcaptcha instead of ff_has_auto_hcaptcha.'
        );
        $autoInclude = apply_filters('fluentform/has_hcaptcha', $hasAutoHcap);
        $disableHCaptcha = apply_filters('fluentform/disable_captcha', false, $this->form, 'hcaptcha');

        FormFieldsParser::resetData();
        if (!$disableHCaptcha && (FormFieldsParser::hasElement($this->form, 'hcaptcha') || $autoInclude)) {
            $keys = get_option('_fluentform_hCaptcha_details');
            $token = Arr::get($this->formData, 'h-captcha-response');
            $isValid = HCaptcha::validate($token, $keys['secretKey']);
            
            if (!$isValid) {
                throw new ValidationException('', 422, null, [
                    'errors' => [
                        'h-captcha-response' => [
                            esc_html(__('hCaptcha verification failed, please try again.', 'fluentform')),
                        ],
                    ],
                ]);
            }
        }
    }

    /**
     * Validate turnstile.
     *
     * @throws ValidationException
     */
    private function validateTurnstile()
    {
        // Check if autoload_captcha is enabled and if it's not turnstile, skip validation
        if ($this->shouldSkipCaptchaValidation('turnstile')) {
            return;
        }

        $hasAutoTurnsTile = apply_filters_deprecated(
            'ff_has_auto_turnstile',
            [
                false
            ],
            FLUENTFORM_FRAMEWORK_UPGRADE,
            'fluentform/has_turnstile',
            'Use fluentform/has_turnstile instead of ff_has_auto_turnstile.'
        );
        $autoInclude = apply_filters('fluentform/has_turnstile', $hasAutoTurnsTile);
        $disableTurnsTile = apply_filters('fluentform/disable_captcha', false, $this->form, 'turnstile');
        
        if (!$disableTurnsTile && (FormFieldsParser::hasElement($this->form, 'turnstile') || $autoInclude)) {
            $keys = get_option('_fluentform_turnstile_details');
            $token = Arr::get($this->formData, 'cf-turnstile-response');
            
            $isValid = Turnstile::validate($token, $keys['secretKey']);
            
            if (!$isValid) {
                throw new ValidationException('', 422, null, [
                    'errors' => [
                        'cf-turnstile-response' => [
                            esc_html(__('Turnstile verification failed, please try again.', 'fluentform')),
                        ],
                    ],
                ]);
            }
        }
    }


    /**
     * Delegate the validation rules & messages to the
     * ones that the validation library recognizes.
     *
     * @param $rules
     * @param $messages
     * @param array $search
     * @param array $replace
     * @return array
     */
    protected function delegateValidations($rules, $messages, $search = [], $replace = [])
    {
        $search = $search ?: ['max_file_size', 'allowed_file_types'];
        $replace = $replace ?: ['max', 'mimes'];

        foreach ($rules as &$rule) {
            $rule = str_replace($search, $replace, $rule);
        }

        foreach ($messages as $key => $message) {
            $newKey = str_replace($search, $replace, $key);
            $messages[$newKey] = $message;
            unset($messages[$key]);
        }

        return [$rules, $messages];
    }

    /**
     * Get IP info from ipinfo.io
     *
     * @throws ValidationException
     */
    private function getIpInfo($ip) {
        $token = Helper::getIpinfo();
        
        if (!$token) {
            return false;
        }
        
        $url = 'https://ipinfo.io/' . $ip . '?token=' . $token;
        $data = wp_remote_get($url);
        $code = wp_remote_retrieve_response_code($data);
        $body = wp_remote_retrieve_body($data);
        $result = \json_decode($body, true);
        if ($code === 200) {
            return $result;
        } else {
            $message = __('Sorry! There is an error in your geocode IP address settings. Please check the token', 'fluentform');
            self::throwValidationException($message);
        }
    }

    /**
     * Get IP and Country from geoplugin
     *
     * @throws ValidationException
     */
    private function getIpBasedOnCountry($ip) {
        $request = wp_remote_get("https://apip.cc/api-json/{$ip}");
        $code = wp_remote_retrieve_response_code($request);

        $message = __('Sorry! There is an error occurred in getting Country using ip-api.com. Please check form settings and try again.', 'fluentform');

        if ($code === 200) {
            $body = wp_remote_retrieve_body($request);
            $body = \json_decode($body, true);
            $status = Arr::get($body, 'status', false) === 'success';
            
            if (!$status) {
                return Helper::getCountryCodeFromHeaders();
            }

            if ($country = Arr::get($body,'CountryCode')) {
                return $country;
            } else {
                self::throwValidationException($message);
            }
        } else {
            if ($country = Helper::getCountryCodeFromHeaders()) {
                return $country;
            }
            self::throwValidationException($message);
        }
    }

    /**
     * @param $value
     * @param $providedKeywords
     * @return bool
     */
    public static function containsRestrictedKeywords($value, $providedKeywords) {
        preg_match_all('/\b[\p{L}\d\s]+\b/u', $value, $matches);
        $words = $matches[0] ?? [];

        foreach ($providedKeywords as $keyword) {
            foreach ($words as $word) {
                if (
                    strtoupper($word) === strtoupper($keyword) ||
                    preg_match('/\b' . strtoupper($keyword) . '\b/', strtoupper($word))
                ) {
                    return true;
                }
            }
        }

        return false;
    }


    /**
     * @throws ValidationException
     */
    private function checkIpRestriction($settings, $ip)
    {
        if (Arr::isTrue($settings, 'fields.ip.status') && $ip) {
            $providedIp = array_map('trim', explode(',', (string) Arr::get($settings, 'fields.ip.values', '')));

            $isFailed = Arr::get($settings, 'fields.ip.validation_type') === 'fail_on_condition_met';

            $failedSubmissionIfExists = $isFailed && in_array($ip, $providedIp);
            $allowSubmissionIfNotExists = !$isFailed && !in_array($ip, $providedIp);

            if ($failedSubmissionIfExists || $allowSubmissionIfNotExists) {
                $defaultMessage = __('Sorry! You can\'t submit a form from your IP address.', 'fluentform');
                $message = apply_filters('fluentform/ip_restriction_message', Arr::get($settings, 'fields.ip.message', $defaultMessage), $this->form);
                self::throwValidationException($message);
            }
        }
    }

    /**
     * @throws ValidationException
     */
    private function checkCountryRestriction($settings, $country)
    {
        if (Arr::isTrue($settings, 'fields.country.status') && $country) {
            $providedCountry = (array) Arr::get($settings, 'fields.country.values', []);

            $isFailed = Arr::get($settings, 'fields.country.validation_type') === 'fail_on_condition_met';

            $failedSubmissionIfExists = $isFailed && in_array($country, $providedCountry);
            $allowSubmissionIfNotExists = !$isFailed && !in_array($country, $providedCountry);

            if ($failedSubmissionIfExists || $allowSubmissionIfNotExists) {
                $defaultMessage = __('Sorry! You can\'t submit this form from the country you are residing.', 'fluentform');
                $message = apply_filters('fluentform/country_restriction_message', Arr::get($settings, 'fields.country.message', $defaultMessage), $this->form);
                self::throwValidationException($message);
            }
        }
    }

    private function checkKeyWordRestriction($settings)
    {
        if (!Arr::isTrue($settings, 'fields.keywords.status')) {
            return;
        }

        $keywords = Arr::get($settings, 'fields.keywords.values');
        if (!$keywords || !is_string($keywords)) {
            return;
        }
        $providedKeywords = explode(',', $keywords);
        $providedKeywords =  array_filter(array_map('trim', $providedKeywords));
        if (!$providedKeywords) {
            return;
        }
        $inputSubmission = array_intersect_key(
            $this->formData,
            array_flip(
                array_keys(
                    FormFieldsParser::getInputs($this->form)
                )
            )
        );
        $defaultMessage = __('Sorry! Your submission contains some restricted keywords.', 'fluentform');
        $message = apply_filters('fluentform/keyword_restriction_message', Arr::get($settings, 'fields.keywords.message', $defaultMessage), $this->form);

        self::checkKeywordsMatching($inputSubmission, $message, $providedKeywords);
    }

    private static function checkKeywordsMatching($inputSubmission, $message, $providedKeywords)
    {
        foreach ($inputSubmission as $value) {
            if (!empty($value)) {
                if (is_array($value)) {
                    self::checkKeywordsMatching($value, $message, $providedKeywords);
                } else {
                    if (self::containsRestrictedKeywords($value, $providedKeywords)) {
                        self::throwValidationException($message);
                    }
                }
            }
        }
    }

    /**
     * @throws ValidationException
     */
    public static function throwValidationException($message) {
        throw new ValidationException('', 422, null,  [
            'errors' => [
                'restricted' => [
                    // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Sanitized by fluentform_sanitize_html
                    fluentform_sanitize_html($message)
                ],
            ],
        ]);
    }

    /**
     * Check if captcha validation should be skipped based on autoload captcha settings
     *
     * When autoload captcha is enabled, only the selected captcha type should be validated.
     * This method returns true if the current captcha type is NOT the selected autoload type,
     * preventing unnecessary validation of multiple captcha types on the same form.
     *
     * @param string $captchaType The captcha type to check ('recaptcha', 'hcaptcha', 'turnstile')
     * @return bool True if validation should be skipped, false otherwise
     */
    private function shouldSkipCaptchaValidation($captchaType)
    {
        $globalSettings = get_option('_fluentform_global_form_settings');
        $autoloadEnabled = Arr::get($globalSettings, 'misc.autoload_captcha');

        // If autoload captcha is not enabled, don't skip any validation
        if (!$autoloadEnabled) {
            return false;
        }

        $selectedCaptchaType = Arr::get($globalSettings, 'misc.captcha_type');

        // If the current captcha type matches the selected autoload type, proceed with validation
        if ($captchaType === $selectedCaptchaType) {
            return false;
        }

        return true; // Skip validation for non-selected captcha types
    }
}
