<?php
/**
 * reCAPTCHA Addon — Core Service
 *
 * Shared API used by login-page and comment integrations.
 * Handles script enqueueing, widget rendering (v2 / v3), and
 * server-side token verification.
 *
 * @package Polanger_Dashboard_Tools
 */

if ( ! defined( 'ABSPATH' ) ) {
  exit;
}

/**
 * reCAPTCHA Service.
 */
class PDT_Recaptcha_Service {

  /**
   * Settings service.
   *
   * @var PDT_Recaptcha_Settings
   */
  private $settings;

  /**
   * Cached config so get_config() only reads once per request.
   *
   * @var array|null
   */
  private $config_cache = null;

  /**
   * Tracks whether the comment-form widget was already rendered by the
   * comment-security engine's extra-fields hook, to avoid double output.
   *
   * @var bool
   */
  private $comment_rendered = false;

  /**
   * Constructor.
   *
   * @param PDT_Recaptcha_Settings $settings Settings service.
   */
  public function __construct( $settings ) {
    $this->settings = $settings;
  }

  // -------------------------------------------------------------------------
  // Public API
  // -------------------------------------------------------------------------

  /**
   * Whether the addon has both keys filled in.
   *
   * @return bool
   */
  public function is_configured() {
    $cfg = $this->get_config();
    return '' !== $cfg['site_key'] && '' !== $cfg['secret_key'];
  }

  /**
   * Return the resolved config array.
   *
   * @return array {
   *   @type string $site_key     Google site key.
   *   @type string $secret_key   Google secret key.
   *   @type string $version      'v2' or 'v3'.
   *   @type float  $v3_threshold Score threshold for v3 (0–1).
   * }
   */
  public function get_config() {
    if ( null !== $this->config_cache ) {
      return $this->config_cache;
    }

    $s = $this->settings->get_settings();

    $this->config_cache = array(
      'site_key'     => (string) $s['site_key'],
      'secret_key'   => (string) $s['secret_key'],
      'version'      => in_array( $s['version'], array( 'v2', 'v3' ), true ) ? $s['version'] : 'v2',
      'v3_threshold' => (float) $s['v3_threshold'],
    );

    return $this->config_cache;
  }

  /**
   * Whether a specific location is enabled for reCAPTCHA.
   *
   * @param string $location One of: login, register, lostpassword, comments.
   * @return bool
   */
  public function is_location_enabled( $location ) {
    $s = $this->settings->get_settings();
    return ! empty( $s[ $location ] );
  }

  /**
   * Enqueue the Google reCAPTCHA JS library.
   *
   * For v2 the library loads normally and WordPress renders the checkbox.
   * For v3 the library is loaded with `?render=SITE_KEY` and an inline
   * script handles token injection into hidden fields.
   *
   * @return void
   */
  public function enqueue_script() {
    if ( ! $this->is_configured() ) {
      return;
    }

    $cfg = $this->get_config();

    $url = 'https://www.google.com/recaptcha/api.js';
    if ( 'v3' === $cfg['version'] ) {
      $url .= '?render=' . rawurlencode( $cfg['site_key'] );
    }

    wp_enqueue_script(
      'pdt-recaptcha-api',
      $url,
      array(),
      null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- external, versioning meaningless
      true
    );
  }

  /**
   * Output the reCAPTCHA widget markup for a given context.
   *
   * v2 → renders the `<div class="g-recaptcha">` checkbox widget.
   * v3 → renders a hidden field that the runtime JS populates with a token.
   *
   * @param string $action Short action label passed to reCAPTCHA v3 (e.g. 'login', 'comment').
   * @return void
   */
  public function render_widget( $action = 'submit' ) {
    if ( ! $this->is_configured() ) {
      return;
    }

    $cfg      = $this->get_config();
    $site_key = $cfg['site_key'];
    $version  = $cfg['version'];
    $action   = sanitize_key( $action );

    if ( 'v2' === $version ) {
      echo '<div class="pdt-recaptcha-widget g-recaptcha" data-sitekey="' . esc_attr( $site_key ) . '"></div>';
    } else {
      // v3: hidden field; JS fills the value before form submit.
      echo '<input type="hidden" class="pdt-recaptcha-v3-token" '
        . 'name="g-recaptcha-response" '
        . 'id="pdt-g-recaptcha-' . esc_attr( $action ) . '" '
        . 'data-action="' . esc_attr( $action ) . '" '
        . 'data-site-key="' . esc_attr( $site_key ) . '" />';
    }
  }

  /**
   * Verify the `g-recaptcha-response` token from the current POST request.
   *
   * @param string $action Expected v3 action (ignored for v2). E.g. 'login', 'comment'.
   * @return bool True when verification passes.
   */
  public function verify( $action = 'submit' ) {
    if ( ! $this->is_configured() ) {
      return true; // No keys → bypass (graceful degradation).
    }

    $cfg = $this->get_config();

    // phpcs:ignore WordPress.Security.NonceVerification.Missing
    $token = isset( $_POST['g-recaptcha-response'] )
      ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) )
      : '';

    if ( '' === $token ) {
      return false;
    }

    $remote_ip = isset( $_SERVER['REMOTE_ADDR'] )
      ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) )
      : '';

    $response = wp_remote_post(
      'https://www.google.com/recaptcha/api/siteverify',
      array(
        'timeout' => 10,
        'body'    => array(
          'secret'   => $cfg['secret_key'],
          'response' => $token,
          'remoteip' => $remote_ip,
        ),
      )
    );

    if ( is_wp_error( $response ) ) {
      // Network failure — fail open to avoid blocking legitimate users.
      return true;
    }

    $body = json_decode( wp_remote_retrieve_body( $response ), true );

    if ( empty( $body['success'] ) ) {
      return false;
    }

    // v3 extra checks.
    if ( 'v3' === $cfg['version'] ) {
      $score = isset( $body['score'] ) ? (float) $body['score'] : 0.0;
      if ( $score < $cfg['v3_threshold'] ) {
        return false;
      }

      // Verify returned action matches expected action.
      if ( ! empty( $action ) && isset( $body['action'] ) ) {
        if ( sanitize_key( $body['action'] ) !== sanitize_key( $action ) ) {
          return false;
        }
      }
    }

    // Hostname validation (when available).
    if ( isset( $body['hostname'] ) ) {
      $expected = wp_parse_url( home_url(), PHP_URL_HOST );
      if ( ! empty( $expected ) ) {
        $normalize = static function ( $host ) {
          return preg_replace( '/^www\./', '', strtolower( trim( (string) $host ) ) );
        };
        if ( $normalize( $expected ) !== $normalize( $body['hostname'] ) ) {
          return false;
        }
      }
    }

    return true;
  }

  // -------------------------------------------------------------------------
  // Comment-form helpers (deduplication)
  // -------------------------------------------------------------------------

  /**
   * Check if comment reCAPTCHA was already rendered.
   *
   * @return bool
   */
  public function was_comment_rendered() {
    return $this->comment_rendered;
  }

  /**
   * Mark comment reCAPTCHA as rendered to prevent duplicates.
   *
   * @return void
   */
  public function mark_comment_rendered() {
    $this->comment_rendered = true;
  }

  /**
   * Render the widget for the comment form, called from the comment-security
   * engine's `pdt_comment_security_extra_fields` hook.
   *
   * Sets a flag so other hooks skip output.
   *
   * @return void
   */
  public function render_for_comment_via_engine() {
    $this->comment_rendered = true;
    $this->render_for_comment_widget();
  }

  /**
   * Render the widget for the comment form, called directly from the
   * `comment_form_after_fields` / `comment_form_logged_in_after` hooks when
   * the comment-security addon is not active.
   *
   * @return void
   */
  public function maybe_render_for_comment() {
    if ( $this->comment_rendered ) {
      return; // Already rendered via engine hook.
    }
    $this->render_for_comment_widget();
  }

  /**
   * Internal: output the comment widget if location is enabled.
   *
   * @return void
   */
  private function render_for_comment_widget() {
    if ( ! $this->is_location_enabled( 'comments' ) ) {
      return;
    }

    $s = $this->settings->get_settings();
    if ( is_user_logged_in() && empty( $s['comment_logged_in'] ) ) {
      return;
    }

    echo '<div class="pdt-recaptcha-comment-wrap">';
    $this->render_widget( 'comment' );
    echo '</div>';
  }
}
