<?php
/**
 * WooCommerce Security Addon bootstrap.
 *
 * @package Polanger_Dashboard_Tools
 */

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

require_once __DIR__ . '/includes/class-woocommerce-security-settings.php';
require_once __DIR__ . '/includes/class-woocommerce-security-page.php';
require_once __DIR__ . '/includes/class-woocommerce-security-rate-limiter.php';

if ( ! function_exists( 'pdt_wc_security_dependency_state' ) ) {
  /**
   * Resolve WooCommerce dependency state for this addon.
   *
   * @return array
   */
  function pdt_wc_security_dependency_state() {
    $woocommerce_file = WP_PLUGIN_DIR . '/woocommerce/woocommerce.php';
    $is_installed     = file_exists( $woocommerce_file );
    $is_active        = class_exists( 'WooCommerce' ) || function_exists( 'WC' );

    if ( $is_active ) {
      return array(
        'installed'   => true,
        'active'      => true,
        'available'   => true,
        'reason'      => '',
        'status_text' => __( 'Inactive', 'polanger-admin-menu-manager' ),
        'button_text' => __( 'Activate', 'polanger-admin-menu-manager' ),
        'fact_text'   => __( 'Requires WooCommerce', 'polanger-admin-menu-manager' ),
      );
    }

    if ( $is_installed ) {
      return array(
        'installed'   => true,
        'active'      => false,
        'available'   => false,
        'reason'      => __( 'WooCommerce is installed but currently inactive. Activate WooCommerce before enabling this addon.', 'polanger-admin-menu-manager' ),
        'status_text' => __( 'WooCommerce inactive', 'polanger-admin-menu-manager' ),
        'button_text' => __( 'Activate WooCommerce first', 'polanger-admin-menu-manager' ),
        'fact_text'   => __( 'WooCommerce installed', 'polanger-admin-menu-manager' ),
      );
    }

    return array(
      'installed'   => false,
      'active'      => false,
      'available'   => false,
      'reason'      => __( 'WooCommerce is not installed. Install WooCommerce before enabling this addon.', 'polanger-admin-menu-manager' ),
      'status_text' => __( 'WooCommerce not installed', 'polanger-admin-menu-manager' ),
      'button_text' => __( 'Install WooCommerce first', 'polanger-admin-menu-manager' ),
      'fact_text'   => __( 'WooCommerce required', 'polanger-admin-menu-manager' ),
    );
  }
}

/**
 * WooCommerce security orchestrator.
 */
class PDT_WooCommerce_Security {

  /**
   * WooCommerce addon settings.
   *
   * @var PDT_WC_Security_Settings
   */
  private $settings;

  /**
   * Settings tab controller.
   *
   * @var PDT_WC_Security_Page
   */
  private $page;

  /**
   * Rate limiter service.
   *
   * @var PDT_WC_Security_Rate_Limiter
   */
  private $rate_limiter;

  /**
   * reCAPTCHA settings service.
   *
   * @var PDT_Recaptcha_Settings|null
   */
  private $recaptcha_settings = null;

  /**
   * reCAPTCHA service.
   *
   * @var PDT_Recaptcha_Service|null
   */
  private $recaptcha_service = null;

  /**
   * Whether runtime hooks have already been registered.
   *
   * @var bool
   */
  private $hooks_registered = false;

  /**
   * Whether the dependency notice hook has already been registered.
   *
   * @var bool
   */
  private $dependency_notice_registered = false;

  /**
   * Constructor.
   */
  public function __construct() {
    $this->settings     = new PDT_WC_Security_Settings();
    $this->page         = new PDT_WC_Security_Page( $this->settings );
    $this->rate_limiter = new PDT_WC_Security_Rate_Limiter( $this->settings );

    if ( did_action( 'plugins_loaded' ) ) {
      $this->maybe_register_hooks();
      return;
    }

    add_action( 'plugins_loaded', array( $this, 'maybe_register_hooks' ), 20 );
  }

  /**
   * Register WooCommerce-dependent hooks after all active plugins are loaded.
   *
   * WordPress loads active plugins in option order, so this addon can be
   * booted before WooCommerce even when WooCommerce is active. Delaying the
   * dependency check prevents the settings tab from disappearing because of
   * plugin load order alone.
   *
   * @return void
   */
  public function maybe_register_hooks() {
    if ( $this->hooks_registered ) {
      return;
    }

    if ( ! $this->is_woocommerce_available() ) {
      $this->register_dependency_notice();
      return;
    }

    $this->hooks_registered = true;

    $this->page->register_hooks();
    $this->rate_limiter->register_hooks();

    if ( class_exists( 'PDT_Recaptcha_Settings' ) && class_exists( 'PDT_Recaptcha_Service' ) ) {
      $this->recaptcha_settings = new PDT_Recaptcha_Settings();
      $this->recaptcha_service  = new PDT_Recaptcha_Service( $this->recaptcha_settings );

      add_filter( 'pdt_recaptcha_location_keys', array( $this, 'register_recaptcha_location_keys' ) );
      add_filter( 'pdt_recaptcha_location_definitions', array( $this, 'register_recaptcha_locations' ), 10, 2 );

      if ( function_exists( 'pdt_is_addon_active' ) && pdt_is_addon_active( 'recaptcha' ) ) {
        add_action( 'wp_enqueue_scripts', array( $this, 'maybe_enqueue_recaptcha_assets' ) );
        add_action( 'woocommerce_login_form', array( $this, 'render_login_recaptcha' ), 30 );
        add_action( 'woocommerce_register_form', array( $this, 'render_register_recaptcha' ), 30 );
        add_action( 'woocommerce_lostpassword_form', array( $this, 'render_lostpassword_recaptcha' ), 30 );
        add_filter( 'woocommerce_process_login_errors', array( $this, 'verify_login_recaptcha' ), 10, 3 );
        add_filter( 'woocommerce_process_registration_errors', array( $this, 'verify_registration_recaptcha' ), 10, 4 );
        add_action( 'lostpassword_post', array( $this, 'verify_lostpassword_recaptcha' ), 10, 2 );
      }
    }

    add_filter( 'pdt_2fa_pending_context', array( $this, 'capture_woocommerce_2fa_context' ), 10, 3 );
    add_filter( 'pdt_2fa_success_redirect_url', array( $this, 'maybe_redirect_2fa_to_woocommerce' ), 10, 3 );
    add_filter( 'pdt_2fa_back_to_login_url', array( $this, 'maybe_redirect_2fa_back_to_woocommerce' ), 10, 3 );
  }

  /**
   * Register the WooCommerce dependency notice once.
   *
   * @return void
   */
  private function register_dependency_notice() {
    if ( $this->dependency_notice_registered ) {
      return;
    }

    $this->dependency_notice_registered = true;
    add_action( 'admin_notices', array( $this, 'maybe_render_dependency_notice' ) );
  }

  /**
   * Whether WooCommerce is available.
   *
   * @return bool
   */
  private function is_woocommerce_available() {
    return class_exists( 'WooCommerce' ) || function_exists( 'WC' );
  }

  /**
   * Capture the original WooCommerce login destination for 2FA flows.
   *
   * @param array        $pending_context Pending 2FA context.
   * @param WP_User      $user            Authenticated user.
   * @param object|null  $provider        2FA provider instance.
   * @return array
   */
  public function capture_woocommerce_2fa_context( $pending_context, $user, $provider = null ) {
    unset( $user, $provider );

    if ( ! is_array( $pending_context ) ) {
      return $pending_context;
    }

    if ( ! $this->is_woocommerce_login_request() ) {
      return $pending_context;
    }

    $redirect_target = $this->get_woocommerce_login_redirect_target();

    $pending_context['origin']            = 'woocommerce';
    $pending_context['redirect_to']       = $redirect_target;
    $pending_context['back_to_login_url'] = $redirect_target;

    return $pending_context;
  }

  /**
   * Redirect completed 2FA challenges back into WooCommerce flows.
   *
   * @param string       $url             Default redirect URL.
   * @param array        $pending_context Pending 2FA context.
   * @param WP_User|null $user            Verified user.
   * @return string
   */
  public function maybe_redirect_2fa_to_woocommerce( $url, $pending_context, $user = null ) {
    unset( $user );

    if ( ! is_array( $pending_context ) || 'woocommerce' !== ( $pending_context['origin'] ?? '' ) ) {
      return $url;
    }

    if ( ! empty( $pending_context['redirect_to'] ) ) {
      return $this->sanitize_woocommerce_redirect( $pending_context['redirect_to'] );
    }

    return $this->get_woocommerce_login_fallback_url();
  }

  /**
   * Point "back to login" links at the original WooCommerce screen.
   *
   * @param string $url             Default login URL.
   * @param array  $pending_context Pending 2FA context.
   * @param array  $args            Optional query arguments.
   * @return string
   */
  public function maybe_redirect_2fa_back_to_woocommerce( $url, $pending_context, $args = array() ) {
    if ( ! is_array( $pending_context ) || 'woocommerce' !== ( $pending_context['origin'] ?? '' ) ) {
      return $url;
    }

    $target = ! empty( $pending_context['back_to_login_url'] )
      ? $pending_context['back_to_login_url']
      : $this->get_woocommerce_login_fallback_url();

    $target = $this->sanitize_woocommerce_redirect( $target );

    if ( ! empty( $args ) ) {
      $target = add_query_arg( $args, $target );
    }

    return $target;
  }

  /**
   * Register WooCommerce-specific reCAPTCHA locations.
   *
   * @param array $locations     Existing location definitions.
   * @param array $location_keys Existing location keys.
   * @return array
   */
  public function register_recaptcha_locations( $locations, $location_keys = array() ) {
    unset( $location_keys );

    if ( ! is_array( $locations ) ) {
      $locations = array();
    }

    $wc_locations = array(
      'wc_login'        => array(
        'label'       => __( 'WooCommerce Login Forms', 'polanger-admin-menu-manager' ),
        'description' => __( 'Add reCAPTCHA to WooCommerce customer login forms, including My Account and checkout login prompts.', 'polanger-admin-menu-manager' ),
        'default'     => '',
      ),
      'wc_register'     => array(
        'label'       => __( 'WooCommerce Registration Form', 'polanger-admin-menu-manager' ),
        'description' => __( 'Add reCAPTCHA to the WooCommerce customer registration form on My Account.', 'polanger-admin-menu-manager' ),
        'default'     => '',
      ),
      'wc_lostpassword' => array(
        'label'       => __( 'WooCommerce Lost Password Form', 'polanger-admin-menu-manager' ),
        'description' => __( 'Add reCAPTCHA to WooCommerce password reset request forms.', 'polanger-admin-menu-manager' ),
        'default'     => '',
      ),
    );

    $ordered  = array();
    $inserted = false;

    foreach ( $locations as $location_key => $location ) {
      $ordered[ $location_key ] = $location;

      if ( 'lostpassword' === $location_key ) {
        foreach ( $wc_locations as $wc_location_key => $wc_location ) {
          $ordered[ $wc_location_key ] = $wc_location;
        }
        $inserted = true;
      }
    }

    if ( ! $inserted ) {
      foreach ( $wc_locations as $wc_location_key => $wc_location ) {
        $ordered[ $wc_location_key ] = $wc_location;
      }
    }

    return $ordered;
  }

  /**
   * Register WooCommerce location keys and defaults without translations.
   *
   * @param array $locations Existing location keys.
   * @return array
   */
  public function register_recaptcha_location_keys( $locations ) {
    if ( ! is_array( $locations ) ) {
      $locations = array();
    }

    $wc_locations = array(
      'wc_login'        => '',
      'wc_register'     => '',
      'wc_lostpassword' => '',
    );

    $ordered  = array();
    $inserted = false;

    foreach ( $locations as $location_key => $location ) {
      $ordered[ $location_key ] = $location;

      if ( 'lostpassword' === $location_key ) {
        foreach ( $wc_locations as $wc_location_key => $wc_location ) {
          $ordered[ $wc_location_key ] = $wc_location;
        }
        $inserted = true;
      }
    }

    if ( ! $inserted ) {
      foreach ( $wc_locations as $wc_location_key => $wc_location ) {
        $ordered[ $wc_location_key ] = $wc_location;
      }
    }

    return $ordered;
  }

  /**
   * Maybe render an admin notice when WooCommerce is missing.
   *
   * @return void
   */
  public function maybe_render_dependency_notice() {
    if ( ! current_user_can( 'manage_options' ) ) {
      return;
    }

    $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
    if ( $screen && ! in_array( $screen->id, array( 'toplevel_page_polanger-addons', 'toplevel_page_polanger-settings', 'polanger_page_polanger-settings' ), true ) ) {
      return;
    }

    $dependency_state = pdt_wc_security_dependency_state();
    $message          = ! empty( $dependency_state['reason'] )
      ? $dependency_state['reason']
      : __( 'WooCommerce Security is active, but WooCommerce is not available.', 'polanger-admin-menu-manager' );

    echo '<div class="notice notice-warning"><p>' . esc_html( $message ) . '</p></div>';
  }

  /**
   * Enqueue reCAPTCHA assets on WooCommerce account-related screens.
   *
   * @return void
   */
  public function maybe_enqueue_recaptcha_assets() {
    if ( ! $this->has_enabled_wc_recaptcha_locations() || ! $this->has_recaptcha_service() ) {
      return;
    }

    if ( ! $this->is_woocommerce_auth_screen() ) {
      return;
    }

    $this->recaptcha_service->enqueue_script();

    $config = $this->recaptcha_service->get_config();
    if ( 'v3' === $config['version'] ) {
      $script_url = $this->get_asset_url( 'assets/js/woocommerce-recaptcha-v3.js' );

      if ( '' === $script_url ) {
        return;
      }

      wp_enqueue_script(
        'pdt-woocommerce-recaptcha-v3',
        $script_url,
        array( 'pdt-recaptcha-api' ),
        $this->get_asset_version(),
        true
      );
    }
  }

  /**
   * Resolve a public asset URL for this addon.
   *
   * @param string $relative_path Relative path inside the addon directory.
   * @return string
   */
  private function get_asset_url( $relative_path ) {
    $relative_path = ltrim( (string) $relative_path, '/' );

    if ( defined( 'PDT_WC_SECURITY_URL' ) ) {
      return trailingslashit( PDT_WC_SECURITY_URL ) . $relative_path;
    }

    if ( defined( 'PDT_PLUGIN_FILE' ) ) {
      return plugins_url( 'addons/woocommerce-security/' . $relative_path, PDT_PLUGIN_FILE );
    }

    return '';
  }

  /**
   * Resolve the current addon asset version.
   *
   * @return string
   */
  private function get_asset_version() {
    if ( defined( 'PDT_WC_SECURITY_VERSION' ) ) {
      return PDT_WC_SECURITY_VERSION;
    }

    if ( defined( 'PDT_VERSION' ) ) {
      return PDT_VERSION;
    }

    return '1.0.0';
  }

  /**
   * Render reCAPTCHA on WooCommerce login forms.
   *
   * @return void
   */
  public function render_login_recaptcha() {
    $this->render_recaptcha( 'wc_login' );
  }

  /**
   * Render reCAPTCHA on WooCommerce registration forms.
   *
   * @return void
   */
  public function render_register_recaptcha() {
    $this->render_recaptcha( 'wc_register' );
  }

  /**
   * Render reCAPTCHA on WooCommerce lost password forms.
   *
   * @return void
   */
  public function render_lostpassword_recaptcha() {
    $this->render_recaptcha( 'wc_lostpassword' );
  }

  /**
   * Verify WooCommerce login reCAPTCHA.
   *
   * @param WP_Error $errors   Validation errors.
   * @param string   $username Submitted username.
   * @param string   $password Submitted password.
   * @return WP_Error
   */
  public function verify_login_recaptcha( $errors, $username, $password ) {
    unset( $username, $password );

    if ( ! $this->should_verify_recaptcha_location( 'wc_login' ) ) {
      return $errors;
    }

    if ( ! $this->recaptcha_service->verify( 'wc_login' ) ) {
      $errors->add(
        'pdt_wc_recaptcha_failed',
        __( 'reCAPTCHA verification failed. Please try again.', 'polanger-admin-menu-manager' )
      );
    }

    return $errors;
  }

  /**
   * Verify WooCommerce registration reCAPTCHA.
   *
   * @param WP_Error $errors   Validation errors.
   * @param string   $username Submitted username.
   * @param string   $password Submitted password.
   * @param string   $email    Submitted email.
   * @return WP_Error
   */
  public function verify_registration_recaptcha( $errors, $username, $password, $email ) {
    unset( $username, $password, $email );

    if ( ! $this->should_verify_recaptcha_location( 'wc_register' ) ) {
      return $errors;
    }

    if ( ! $this->recaptcha_service->verify( 'wc_register' ) ) {
      $errors->add(
        'pdt_wc_recaptcha_failed',
        __( 'reCAPTCHA verification failed. Please try again.', 'polanger-admin-menu-manager' )
      );
    }

    return $errors;
  }

  /**
   * Verify WooCommerce lost password reCAPTCHA.
   *
   * @param WP_Error     $errors    Validation errors.
   * @param WP_User|bool $user_data Resolved user.
   * @return void
   */
  public function verify_lostpassword_recaptcha( $errors, $user_data ) {
    unset( $user_data );

    if ( ! $errors instanceof WP_Error ) {
      return;
    }

    if ( ! $this->should_verify_recaptcha_location( 'wc_lostpassword' ) ) {
      return;
    }

    if ( ! $this->is_woocommerce_lostpassword_request() ) {
      return;
    }

    if ( ! $this->recaptcha_service->verify( 'wc_lostpassword' ) ) {
      $errors->add(
        'pdt_wc_recaptcha_failed',
        __( 'reCAPTCHA verification failed. Please try again.', 'polanger-admin-menu-manager' )
      );
    }
  }

  /**
   * Render a configured reCAPTCHA location.
   *
   * @param string $location Location key.
   * @return void
   */
  private function render_recaptcha( $location ) {
    if ( ! $this->should_render_recaptcha_location( $location ) ) {
      return;
    }

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

  /**
   * Whether the current request should render a location.
   *
   * @param string $location Location key.
   * @return bool
   */
  private function should_render_recaptcha_location( $location ) {
    return $this->should_verify_recaptcha_location( $location );
  }

  /**
   * Whether the current request should verify a location.
   *
   * @param string $location Location key.
   * @return bool
   */
  private function should_verify_recaptcha_location( $location ) {
    if ( ! $this->has_recaptcha_service() ) {
      return false;
    }

    if ( ! $this->recaptcha_service->is_configured() ) {
      return false;
    }

    return $this->recaptcha_service->is_location_enabled( $location );
  }

  /**
   * Whether WooCommerce reCAPTCHA integration is enabled anywhere.
   *
   * @return bool
   */
  private function has_enabled_wc_recaptcha_locations() {
    return $this->should_verify_recaptcha_location( 'wc_login' )
      || $this->should_verify_recaptcha_location( 'wc_register' )
      || $this->should_verify_recaptcha_location( 'wc_lostpassword' );
  }

  /**
   * Whether the current request is a WooCommerce customer login attempt.
   *
   * @return bool
   */
  private function is_woocommerce_login_request() {
    if ( ! isset( $_POST['login'], $_POST['username'], $_POST['password'] ) ) {
      return false;
    }

    $nonce_value = '';

    if ( isset( $_REQUEST['woocommerce-login-nonce'] ) ) {
      $nonce_value = wp_unslash( $_REQUEST['woocommerce-login-nonce'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    } elseif ( isset( $_REQUEST['_wpnonce'] ) ) {
      $nonce_value = wp_unslash( $_REQUEST['_wpnonce'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    }

    return (bool) wp_verify_nonce( $nonce_value, 'woocommerce-login' );
  }

  /**
   * Resolve the correct post-login destination for WooCommerce 2FA.
   *
   * Mirrors WooCommerce's own login redirect logic so the user returns to the
   * same frontend flow after completing verification.
   *
   * @return string
   */
  private function get_woocommerce_login_redirect_target() {
    if ( ! empty( $_POST['redirect'] ) ) {
      $redirect = wp_unslash( $_POST['redirect'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    } elseif ( function_exists( 'wc_get_raw_referer' ) && wc_get_raw_referer() ) {
      $redirect = wc_get_raw_referer();
    } else {
      $redirect = $this->get_woocommerce_login_fallback_url();
    }

    $redirect = remove_query_arg( array( 'wc_error', 'password-reset' ), $redirect );

    return $this->sanitize_woocommerce_redirect( $redirect );
  }

  /**
   * Get the canonical WooCommerce frontend login page.
   *
   * @return string
   */
  private function get_woocommerce_login_fallback_url() {
    if ( function_exists( 'wc_get_page_permalink' ) ) {
      $myaccount_url = wc_get_page_permalink( 'myaccount' );
      if ( ! empty( $myaccount_url ) ) {
        return $myaccount_url;
      }
    }

    return home_url( '/' );
  }

  /**
   * Validate a WooCommerce redirect target against the site domain.
   *
   * @param string $url Candidate redirect URL.
   * @return string
   */
  private function sanitize_woocommerce_redirect( $url ) {
    return wp_validate_redirect( $url, $this->get_woocommerce_login_fallback_url() );
  }

  /**
   * Whether the current frontend screen can contain WooCommerce auth forms.
   *
   * @return bool
   */
  private function is_woocommerce_auth_screen() {
    $is_account_page = function_exists( 'is_account_page' ) && is_account_page();
    $is_checkout     = function_exists( 'is_checkout' ) && is_checkout();

    return $is_account_page || $is_checkout;
  }

  /**
   * Whether the current POST request is WooCommerce lost password form submit.
   *
   * @return bool
   */
  private function is_woocommerce_lostpassword_request() {
    if ( ! isset( $_POST['wc_reset_password'], $_POST['user_login'] ) ) {
      return false;
    }

    $nonce_value = '';

    if ( isset( $_REQUEST['woocommerce-lost-password-nonce'] ) ) {
      $nonce_value = sanitize_text_field( wp_unslash( $_REQUEST['woocommerce-lost-password-nonce'] ) );
    } elseif ( isset( $_REQUEST['_wpnonce'] ) ) {
      $nonce_value = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
    }

    return wp_verify_nonce( $nonce_value, 'lost_password' );
  }

  /**
   * Whether the reCAPTCHA services are available.
   *
   * @return bool
   */
  private function has_recaptcha_service() {
    return $this->recaptcha_settings instanceof PDT_Recaptcha_Settings
      && $this->recaptcha_service instanceof PDT_Recaptcha_Service;
  }
}
