<?php

namespace SugarCalendarBookings\Antispam\Captcha;

use SugarCalendarBookings\Options;
use SugarCalendarBookings\Plugin;

/**
 * Google reCAPTCHA v3 verifier.
 *
 * Mirrors the Google-shared scaffolding from {@see RecaptchaV2} (endpoint,
 * script URL/handle, conflict hosts, test-key hostname, theme/fail-message
 * option keys) instead of extending it — v2 and v3 keep separate site/secret
 * option slots so admins can configure both methods without losing the other's
 * credentials when switching sub-methods.
 *
 * @since 1.5.0
 */
final class RecaptchaV3 extends AbstractCaptchaVerifier {

	public const CATEGORY                = 'recaptcha';
	public const ENDPOINT_URL            = 'https://www.google.com/recaptcha/api/siteverify';
	public const SECRET_OPTION_KEY       = 'antispam.recaptcha_v3.secret_key';
	public const SECRET_FILTER_NAME      = 'sugar_calendar_bookings_antispam_recaptcha_v3_secret_key';
	public const SITE_KEY_OPTION_KEY     = 'antispam.recaptcha_v3.site_key';
	public const THEME_OPTION_KEY        = 'antispam.recaptcha.theme';
	public const FAIL_MESSAGE_OPTION_KEY = 'antispam.recaptcha.fail_message';

	public const SCRIPT_HANDLE  = 'scbookings-booking-form-google-recaptcha';
	public const SCRIPT_API_URL = 'https://www.google.com/recaptcha/api.js';
	public const CONFLICT_HOSTS = [
		'google.com/recaptcha',
		'gstatic.com/recaptcha',
	];

	public const TEST_KEY_HOSTNAME = 'testkey.google.com';

	/**
	 * Frontend config for the localized booking-form payload.
	 *
	 * @since 1.5.0
	 *
	 * @return array
	 */
	public function get_frontend_config(): array {

		return [
			'site_key' => $this->get_site_key(),
			'theme'    => $this->get_theme(),
		];
	}

	/**
	 * Default fail message for reCAPTCHA v3.
	 *
	 * @since 1.5.0
	 *
	 * @return string
	 */
	public function get_fail_message_default(): string {

		return esc_html__( 'reCAPTCHA verification failed, please try again.', 'sugar-calendar-bookings-scheduling-appointments-lite' );
	}

	/**
	 * Load api.js with `?render=SITE_KEY` so Google auto-mounts the floating
	 * v3 badge in the page corner — matching the standard v3 experience.
	 * Falls back to the abstract default ('explicit') when no key is set.
	 *
	 * @since 1.5.0
	 *
	 * @return string
	 */
	public function get_script_render_arg(): string {

		$site_key = $this->get_site_key();

		return $site_key !== '' ? $site_key : parent::get_script_render_arg();
	}

	/**
	 * Accept a v3 response when success is true and the score meets the threshold.
	 *
	 * @since 1.5.0
	 *
	 * @param object $decoded Decoded JSON response.
	 * @param string $context Verification context (e.g. 'booking_form_create').
	 *
	 * @return bool
	 */
	protected function is_response_valid( object $decoded, string $context ): bool {

		if ( empty( $decoded->success ) || ! isset( $decoded->score ) ) {
			return false;
		}

		return floatval( $decoded->score ) >= $this->get_threshold( $decoded, $context );
	}

	/**
	 * Resolve the minimum acceptable score, clamped to [0.0, 1.0].
	 *
	 * @since 1.5.0
	 *
	 * @param object $decoded Decoded JSON response.
	 * @param string $context Verification context (e.g. 'booking_form_create').
	 *
	 * @return float
	 */
	private function get_threshold( object $decoded, string $context ): float {

		$stored = (float) Plugin::get( Options::class )->get( 'antispam.recaptcha.threshold' );

		/**
		 * Filter the minimum acceptable reCAPTCHA v3 score.
		 *
		 * @since 1.5.0
		 *
		 * @param float  $min_score Minimum acceptable score.
		 * @param string $context   Verification context.
		 * @param object $response  Raw verification response.
		 */
		$min_score = (float) apply_filters(
			'sugar_calendar_bookings_antispam_recaptcha_v3_min_score',
			$stored,
			$context,
			$decoded
		);

		if ( $min_score < 0.0 || $min_score > 1.0 ) {
			$min_score = 0.5;
		}

		return $min_score;
	}
}
