<?php
/**
 * Plugin core orchestration.
 *
 * @package SHC4WC
 */

// phpcs:disable Squiz.Commenting,WordPress.WP.Capabilities.Unknown
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class SHC4WC_Plugin {
	const OAUTH_SITE_KEY_OPTION       = 'shc4wc_oauth_site_key';
	const OAUTH_REGISTERED_URL_OPTION = 'shc4wc_oauth_registered_site_url';
	const ADMIN_DIAGNOSTICS_OPTION    = 'shc4wc_admin_diagnostics';

	private static $instance = null;

	/**
	 * Last Worker /register failure details for logging and redirects.
	 *
	 * @var array
	 */
	private $worker_register_diagnostics = array();

	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		add_action( 'admin_post_shc4wc_oauth_start', array( $this, 'handle_oauth_start' ) );
		add_action( 'admin_post_shc4wc_oauth_revoke', array( $this, 'handle_oauth_revoke' ) );
		add_action( 'admin_post_shc4wc_worker_register', array( $this, 'handle_worker_register' ) );
		add_action( 'init', array( $this, 'handle_oauth_worker_return' ), 5 );
		add_action( 'init', array( $this, 'maybe_handle_public_api' ), 6 );

		if ( class_exists( 'WooCommerce' ) ) {
			add_filter( 'woocommerce_payment_gateways', array( $this, 'register_gateway' ) );
			add_filter( 'woocommerce_can_reduce_order_stock', array( $this, 'control_stock_reduction' ), 10, 2 );
			add_action( 'woocommerce_blocks_loaded', array( $this, 'register_blocks_support' ) );
			add_action( 'woocommerce_blocks_payment_method_type_registration', array( $this, 'register_blocks_payment_method_type' ) );
			add_action( 'woocommerce_api_shc4wc_oauth_callback', array( $this, 'handle_oauth_callback' ) );
			add_action( 'woocommerce_api_shc4wc_return', array( $this, 'handle_buyer_return' ) );
			add_action( 'woocommerce_api_shc4wc_webhook', array( 'SHC4WC_Webhook', 'handle' ) );
		}
	}

	/**
	 * Public callback URLs (?wc-api=…) when WooCommerce is not installed.
	 */
	public function maybe_handle_public_api() {
		if ( class_exists( 'WooCommerce' ) ) {
			return;
		}

		$api = isset( $_GET['wc-api'] ) ? sanitize_text_field( wp_unslash( $_GET['wc-api'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( '' === $api ) {
			return;
		}

		switch ( $api ) {
			case 'shc4wc_oauth_callback':
				$this->handle_oauth_callback();
				break;
			case 'shc4wc_return':
				$this->handle_buyer_return();
				break;
			case 'shc4wc_webhook':
				SHC4WC_Webhook::handle();
				break;
		}
	}

	/**
	 * Buyer return, webhook, and OAuth callback URL (works with or without WooCommerce).
	 *
	 * @param string $route shc4wc_return|shc4wc_webhook|shc4wc_oauth_callback.
	 * @return string
	 */
	public static function public_api_url( $route ) {
		return add_query_arg( 'wc-api', sanitize_key( $route ), home_url( '/' ) );
	}

	public function register_gateway( $methods ) {
		$methods[] = 'SHC4WC_Gateway';
		return $methods;
	}

	public function control_stock_reduction( $can_reduce, $order ) {
		if ( ! $order instanceof WC_Order ) {
			return $can_reduce;
		}

		if ( 'shc4wc' !== $order->get_payment_method() ) {
			return $can_reduce;
		}

		// Keep stock unchanged until Square confirms payment success.
		if ( $order->has_status( array( 'processing', 'completed' ) ) || $order->is_paid() ) {
			return $can_reduce;
		}

		return false;
	}

	public function register_blocks_support() {
		if ( ! class_exists( 'Automattic\\WooCommerce\\Blocks\\Payments\\Integrations\\AbstractPaymentMethodType' ) ) {
			return;
		}
	}

	public function register_blocks_payment_method_type( $payment_method_registry ) {
		if ( ! class_exists( 'Automattic\\WooCommerce\\Blocks\\Payments\\Integrations\\AbstractPaymentMethodType' ) ) {
			return;
		}

		require_once SHC4WC_PLUGIN_DIR . 'includes/class-shc4wc-blocks-support.php';
		$payment_method_registry->register( new SHC4WC_Blocks_Support() );
	}

	public static function get_settings() {
		return get_option( 'woocommerce_shc4wc_settings', array() );
	}

	/**
	 * Worker site key (dedicated option — works without WooCommerce).
	 *
	 * @return string
	 */
	public static function get_oauth_site_key() {
		$key = get_option( self::OAUTH_SITE_KEY_OPTION, '' );
		if ( '' !== trim( (string) $key ) ) {
			return trim( (string) $key );
		}

		$settings = self::get_settings();
		if ( isset( $settings['oauth_site_key'] ) && '' !== trim( (string) $settings['oauth_site_key'] ) ) {
			return trim( (string) $settings['oauth_site_key'] );
		}

		return '';
	}

	/**
	 * Site URL used when the Worker site key was issued.
	 *
	 * @return string
	 */
	public static function get_oauth_registered_site_url() {
		$url = get_option( self::OAUTH_REGISTERED_URL_OPTION, '' );
		if ( '' !== trim( (string) $url ) ) {
			return trim( (string) $url );
		}

		$settings = self::get_settings();
		return isset( $settings['oauth_registered_site_url'] ) ? trim( (string) $settings['oauth_registered_site_url'] ) : '';
	}

	/**
	 * Last connect/register failure details for the admin screen (no WooCommerce logs required).
	 *
	 * @return array<string, mixed>
	 */
	public static function get_admin_diagnostics() {
		$data = get_option( self::ADMIN_DIAGNOSTICS_OPTION, array() );
		return is_array( $data ) ? $data : array();
	}

	/**
	 * @param string               $context Short label for the failure path.
	 * @param array<string, mixed> $extra   Optional fields.
	 */
	private function store_admin_diagnostics( $context, $extra = array() ) {
		$payload = array_merge(
			array(
				'context'      => sanitize_key( (string) $context ),
				'time'         => gmdate( 'c' ),
				'site_url'     => home_url( '/' ),
				'worker_base'  => self::get_oauth_worker_base(),
				'site_key_set' => '' !== self::get_oauth_site_key(),
			),
			is_array( $extra ) ? $extra : array(),
			$this->worker_register_diagnostics
		);
		update_option( self::ADMIN_DIAGNOSTICS_OPTION, $payload, false );
	}

	/**
	 * Optional host file: shc4wc-host-config.php in plugin root (return array).
	 *
	 * @return array<string, string>
	 */
	public static function get_host_worker_config() {
		static $cached = null;
		if ( null !== $cached ) {
			return $cached;
		}
		$file = SHC4WC_PLUGIN_DIR . 'shc4wc-host-config.php';
		if ( is_readable( $file ) ) {
			$data   = include $file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
			$cached = is_array( $data ) ? $data : array();
		} else {
			$cached = array();
		}
		return $cached;
	}

	/**
	 * Worker OAuth endpoint is preset in plugin folder or wp-config. Hides technical fields; merchant Connect UI.
	 */
	public static function is_merchant_connect_ui() {
		$base = SHC4WC_OAUTH_WORKER_BASE;
		if ( is_string( $base ) && '' !== $base ) {
			return true;
		}
		$host = self::get_host_worker_config();
		return ! empty( $host['oauth_worker_base'] );
	}

	/**
	 * Registration secret comes from wp-config or host file (not WooCommerce form).
	 */
	public static function is_host_registration_secret_preset() {
		$const = SHC4WC_OAUTH_WORKER_REG_SECRET;
		if ( is_string( $const ) && '' !== $const ) {
			return true;
		}
		$host = self::get_host_worker_config();
		return ! empty( $host['oauth_worker_registration_secret'] );
	}

	/**
	 * Whether OAuth uses the Cloudflare Worker relay (no local client secret for token exchange).
	 */
	public static function uses_worker_oauth() {
		if ( self::is_merchant_connect_ui() ) {
			return true;
		}
		if ( '' !== self::get_oauth_worker_base() ) {
			return true;
		}
		if ( '' !== self::get_oauth_site_key() ) {
			return true;
		}
		$settings = self::get_settings();
		if ( ! empty( $settings['oauth_via_worker'] ) && 'yes' === $settings['oauth_via_worker'] ) {
			return true;
		}
		$creds = self::get_credentials();
		if ( empty( $creds['client_id'] ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Worker HTTPS origin without trailing slash.
	 */
	public static function get_oauth_worker_base() {
		$const = SHC4WC_OAUTH_WORKER_BASE;
		if ( is_string( $const ) && '' !== $const ) {
			return rtrim( esc_url_raw( trim( $const ) ), '/' );
		}
		$host = self::get_host_worker_config();
		if ( ! empty( $host['oauth_worker_base'] ) ) {
			return rtrim( esc_url_raw( trim( (string) $host['oauth_worker_base'] ) ), '/' );
		}
		$settings = self::get_settings();
		$opt      = isset( $settings['oauth_worker_base'] ) ? (string) $settings['oauth_worker_base'] : '';
		return '' !== $opt ? rtrim( esc_url_raw( trim( $opt ) ), '/' ) : '';
	}

	/**
	 * Optional registration secret header value.
	 */
	public static function get_oauth_worker_registration_secret() {
		$const = SHC4WC_OAUTH_WORKER_REG_SECRET;
		if ( is_string( $const ) && '' !== $const ) {
			return (string) $const;
		}
		$host = self::get_host_worker_config();
		if ( ! empty( $host['oauth_worker_registration_secret'] ) ) {
			return (string) $host['oauth_worker_registration_secret'];
		}
		$settings = self::get_settings();
		return isset( $settings['oauth_worker_reg_secret'] ) ? (string) $settings['oauth_worker_reg_secret'] : '';
	}

	public static function get_credentials() {
		$settings         = self::get_settings();
		$credentials_file = SHC4WC_PLUGIN_DIR . 'credentials.json';
		$creds            = array();

		if ( $credentials_file && file_exists( $credentials_file ) ) {
			$content = file_get_contents( $credentials_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			$data    = json_decode( $content, true );
			if ( is_array( $data ) ) {
				$mode = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'live' : 'sandbox';
				if ( isset( $data[ $mode ] ) && is_array( $data[ $mode ] ) ) {
					$creds = $data[ $mode ];
				}
			}
		}

		return array(
			'client_id'      => isset( $creds['client_id'] ) ? sanitize_text_field( $creds['client_id'] ) : '',
			'client_secret'  => isset( $creds['application_secret'] ) ? sanitize_text_field( $creds['application_secret'] ) : '',
			'webhook_secret' => isset( $creds['webhook_signature_key'] ) ? sanitize_text_field( $creds['webhook_signature_key'] ) : '',
		);
	}

	public static function get_oauth_data() {
		$data = get_option( 'shc4wc_oauth_data', array() );
		return is_array( $data ) ? $data : array();
	}

	public static function get_locations() {
		$settings  = self::get_settings();
		$mode      = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'live' : 'sandbox';
		$all_saved = get_option( 'shc4wc_square_locations', array() );
		if ( ! is_array( $all_saved ) ) {
			return array();
		}
		$locations = isset( $all_saved[ $mode ] ) && is_array( $all_saved[ $mode ] ) ? $all_saved[ $mode ] : array();
		return $locations;
	}

	public static function is_connected() {
		if ( ! self::has_oauth_connection() ) {
			return false;
		}

		$settings    = self::get_settings();
		$location_id = isset( $settings['location_id'] ) ? trim( (string) $settings['location_id'] ) : '';
		$locations   = self::get_locations();

		if ( empty( $location_id ) || empty( $locations ) ) {
			return false;
		}

		foreach ( $locations as $loc ) {
			if ( ! empty( $loc['id'] ) && $location_id === $loc['id'] ) {
				return true;
			}
		}

		return false;
	}

	public static function has_oauth_connection() {
		$oauth     = self::get_oauth_data();
		$locations = self::get_locations();

		if ( empty( $oauth['access_token'] ) || empty( $locations ) ) {
			return false;
		}
		return true;
	}

	/**
	 * POST /register on the OAuth Worker. Sets worker_register_diagnostics on failure.
	 *
	 * @return string Non-empty site_key or empty string on failure.
	 */
	private function request_oauth_worker_registration_raw() {
		$this->worker_register_diagnostics = array(
			'http_code'       => 0,
			'worker_error'    => '',
			'transport_error' => '',
			'raw_snippet'     => '',
		);

		$base   = self::get_oauth_worker_base();
		$secret = self::get_oauth_worker_registration_secret();
		if ( empty( $base ) || ! preg_match( '#^https://#i', $base ) ) {
			$this->worker_register_diagnostics['worker_error'] = 'invalid_worker_base_local';
			$this->log_worker_register_failure();
			return array( 'site_key' => '' );
		}

		$site_url = $this->normalize_oauth_site_base_url( home_url( '/' ) );
		if ( ! is_string( $site_url ) || ! preg_match( '#^https?://#i', $site_url ) ) {
			$this->worker_register_diagnostics['worker_error'] = 'invalid_home_url_local';
			$this->log_worker_register_failure();
			return array( 'site_key' => '' );
		}

		$headers = array(
			'Content-Type' => 'application/json; charset=utf-8',
		);
		if ( '' !== trim( (string) $secret ) ) {
			$headers['X-SHC4WC-Registration-Secret'] = $secret;
		}

		$response = wp_remote_post(
			$base . '/register',
			array(
				'headers'   => $headers,
				'body'      => wp_json_encode(
					array(
						'site_url' => $site_url,
					)
				),
				'timeout'   => 45,
				'sslverify' => true,
			)
		);

		if ( is_wp_error( $response ) ) {
			$this->worker_register_diagnostics['transport_error'] = $response->get_error_message();
			$this->log_worker_register_failure();
			return array( 'site_key' => '' );
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		$raw  = wp_remote_retrieve_body( $response );
		$this->worker_register_diagnostics['http_code']   = $code;
		$this->worker_register_diagnostics['raw_snippet'] = strlen( $raw ) > 400 ? substr( $raw, 0, 400 ) : $raw;

		$body = json_decode( $raw, true );
		if ( is_array( $body ) && ! empty( $body['error'] ) ) {
			$this->worker_register_diagnostics['worker_error'] = sanitize_text_field( (string) $body['error'] );
		}

		if ( $code >= 200 && $code < 300 && is_array( $body ) && ! empty( $body['site_key'] ) ) {
			return array( 'site_key' => sanitize_text_field( $body['site_key'] ) );
		}

		$this->log_worker_register_failure();
		return array( 'site_key' => '' );
	}

	private function log_worker_register_failure() {
		$this->store_admin_diagnostics( 'worker_register' );
		$line = 'SHC4WC Worker /register failed: ' . wp_json_encode( $this->worker_register_diagnostics );
		if ( function_exists( 'wc_get_logger' ) ) {
			wc_get_logger()->log( 'error', $line, array( 'source' => 'shc4wc' ) );
			return;
		}
		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		error_log( '[shc4wc] ' . $line );
	}

	/**
	 * @param string               $context Failure path.
	 * @param array<string, mixed> $extra   Optional fields.
	 */
	private function redirect_oauth_worker_misconfigured( $context, $extra = array() ) {
		$this->store_admin_diagnostics( $context, $extra );
		wp_safe_redirect( $this->settings_url( 'oauth_worker_misconfigured' ) );
		exit;
	}

	private function redirect_after_worker_register_failure() {
		$e = isset( $this->worker_register_diagnostics['worker_error'] ) ? $this->worker_register_diagnostics['worker_error'] : '';
		$h = isset( $this->worker_register_diagnostics['http_code'] ) ? (int) $this->worker_register_diagnostics['http_code'] : 0;

		if ( 403 === $h && 'registration_forbidden' === $e ) {
			wp_safe_redirect( $this->settings_url( 'worker_register_secret_required' ) );
			exit;
		}
		if ( 'invalid_site_url' === $e ) {
			wp_safe_redirect( $this->settings_url( 'worker_register_bad_home_url' ) );
			exit;
		}
		if ( 'invalid_home_url_local' === $e ) {
			wp_safe_redirect( $this->settings_url( 'worker_register_bad_home_url' ) );
			exit;
		}
		if ( ! empty( $this->worker_register_diagnostics['transport_error'] ) ) {
			wp_safe_redirect( $this->settings_url( 'worker_register_unreachable' ) );
			exit;
		}
		wp_safe_redirect( $this->settings_url( 'worker_register_failed' ) );
		exit;
	}

	/**
	 * POST /register on the OAuth Worker. Returns site_key string or null.
	 */
	private function request_oauth_worker_site_key() {
		$r = $this->request_oauth_worker_registration_raw();
		return '' !== $r['site_key'] ? $r['site_key'] : null;
	}

	private function persist_oauth_site_key( $site_key ) {
		$registered = $this->normalize_oauth_site_base_url( home_url( '/' ) );

		update_option( self::OAUTH_SITE_KEY_OPTION, $site_key, false );
		update_option( self::OAUTH_REGISTERED_URL_OPTION, $registered, false );
		wp_cache_delete( self::OAUTH_SITE_KEY_OPTION, 'options' );
		wp_cache_delete( self::OAUTH_REGISTERED_URL_OPTION, 'options' );

		$settings                              = self::get_settings();
		$settings['oauth_site_key']            = $site_key;
		$settings['oauth_registered_site_url'] = $registered;
		update_option( 'woocommerce_shc4wc_settings', $settings, false );
		wp_cache_delete( 'woocommerce_shc4wc_settings', 'options' );
	}

	/**
	 * Canonical site base URL for Worker registration and return URL checks.
	 *
	 * @param string $url Site URL.
	 * @return string
	 */
	private function normalize_oauth_site_base_url( $url ) {
		$url  = trailingslashit( (string) $url );
		$host = wp_parse_url( $url, PHP_URL_HOST );
		if ( $host && ! in_array( $host, array( 'localhost', '127.0.0.1' ), true ) ) {
			$url = set_url_scheme( $url, 'https' );
		} elseif ( is_ssl() ) {
			$url = set_url_scheme( $url, 'https' );
		}
		return $url;
	}

	/**
	 * Front-end return URL after Square OAuth (Worker requires HTTPS except local dev).
	 *
	 * @param string $return_state OAuth return state token.
	 * @return string
	 */
	private function get_oauth_return_url( $return_state ) {
		$url = add_query_arg(
			'shc4wc_oauth_state',
			$return_state,
			home_url( '/' )
		);
		return $this->normalize_oauth_site_base_url( $url );
	}

	/**
	 * Register or refresh Worker site_key when the public site URL changes.
	 *
	 * @return bool
	 */
	private function refresh_worker_site_registration() {
		if ( '' === self::get_oauth_worker_base() ) {
			return false;
		}

		$current_home = $this->normalize_oauth_site_base_url( home_url( '/' ) );
		$stored_home  = self::get_oauth_registered_site_url();
		$stored_home  = '' !== $stored_home ? $this->normalize_oauth_site_base_url( $stored_home ) : '';
		$site_key     = self::get_oauth_site_key();

		if ( '' !== $site_key && $stored_home === $current_home ) {
			return true;
		}

		$key = $this->request_oauth_worker_site_key();
		if ( ! $key ) {
			return false;
		}

		$this->persist_oauth_site_key( $key );
		return true;
	}

	/**
	 * Ensure a Worker site_key exists when a relay base URL is configured (auto-register if missing).
	 *
	 * @return bool True when a non-empty site key is available or registration is not required.
	 */
	private function ensure_merchant_worker_site_registered() {
		return $this->refresh_worker_site_registration();
	}

	public function handle_worker_register() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Not allowed.', 'payments-connect-square' ) );
		}
		check_admin_referer( 'shc4wc_worker_register' );

		$base = self::get_oauth_worker_base();
		if ( empty( $base ) || ! preg_match( '#^https://#i', $base ) ) {
			wp_safe_redirect( $this->settings_url( 'worker_register_invalid_base' ) );
			exit;
		}

		$key = $this->request_oauth_worker_site_key();
		if ( ! $key ) {
			$this->redirect_after_worker_register_failure();
		}

		$this->persist_oauth_site_key( $key );
		wp_safe_redirect( $this->settings_url( 'worker_registered' ) );
		exit;
	}

	/**
	 * Complete OAuth when returning from Cloudflare Worker with shc4wc_session.
	 */
	public function handle_oauth_worker_return() {
		if ( ! isset( $_GET['shc4wc_session'] ) ) {
			return;
		}

		if ( ! self::uses_worker_oauth() ) {
			return;
		}

		$session_id = sanitize_text_field( wp_unslash( $_GET['shc4wc_session'] ) );
		if ( ! SHC4WC_OAuth_Return_State::is_valid_session_id( $session_id ) ) {
			wp_safe_redirect( $this->settings_url( 'oauth_claim_failed' ) );
			exit;
		}

		$return_verified = false;
		$has_state_param = isset( $_GET['shc4wc_oauth_state'] ) && '' !== trim( (string) wp_unslash( $_GET['shc4wc_oauth_state'] ) );

		if ( $has_state_param ) {
			$return_state    = SHC4WC_OAuth_Return_State::sanitize( wp_unslash( $_GET['shc4wc_oauth_state'] ) );
			$return_verified = SHC4WC_OAuth_Return_State::verify( $return_state );
		} elseif ( isset( $_GET['shc4wc_return_nonce'] ) ) {
			$return_nonce    = sanitize_text_field( wp_unslash( $_GET['shc4wc_return_nonce'] ) );
			$return_verified = (bool) wp_verify_nonce( $return_nonce, 'shc4wc_oauth_worker_return' );
		} else {
			// Worker/Square redirect may drop custom query args; session_id + /claim is enough.
			$return_verified = true;
		}

		if ( ! $return_verified ) {
			wp_safe_redirect( $this->settings_url( 'oauth_state_error' ) );
			exit;
		}

		$base     = self::get_oauth_worker_base();
		$site_key = self::get_oauth_site_key();

		if ( empty( $site_key ) && '' !== self::get_oauth_worker_base() ) {
			if ( ! $this->ensure_merchant_worker_site_registered() ) {
				$this->redirect_after_worker_register_failure();
			}
			$site_key = self::get_oauth_site_key();
		}

		if ( empty( $base ) || empty( $site_key ) ) {
			$this->redirect_oauth_worker_misconfigured(
				'oauth_worker_return',
				array(
					'worker_base_set' => '' !== $base,
					'site_key_set'    => '' !== $site_key,
				)
			);
		}

		// Return state already verified; claim does not require an active admin cookie after Square redirect.

		$response = wp_remote_post(
			$base . '/claim',
			array(
				'headers' => array(
					'Content-Type' => 'application/json; charset=utf-8',
				),
				'body'    => wp_json_encode(
					array(
						'site_key'   => $site_key,
						'session_id' => $session_id,
						'site_url'   => $this->normalize_oauth_site_base_url( home_url( '/' ) ),
					)
				),
				'timeout' => 60,
			)
		);

		if ( is_wp_error( $response ) ) {
			wp_safe_redirect( $this->settings_url( 'oauth_claim_failed' ) );
			exit;
		}

		$code = (int) wp_remote_retrieve_response_code( $response );
		$data = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( $code < 200 || $code >= 300 || ! is_array( $data ) || empty( $data['access_token'] ) ) {
			wp_safe_redirect( $this->settings_url( 'oauth_claim_failed' ) );
			exit;
		}

		$settings = self::get_settings();
		$redirect = $this->persist_oauth_from_square_token( $data, $settings );
		wp_safe_redirect( $redirect );
		exit;
	}

	public function handle_oauth_start() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Not allowed.', 'payments-connect-square' ) );
		}

		check_admin_referer( 'shc4wc_oauth_start' );

		$settings = self::get_settings();
		$creds    = self::get_credentials();

		if ( self::uses_worker_oauth() ) {
			if ( ! $this->refresh_worker_site_registration() ) {
				$this->redirect_after_worker_register_failure();
			}

			$settings = self::get_settings();
			$base     = self::get_oauth_worker_base();
			$site_key = self::get_oauth_site_key();

			if ( empty( $base ) || ! preg_match( '#^https://#i', $base ) || empty( $site_key ) ) {
				$this->redirect_oauth_worker_misconfigured(
					'oauth_start',
					array(
						'worker_base'     => $base,
						'worker_base_ok'  => ( ! empty( $base ) && preg_match( '#^https://#i', $base ) ),
						'site_key_length' => strlen( $site_key ),
					)
				);
			}

			$return_state = SHC4WC_OAuth_Return_State::create();
			$return_url   = $this->get_oauth_return_url( $return_state );
			$env        = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'live' : 'sandbox';
			$ts         = time();
			$canonical  = $return_url . "\n" . $env . "\n" . $ts;
			$sig        = hash_hmac( 'sha256', $canonical, $site_key );

			$start = $base . '/start?' . rawurlencode( 'return_url' ) . '=' . rawurlencode( $return_url )
				. '&' . rawurlencode( 'site_key' ) . '=' . rawurlencode( $site_key )
				. '&' . rawurlencode( 'env' ) . '=' . rawurlencode( $env )
				. '&' . rawurlencode( 'ts' ) . '=' . rawurlencode( (string) $ts )
				. '&' . rawurlencode( 'sig' ) . '=' . rawurlencode( $sig );

			wp_redirect( $start ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- external worker.
			exit;
		}

		if ( empty( $creds['client_id'] ) ) {
			wp_safe_redirect( $this->settings_url( 'missing_client_id' ) );
			exit;
		}

		$base  = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'https://connect.squareup.com' : 'https://connect.squareupsandbox.com';
		$state = wp_generate_password( 20, false, false );
		update_option( 'shc4wc_oauth_state', $state, false );

		$scope = implode(
			' ',
			array(
				'MERCHANT_PROFILE_READ',
				'MERCHANT_PROFILE_WRITE',
				'PAYMENT_METHODS_READ',
				'ORDERS_READ',
				'ORDERS_WRITE',
				'PAYMENTS_READ',
				'PAYMENTS_WRITE',
			)
		);

		$url = add_query_arg(
			array(
				'client_id'    => $creds['client_id'],
				'scope'        => $scope,
				'state'        => $state,
				'session'      => 'false',
				'redirect_uri' => $this->oauth_redirect_uri(),
			),
			$base . '/oauth2/authorize'
		);

		wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- Square.
		exit;
	}

	public function handle_oauth_callback() {
		if ( self::uses_worker_oauth() ) {
			$this->redirect_oauth_worker_misconfigured( 'legacy_oauth_callback' );
		}

		// Public Square OAuth return URL (wc-api); not wp-admin — use OAuth `state` + hash_equals instead of WP nonces.
		// phpcs:disable WordPress.Security.NonceVerification
		$code  = isset( $_GET['code'] ) ? sanitize_text_field( wp_unslash( $_GET['code'] ) ) : '';
		$state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : '';
		$error = isset( $_GET['error'] ) ? sanitize_text_field( wp_unslash( $_GET['error'] ) ) : '';
		// phpcs:enable WordPress.Security.NonceVerification
		$state_saved = get_option( 'shc4wc_oauth_state', '' );

		if ( $error ) {
			wp_safe_redirect( $this->settings_url( 'oauth_denied' ) );
			exit;
		}

		if ( ! $code || ! $state || ! hash_equals( $state_saved, $state ) ) {
			wp_safe_redirect( $this->settings_url( 'oauth_state_error' ) );
			exit;
		}

		$settings = self::get_settings();
		$creds    = self::get_credentials();
		$api_base = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'https://connect.squareup.com' : 'https://connect.squareupsandbox.com';

		$payload = array(
			'client_id'     => $creds['client_id'],
			'client_secret' => $creds['client_secret'],
			'code'          => $code,
			'grant_type'    => 'authorization_code',
			'redirect_uri'  => $this->oauth_redirect_uri(),
		);

		$response = wp_remote_post(
			$api_base . '/oauth2/token',
			array(
				'headers' => array(
					'Content-Type' => 'application/json',
				),
				'body'    => wp_json_encode( $payload ),
				'timeout' => 60,
			)
		);

		if ( is_wp_error( $response ) ) {
			wp_safe_redirect( $this->settings_url( 'oauth_request_failed' ) );
			exit;
		}

		$code_http = (int) wp_remote_retrieve_response_code( $response );
		$data      = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( $code_http < 200 || $code_http >= 300 || ! is_array( $data ) || empty( $data['access_token'] ) ) {
			wp_safe_redirect( $this->settings_url( 'oauth_exchange_failed' ) );
			exit;
		}

		delete_option( 'shc4wc_oauth_state' );
		$redirect = $this->persist_oauth_from_square_token( $data, $settings );
		wp_safe_redirect( $redirect );
		exit;
	}

	/**
	 * Save token + refresh locations. Returns admin redirect URL (success or failure).
	 *
	 * @param array $data    Square token response.
	 * @param array $settings Gateway settings option.
	 */
	private function persist_oauth_from_square_token( array $data, array $settings ) {
		$api                = new SHC4WC_API( $settings );
		$locations_response = $api->list_locations( sanitize_text_field( $data['access_token'] ) );
		if ( is_wp_error( $locations_response ) || empty( $locations_response['locations'] ) || ! is_array( $locations_response['locations'] ) ) {
			return $this->settings_url( 'oauth_locations_failed' );
		}

		$locations = array();
		foreach ( $locations_response['locations'] as $location ) {
			if ( empty( $location['id'] ) ) {
				continue;
			}
			$locations[] = array(
				'id'       => sanitize_text_field( $location['id'] ),
				'name'     => isset( $location['name'] ) ? sanitize_text_field( $location['name'] ) : '',
				'status'   => isset( $location['status'] ) ? sanitize_text_field( $location['status'] ) : '',
				'country'  => isset( $location['country'] ) ? sanitize_text_field( $location['country'] ) : '',
				'currency' => isset( $location['currency'] ) ? sanitize_text_field( $location['currency'] ) : '',
			);
		}

		$mode          = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'live' : 'sandbox';
		$all_locations = get_option( 'shc4wc_square_locations', array() );
		if ( ! is_array( $all_locations ) ) {
			$all_locations = array();
		}
		$all_locations[ $mode ] = $locations;
		update_option( 'shc4wc_square_locations', $all_locations, false );

		update_option(
			'shc4wc_oauth_data',
			array(
				'access_token'             => sanitize_text_field( $data['access_token'] ),
				'refresh_token'            => isset( $data['refresh_token'] ) ? sanitize_text_field( $data['refresh_token'] ) : '',
				'expires_at'               => isset( $data['expires_at'] ) ? sanitize_text_field( $data['expires_at'] ) : '',
				'merchant_id'              => isset( $data['merchant_id'] ) ? sanitize_text_field( $data['merchant_id'] ) : '',
				'refresh_token_expires_at' => isset( $data['refresh_token_expires_at'] ) ? sanitize_text_field( $data['refresh_token_expires_at'] ) : '',
			),
			false
		);

		return $this->settings_url( 'connected' );
	}

	public function handle_oauth_revoke() {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'Not allowed.', 'payments-connect-square' ) );
		}
		check_admin_referer( 'shc4wc_oauth_revoke' );

		$settings = self::get_settings();
		$creds    = self::get_credentials();
		$oauth    = self::get_oauth_data();

		if ( self::uses_worker_oauth() ) {
			$base     = self::get_oauth_worker_base();
			$site_key = self::get_oauth_site_key();
			$env      = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'live' : 'sandbox';
			$token    = isset( $oauth['access_token'] ) ? (string) $oauth['access_token'] : '';

			if ( $base && $site_key && $token ) {
				$ts  = time();
				$sig = hash_hmac( 'sha256', $token . "\n" . $env . "\n" . $ts, $site_key );
				wp_remote_post(
					$base . '/revoke',
					array(
						'headers' => array(
							'Content-Type' => 'application/json; charset=utf-8',
						),
						'body'    => wp_json_encode(
							array(
								'site_key'     => $site_key,
								'access_token' => $token,
								'env'          => $env,
								'ts'           => $ts,
								'sig'          => $sig,
							)
						),
						'timeout' => 45,
					)
				);
			}
		} elseif ( ! empty( $oauth['access_token'] ) && ! empty( $creds['client_secret'] ) ) {
			$api_base = ( isset( $settings['environment'] ) && 'live' === $settings['environment'] ) ? 'https://connect.squareup.com' : 'https://connect.squareupsandbox.com';
			wp_remote_post(
				$api_base . '/oauth2/revoke',
				array(
					'headers' => array(
						'Authorization' => 'Client ' . $creds['client_secret'],
						'Content-Type'  => 'application/json',
					),
					'body'    => wp_json_encode(
						array(
							'client_id'    => $creds['client_id'],
							'access_token' => $oauth['access_token'],
						)
					),
					'timeout' => 45,
				)
			);
		}

		delete_option( 'shc4wc_oauth_data' );
		delete_option( 'shc4wc_square_locations' );
		wp_safe_redirect( $this->settings_url( 'disconnected' ) );
		exit;
	}

	public function handle_buyer_return() {
		$type = (string) filter_input( INPUT_GET, 'type', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		if ( 'cf7' === $type ) {
			$ref = (string) filter_input( INPUT_GET, 'ref', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
			$key = (string) filter_input( INPUT_GET, 'key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
			if ( class_exists( 'SHC4WC_CF7' ) ) {
				SHC4WC_CF7::handle_return( $ref, $key );
			}
			wp_safe_redirect( home_url( '/' ) );
			exit;
		}

		if ( ! class_exists( 'WooCommerce' ) || ! function_exists( 'wc_get_order' ) ) {
			wp_safe_redirect( home_url( '/' ) );
			exit;
		}

		$order_id      = absint( (string) filter_input( INPUT_GET, 'order_id', FILTER_SANITIZE_NUMBER_INT ) );
		$order_key_raw = (string) filter_input( INPUT_GET, 'order_key', FILTER_SANITIZE_FULL_SPECIAL_CHARS );
		$order_key     = wc_clean( wp_unslash( $order_key_raw ) );
		$order         = $order_id ? wc_get_order( $order_id ) : false;

		if ( ! $order || $order->get_order_key() !== $order_key ) {
			wp_safe_redirect( wc_get_checkout_url() );
			exit;
		}

		$stored_payment_id = (string) $order->get_meta( '_shc4wc_square_payment_id' );
		$square_payment_id = $stored_payment_id;
		if ( '' === $square_payment_id ) {
			$square_payment_id = $this->retrieve_square_payment_id_from_remote_order( $order );
		}
		if ( '' !== $square_payment_id && $square_payment_id !== $stored_payment_id ) {
			$order->update_meta_data( '_shc4wc_square_payment_id', $square_payment_id );
			if ( ! $order->is_paid() ) {
				$order->payment_complete( $square_payment_id );
			}
			$order->save();
		}
		if ( '' !== $square_payment_id && $square_payment_id !== $stored_payment_id ) {
			$order->add_order_note(
				sprintf(
					/* translators: %s: Square Payment API id (transaction / tender id). */
					__( 'Customer returned from Square Hosted Checkout. Square payment (transaction) ID: %s', 'payments-connect-square' ),
					$square_payment_id
				)
			);
		} elseif ( '' === $square_payment_id ) {
			$diag_order_id = (string) $order->get_meta( '_shc4wc_square_order_id' );
			$diag_link_id  = (string) $order->get_meta( '_shc4wc_payment_link_id' );
			$diag_location = (string) $order->get_meta( '_shc4wc_square_location_id' );
			$detail        = '' === (string) $order->get_meta( '_shc4wc_square_order_id' )
				? __( 'Square order ID is missing on this order (gateway did not store it). Update the plugin and run a new checkout test.', 'payments-connect-square' )
				: __( 'Square payment ID could not be resolved (order tenders still empty and List Payments had no matching payment yet). Enable gateway debug logging, wait a few seconds and reload the order, or use Square webhooks.', 'payments-connect-square' );
			$order->add_order_note(
				__( 'Customer returned from Square Hosted Checkout.', 'payments-connect-square' ) . ' ' . $detail
			);
			$order->add_order_note(
				sprintf(
					/* translators: 1: square order id, 2: payment link id, 3: location id. */
					__( 'Square return diagnostics: order_id=%1$s, payment_link_id=%2$s, location_id=%3$s', 'payments-connect-square' ),
					'' !== $diag_order_id ? $diag_order_id : '(empty)',
					'' !== $diag_link_id ? $diag_link_id : '(empty)',
					'' !== $diag_location ? $diag_location : '(empty)'
				)
			);
		}
		wp_safe_redirect( $order->get_checkout_order_received_url() );
		exit;
	}

	/**
	 * Read Square Payment id from Order tenders via Orders API (works without webhooks; may need a moment after checkout).
	 *
	 * @param WC_Order $order WooCommerce order.
	 * @return string Payment id or empty.
	 */
	private function retrieve_square_payment_id_from_remote_order( WC_Order $order ) {
		$square_order_id = (string) $order->get_meta( '_shc4wc_square_order_id' );
		if ( '' === $square_order_id ) {
			return '';
		}
		$settings    = self::get_settings();
		$location_id = (string) $order->get_meta( '_shc4wc_square_location_id' );
		if ( '' === $location_id && ! empty( $settings['location_id'] ) ) {
			$location_id = trim( (string) $settings['location_id'] );
		}

		$api = new SHC4WC_API( $settings );

		// 0) Retrieve Payment Link — embedded orders are sometimes fresher than GET /v2/orders right after redirect.
		$payment_link_id = (string) $order->get_meta( '_shc4wc_payment_link_id' );
		if ( '' !== $payment_link_id ) {
			for ( $plink_attempt = 0; $plink_attempt < 5; $plink_attempt++ ) {
				if ( $plink_attempt > 0 ) {
					usleep( 500000 );
				}
				$plink = $api->retrieve_payment_link( $payment_link_id );
				if ( is_wp_error( $plink ) ) {
					$api->log(
						'Retrieve Square payment link failed (buyer return)',
						array(
							'payment_link_id' => $payment_link_id,
							'error'           => $plink->get_error_message(),
						),
						'warning'
					);
					continue;
				}
				$rel = isset( $plink['related_resources'] ) && is_array( $plink['related_resources'] ) ? $plink['related_resources'] : array();
				$ros = isset( $rel['orders'] ) && is_array( $rel['orders'] ) ? $rel['orders'] : array();
				foreach ( $ros as $ro ) {
					$from_tender = $this->extract_square_payment_id_from_order_payload( $ro );
					if ( '' !== $from_tender ) {
						return $from_tender;
					}
				}
			}
		}

		// 1) Retrieve Order → tenders (sometimes empty right after hosted checkout).
		for ( $attempt = 0; $attempt < 5; $attempt++ ) {
			if ( $attempt > 0 ) {
				usleep( 500000 );
			}
			$data = $api->retrieve_order( $square_order_id );
			if ( is_wp_error( $data ) ) {
				$api->log(
					'Retrieve Square order failed (buyer return)',
					array(
						'square_order_id' => $square_order_id,
						'error'           => $data->get_error_message(),
					),
					'warning'
				);
				continue;
			}
			$sq_order    = isset( $data['order'] ) && is_array( $data['order'] ) ? $data['order'] : array();
			$from_tender = $this->extract_square_payment_id_from_order_payload( $sq_order );
			if ( '' !== $from_tender ) {
				return $from_tender;
			}
		}

		// 2) GET /v2/payments (paginated). Connect API has no POST /v2/payments/search — match payments[].order_id client-side.
		if ( '' !== $location_id ) {
			$created  = $order->get_date_created();
			$ts       = $created ? $created->getTimestamp() : time();
			$begin_ts = max( 0, $ts - 604800 );
			$begin    = gmdate( 'Y-m-d\TH:i:s\Z', $begin_ts );

			for ( $round = 0; $round < 6; $round++ ) {
				if ( $round > 0 ) {
					usleep( 500000 );
				}
				$cursor = '';
				for ( $page = 0; $page < 10; $page++ ) {
					$list = $api->list_recent_payments( $location_id, $begin, $cursor );
					if ( is_wp_error( $list ) ) {
						$api->log(
							'Square list payments failed (buyer return)',
							array(
								'square_order_id' => $square_order_id,
								'error'           => $list->get_error_message(),
								'page'            => $page,
							),
							'warning'
						);
						break;
					}
					$payments = ! empty( $list['payments'] ) && is_array( $list['payments'] ) ? $list['payments'] : array();
					$found    = $this->find_square_payment_id_in_payment_objects( $payments, $square_order_id );
					if ( '' !== $found ) {
						return $found;
					}
					$cursor = isset( $list['cursor'] ) ? (string) $list['cursor'] : '';
					if ( '' === $cursor ) {
						break;
					}
				}
			}
		}

		return '';
	}

	/**
	 * First card/cash tender payment_id on a Square Order payload (snake_case JSON).
	 *
	 * @param mixed $sq_order Order array from Square.
	 * @return string Payment id or empty.
	 */
	private function extract_square_payment_id_from_order_payload( $sq_order ) {
		if ( ! is_array( $sq_order ) ) {
			return '';
		}
		$tenders = isset( $sq_order['tenders'] ) && is_array( $sq_order['tenders'] ) ? $sq_order['tenders'] : array();
		foreach ( $tenders as $tender ) {
			if ( ! is_array( $tender ) ) {
				continue;
			}
			if ( ! empty( $tender['payment_id'] ) ) {
				return sanitize_text_field( (string) $tender['payment_id'] );
			}
		}
		return '';
	}

	/**
	 * Pick a Payment id from List Payments rows that reference this Square order id.
	 *
	 * @param array  $payments        Square Payment objects.
	 * @param string $square_order_id Square order id.
	 * @return string Payment id or empty.
	 */
	private function find_square_payment_id_in_payment_objects( array $payments, $square_order_id ) {
		$square_order_id = (string) $square_order_id;
		foreach ( $payments as $p ) {
			if ( ! is_array( $p ) || empty( $p['id'] ) ) {
				continue;
			}
			$oid = isset( $p['order_id'] ) ? (string) $p['order_id'] : '';
			if ( $oid !== $square_order_id ) {
				continue;
			}
			$st = isset( $p['status'] ) ? strtoupper( (string) $p['status'] ) : '';
			if ( in_array( $st, array( 'COMPLETED', 'CAPTURED', 'APPROVED', 'PENDING' ), true ) ) {
				return sanitize_text_field( (string) $p['id'] );
			}
		}
		return '';
	}

	private function oauth_redirect_uri() {
		return self::public_api_url( 'shc4wc_oauth_callback' );
	}

	private function settings_url( $status = '' ) {
		if ( class_exists( 'SHC4WC_Connection_Settings' ) ) {
			return SHC4WC_Connection_Settings::page_url( $status );
		}
		$url = admin_url( 'admin.php?page=payments-square-connect' );
		if ( $status ) {
			$url = add_query_arg( 'shc4wc_status', $status, $url );
		}
		return $url;
	}
}
