<?php

/**
 * PeachPay : ConvesioPay Order Reconciliation.
 *
 * Background polling to reconcile WC order status when payment completed
 * but the order remains Pending (webhook failure, plugin conflicts, etc.).
 *
 * @package PeachPay
 */

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

// Reconciliation cron hook name.
define('PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK', 'peachpay_convesiopay_reconcile_pending_orders');

// Reconciliation interval in seconds (10 minutes).
define('PEACHPAY_CONVESIOPAY_RECONCILE_INTERVAL', 10 * MINUTE_IN_SECONDS);

// Minimum order age before first poll (5 minutes).
define('PEACHPAY_CONVESIOPAY_RECONCILE_MIN_AGE', 5 * MINUTE_IN_SECONDS);

// Max reconciliation attempts per order (12).
define('PEACHPAY_CONVESIOPAY_RECONCILE_MAX_ATTEMPTS', 12);

// Lock TTL in seconds (2 minutes).
define('PEACHPAY_CONVESIOPAY_RECONCILE_LOCK_TTL', 2 * MINUTE_IN_SECONDS);

// PeachPay ConvesioPay Reconciliation class.
class PeachPay_ConvesioPay_Reconciliation
{

	/**
	 * ConvesioPay order statuses that stop polling.
	 * @var array
	 */
	private static $convesiopay_order_statuses = array('Succeeded', 'Failed', 'Blocked', 'Refunded', 'Cancelled', 'Disputed');

	/**
	 * Constructor.
	 */
	public function __construct()
	{
		// Register PeachPay ConvesioPay reconciliation cron schedule.
		add_filter('cron_schedules', array($this, 'register_convesiopay_reconciliation_cron_schedule'));

		// Run PeachPay ConvesioPay reconciliation.
		add_action(PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK, array($this, 'run_reconciliation'));

		// Migrate from old 5min schedule before any cron runs.
		add_action('init', array($this, 'migrate_cron_from_old_schedule'), 5);
		// Schedule PeachPay ConvesioPay reconciliation cron when connected and event not already scheduled.
		add_action('init', array($this, 'schedule_cron_if_not_scheduled'), 20);
	}

	/**
	 * Register PeachPay ConvesioPay reconciliation cron schedule.
	 *
	 * @param array $schedules Existing schedules.
	 * @return array
	 */
	public function register_convesiopay_reconciliation_cron_schedule($schedules)
	{
		// Add a custom cron schedule for every 10 minutes.
		if (! isset($schedules['peachpay_convesiopay_reconciliation_10min'])) {
			$schedules['peachpay_convesiopay_reconciliation_10min'] = array(
				'interval' => PEACHPAY_CONVESIOPAY_RECONCILE_INTERVAL,
				'display'  => __('Every 10 minutes', 'peachpay-for-woocommerce'),
			);
		}
		return $schedules;
	}

	/**
	 * One-time migration: unschedule any events still using the old 5min schedule.
	 */
	public function migrate_cron_from_old_schedule()
	{
		$event = wp_get_scheduled_event(PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK);
		if ($event && $event->schedule === 'peachpay_convesiopay_reconciliation_5min') {
			wp_unschedule_event($event->timestamp, PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK);
		}
	}

	/**
	 * Schedule PeachPay ConvesioPay reconciliation cron when connected and event not already scheduled.
	 */
	public function schedule_cron_if_not_scheduled()
	{
		try {
			// Prevent scheduling cron if not in admin or cron context
			if ( ! is_admin() && ! wp_doing_cron() ) {
				return;
			}

			// Check if PeachPay ConvesioPay is connected.
			if (! class_exists('PeachPay_ConvesioPay_Integration') || ! PeachPay_ConvesioPay_Integration::connected()) {
				return;
			}

			// Prevent scheduling duplicate cron events.
			if (wp_next_scheduled(PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK)) {
				return;
			}

			// Schedule the reconciliation cron event.
			wp_schedule_event(time(), 'peachpay_convesiopay_reconciliation_10min', PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK);
		} catch ( \Throwable $e ) {
			$this->log_reconciliation_error( 'schedule_cron_if_not_scheduled', $e );
		}
	}

	/**
	 * Cron callback: find eligible orders and poll ConvesioPay for each.
	 */
	public function run_reconciliation()
	{
		try {
			// Check if PeachPay ConvesioPay is connected.
			if (! class_exists('PeachPay_ConvesioPay_Integration') || ! PeachPay_ConvesioPay_Integration::connected()) {
				return;
			}

			peachpay_log(
				'info',
				'ConvesioPay Reconciliation cron callback - started.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.cron_callback.started',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'cron_callback',
					'outcome'   => 'started',
					'method'    => __METHOD__,
					'line'      => __LINE__,
				)
			);

			// Get eligible orders for reconciliation
			$orders = $this->get_eligible_orders();

			$order_ids = array( 0 );
			if ( empty( $orders ) ) {
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation cron callback - no eligible orders.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
					)
				);
				return;
			}

			// Fetch payments in batch via List API to reduce API calls
			$payments_map = $this->fetch_payments_list_batch( $orders );

			// Process each order (one failure must not stop the rest)
			foreach ( $orders as $order ) {
				$order_ids[] = $order->get_id();
				try {
					$attempts = (int) $order->get_meta( '_convesiopay_reconcile_attempts', true );

					if ( $attempts >= PEACHPAY_CONVESIOPAY_RECONCILE_MAX_ATTEMPTS ) {
						$order->update_status( 'failed', 'ConvesioPay reconciliation failed after multiple attempts.' );
						$order->save();
						peachpay_log(
							'info',
							'ConvesioPay Reconciliation order processing - max attempts reached.',
							array(
								'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
								'component' => 'payments',
								'feature'   => 'convesiopay_reconciliation',
								'operation' => 'reconciliation',
								'outcome'   => 'success',
								'method'    => __METHOD__,
								'line'      => __LINE__,
								'data'      => array(
									'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' marked failed (max attempts in loop)'
						)));
						continue;
					}

					$this->process_order( $order, $payments_map );
				} catch ( \Throwable $e ) {
					$order_id = is_object( $order ) && method_exists( $order, 'get_id' ) ? $order->get_id() : '?';
					$this->log_reconciliation_error( 'process_order order_id=' . $order_id, $e );
				}
			}

			peachpay_log(
				'info',
				'ConvesioPay Reconciliation cron callback - completed.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation',
					'outcome'   => 'success',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: Cron finished for orders: ' . implode(', ', $order_ids)
			)));
		} catch ( \Throwable $e ) {
			$this->log_reconciliation_error( 'run_reconciliation', $e );
		}
	}

	/**
	 * Get orders eligible for reconciliation (pending, have payment ID, 10+ min old, not exhausted).
	 * All eligibility filters are applied in the DB query to avoid loading and discarding orders in PHP.
	 *
	 * @return WC_Order[]
	 */
	private function get_eligible_orders()
	{
		try {
			// Use Unix timestamp for date_created cutoff (WooCommerce compatibility; avoid gmdate).
			$cutoff = time() - PEACHPAY_CONVESIOPAY_RECONCILE_MIN_AGE;

			$orders = wc_get_orders(
				array(
					'status'         => 'pending',
					'limit'          => 50,
					'orderby'        => 'date',
					'order'          => 'ASC',
					'date_created'   => '<' . $cutoff,
					'payment_method' => array(
						'peachpay_convesiopay_card',
						'peachpay_convesiopay_applepay',
						'peachpay_convesiopay_googlepay',
						'peachpay_convesiopay_btcpay',
					),
					'meta_query'     => array(
						'relation' => 'AND',
						// Payment ID: NOT IN excludes both missing meta and invalid values (''/'0') – single JOIN
						array(
							'key'     => '_convesiopay_payment_id',
							'value'   => array( '', '0' ),
							'compare' => 'NOT IN',
							'type'    => 'CHAR',
						),
						// Manual capture explicitly set to 'no'
						array(
							'key'     => '_convesiopay_manual_capture_mode',
							'value'   => 'no',
							'compare' => '=',
						),
					),
					'return' => 'objects',
				)
			);

			// Filter out orders with empty/zero payment ID (safety net: meta_query may not exclude all edge cases)
			$orders = is_array( $orders ) ? $orders : array();
			$orders = array_filter( $orders, function ( $order ) {
				$pid = $order->get_meta( '_convesiopay_payment_id' );
				$pid = is_string( $pid ) ? trim( $pid ) : (string) $pid;
				return $pid !== '' && $pid !== '0';
			} );

			$count = count( $orders );
			peachpay_log(
				'info',
				'ConvesioPay Reconciliation eligible orders fetch - completed.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation',
					'outcome'   => 'success',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: ' . $count . ' eligible orders'
			)));
			return array_values( $orders );
		} catch ( \Throwable $e ) {
			$this->log_reconciliation_error( 'get_eligible_orders', $e );
			return array();
		}
	}

	/**
	 * Process one order: acquire lock, poll ConvesioPay, apply result.
	 *
	 * @param WC_Order $order        Order.
	 * @param array    $payments_map Optional. Map of payment_id => payment_data from List API (reduces per-order API calls).
	 */
	private function process_order( $order, $payments_map = array() ) {
		$order_id  = $order->get_id();
		$lock_key  = 'peachpay_convesiopay_reconcile_lock_' . $order_id;

		try {
			if ( get_transient( $lock_key ) ) {
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation order processing - skipped (already locked).',
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' already being processed by another cron run'
				)));
				return;
			}

			set_transient( $lock_key, 1, PEACHPAY_CONVESIOPAY_RECONCILE_LOCK_TTL );

			peachpay_log(
				'info',
				'ConvesioPay Reconciliation order processing - started for order #' . $order_id,
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation',
					'outcome'   => 'success',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: Processing order #' . $order_id
			)));

			$order = wc_get_order( $order_id );

			if ( ! $order || ! $order->has_status( 'pending' ) ) {
				delete_transient( $lock_key );
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation order processing - skipped (order not pending) , order #' . $order_id,
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' no longer pending'
				)));
				return;
			}

			// Get the Payment ID
			$payment_id = $order->get_meta('_convesiopay_payment_id');
			$payment_id = is_string( $payment_id ) ? trim( $payment_id ) : (string) $payment_id;
			if ( $payment_id === '' || $payment_id === '0' ) {
				$raw = $order->get_meta('_convesiopay_payment_id');
				$debug = $raw === null ? 'null' : ( $raw === '' ? 'empty' : ( (string) $raw === '0' ? 'zero' : 'len=' . strlen( (string) $raw ) ) );
				$this->increment_reconcile_attempt_and_maybe_fail( $order );
				delete_transient($lock_key);
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation order processing - skipped (missing payment ID) , order #' . $order_id,
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' no payment ID (' . $debug . ')'
				)));
				return;
			}

			// Use pre-fetched payment from List API when available; otherwise fetch individually
			$from_map = isset( $payments_map[ $payment_id ] );
			$poll_result = $from_map ? $payments_map[ $payment_id ] : $this->fetch_payment_status( $payment_id );

			delete_transient( $lock_key );

			// Check if the payment status is false (API error: 404, 503, etc.)
			if ($poll_result === false) {
				$this->increment_reconcile_attempt_and_maybe_fail( $order );
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation order processing - poll failed, order #' . $order_id,
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' poll result false'
				)));
				return;
			}

			// Get the reconciliation attempts
			$attempts = (int) $order->get_meta('_convesiopay_reconcile_attempts') + 1;

			// Get the payment status from the poll result
			$status   = isset($poll_result['status']) ? $poll_result['status'] : '';

			// Check if the payment status is in the list of ConvesioPay order statuses that stop polling next time
			if (in_array($status, self::$convesiopay_order_statuses, true)) {
				// Update Order status
				$this->apply_status_to_order($order, $status, $poll_result, $payment_id);
				$order = wc_get_order($order->get_id());
				if ($order) {
					$order->update_meta_data('_convesiopay_reconcile_attempts', $attempts);
					$order->update_meta_data('_convesiopay_reconcile_last_poll_at', time());
					$order->save();
				}
			} else {
				$order->update_meta_data('_convesiopay_reconcile_attempts', $attempts);
				$order->update_meta_data('_convesiopay_reconcile_last_poll_at', time());
				$order->save();
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation order processing - pending retry, order #' . $order_id,
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' still pending (status: ' . $status . '), will retry'
				)));
			}
		} catch ( \Throwable $e ) {
			delete_transient($lock_key);
			$this->log_reconciliation_error( 'process_order order_id=' . $order_id, $e );
		}
	}

	/**
	 * Increment reconciliation attempts and mark order failed if max reached.
	 *
	 * @param WC_Order $order Order.
	 */
	private function increment_reconcile_attempt_and_maybe_fail( $order ) {
		$attempts = (int) $order->get_meta( '_convesiopay_reconcile_attempts' ) + 1;
		$order->update_meta_data( '_convesiopay_reconcile_attempts', $attempts );
		$order->update_meta_data( '_convesiopay_reconcile_last_poll_at', time() );
		if ( $attempts >= PEACHPAY_CONVESIOPAY_RECONCILE_MAX_ATTEMPTS ) {
			$order->update_status( 'failed', 'ConvesioPay reconciliation failed after multiple attempts.' );
			peachpay_log(
				'info',
				'ConvesioPay Reconciliation retry attempts - max reached.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation',
					'outcome'   => 'success',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' marked failed (max attempts reached)'
			)));
		}
		$order->save();
	}

	/**
	 * Fetch payment status from ConvesioPay API (GET /payments/{id}).
	 *
	 * @param string $payment_id Payment ID.
	 * @return array|false Parsed response body on success, false on failure (do not increment attempt).
     * @link https://docs.convesiopay.com/convesiopay-payment-checkout-integration-api/payments/get-a-single-payment
	 */
	private function fetch_payment_status($payment_id)
	{
		try {
			// Get the API URL and secret key from the configuration
			$config   = PeachPay_ConvesioPay_Integration::config();

			// Get the API URL
			$api_url  = isset($config['api_url']) ? $config['api_url'] : '';
			$secret   = isset($config['secret_key']) ? $config['secret_key'] : '';

            // Check if the API URL is empty
            if (empty($api_url)) {
                return false;
            }

			// Check if the secret key is empty
			if (empty($secret)) {
				return false;
			}

			// Build the API URL
			$url      = rtrim($api_url, '/') . '/payments/' . $payment_id;

			// Make the API request
			$response = wp_remote_get(
				$url,
				array(
					'headers' => array(
						'Authorization' => $secret,
						'Content-Type'  => 'application/json',
					),
					'timeout' => 30,
				)
			);

			// Check if the API request failed
			if (is_wp_error($response)) {
				peachpay_log(
					'error',
					'ConvesioPay Reconciliation fetch payment status - failed.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.failed',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'failed',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: GET payment failed - ' . $response->get_error_message()
				)));
				return false;
			}
			$code = wp_remote_retrieve_response_code( $response );
			$body = wp_remote_retrieve_body( $response );

			if ( $code !== 200 ) {
				peachpay_log(
					'error',
					'ConvesioPay Reconciliation fetch payment status - failed.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.failed',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'failed',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: GET payment HTTP ' . $code . ' for payment ' . $payment_id . ' - ' . $body
				)));
				return false;
			}

			peachpay_log(
				'debug',
				'ConvesioPay Reconciliation fetch payment status - debug.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.fetch_payment_status.debug',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'fetch_payment_status',
					'outcome'   => 'debug',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay CPay API: GET /payments/' . $payment_id . ' - HTTP ' . $code . ' - ' . $body
			)));

			// Decode the response body
			$data = json_decode($body, true);

			// Check if the response body is not valid JSON or does not have a status
			if (! is_array($data) || empty($data['status'])) {
				peachpay_log(
					'error',
					'ConvesioPay Reconciliation fetch payment status - invalid response.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.failed',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'failed',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: GET payment invalid response for ' . $payment_id . ' - missing status or invalid JSON'
				)));
				return false;
			}

			// Return the response data
			return $data;
		} catch ( \Throwable $e ) {
			$this->log_reconciliation_error( 'fetch_payment_status payment_id=' . $payment_id, $e );
			return false;
		}
	}

	/**
	 * Fetch payments in batch via List API (GET /v1/payments/list) to reduce API calls.
	 * Returns a map of payment_id => payment_data for all payments in the orders' date range.
	 *
	 * @param WC_Order[] $orders Eligible orders.
	 * @return array Map of payment_id => payment_data (empty on failure).
	 * @link https://docs.convesiopay.com/convesiopay-payment-checkout-integration-api/payments/list-payments
	 */
	private function fetch_payments_list_batch( $orders ) {
		$map = array();
		try {
			$config = PeachPay_ConvesioPay_Integration::config();
			$api_url = isset( $config['api_url'] ) ? $config['api_url'] : '';
			$secret  = isset( $config['secret_key'] ) ? $config['secret_key'] : '';

			if ( empty( $api_url ) || empty( $secret ) ) {
				peachpay_log(
					'error',
					'ConvesioPay Reconciliation list payments fetch - config missing.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.failed',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'failed',
						'method'    => __METHOD__,
						'line'      => __LINE__,
					)
				);
				return $map;
			}

			// Compute date range from order dates (List API uses createdAt)
			$min_ts = PHP_INT_MAX;
			$max_ts = 0;
			foreach ( $orders as $order ) {
				$created = $order->get_date_created();
				if ( $created ) {
					$ts = $created->getTimestamp();
					$min_ts = min( $min_ts, $ts );
					$max_ts = max( $max_ts, $ts );
				}
			}
			if ( $min_ts === PHP_INT_MAX ) {
				$min_ts = time() - ( 30 * DAY_IN_SECONDS );
			}
			if ( $max_ts === 0 ) {
				$max_ts = time();
			}
			// Extend range to catch edge cases (7 days each side - API may use different timezone)
			$from_ts = $min_ts - ( 7 * DAY_IN_SECONDS );
			$to_ts   = min( time() + HOUR_IN_SECONDS, $max_ts + ( 7 * DAY_IN_SECONDS ) );
			// API requires RFC 3339 format for from/to (uses createdAt field)
			$from = gmdate( 'Y-m-d\TH:i:s\Z', $from_ts );
			$to   = gmdate( 'Y-m-d\TH:i:s\Z', $to_ts );

			$base = rtrim( $api_url, '/' );
			if ( strpos( $base, '/v1' ) === false ) {
				$base .= '/v1';
			}
			$base_url  = $base . '/payments/list';
			$page      = 1;
			$page_size = 30;

			do {
				$url = add_query_arg(
					array(
						'from'       => $from,
						'to'         => $to,
						'pageSize'   => $page_size,
						'pageNumber' => $page,
					),
					$base_url
				);

				$response = wp_remote_get(
					$url,
					array(
						'headers' => array(
							'Authorization' => $secret,
							'Content-Type'  => 'application/json',
						),
						'timeout' => 30,
					)
				);

				if ( is_wp_error( $response ) ) {
					peachpay_log(
						'error',
						'ConvesioPay Reconciliation list payments fetch - failed.',
						array(
							'event'     => 'payments.convesiopay_reconciliation.reconciliation.failed',
							'component' => 'payments',
							'feature'   => 'convesiopay_reconciliation',
							'operation' => 'reconciliation',
							'outcome'   => 'failed',
							'method'    => __METHOD__,
							'line'      => __LINE__,
							'data'      => array(
								'details' => 'PeachPay Reconciliation: List payments failed - ' . $response->get_error_message()
					)));
					break;
				}

				$code = wp_remote_retrieve_response_code( $response );
				if ( $code !== 200 ) {
					$body = wp_remote_retrieve_body( $response );
					peachpay_log(
						'error',
						'ConvesioPay Reconciliation list payments fetch - failed.',
						array(
							'event'     => 'payments.convesiopay_reconciliation.reconciliation.failed',
							'component' => 'payments',
							'feature'   => 'convesiopay_reconciliation',
							'operation' => 'reconciliation',
							'outcome'   => 'failed',
							'method'    => __METHOD__,
							'line'      => __LINE__,
							'data'      => array(
								'details' => 'PeachPay Reconciliation: List payments HTTP ' . $code . ' - ' . $body
					)));
					break;
				}

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

				if ( ! is_array( $data ) || empty( $data['payments'] ) ) {
					break;
				}

				foreach ( $data['payments'] as $payment ) {
					$pid = $payment['id'] ?? $payment['paymentId'] ?? $payment['payment_id'] ?? '';
					if ( ! empty( $pid ) ) {
						$map[ $pid ] = $payment;
					}
				}

				$pagination = isset( $data['pagination'] ) ? $data['pagination'] : array();
				$pages     = isset( $pagination['pages'] ) ? (int) $pagination['pages'] : 1;
				$page++;
			} while ( $page <= $pages );

			peachpay_log(
				'debug',
				'ConvesioPay Reconciliation list payments fetch - debug.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation.debug',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation',
					'outcome'   => 'debug',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: List API returned ' . count( $map ) . ' payments'
			)));

		} catch ( \Throwable $e ) {
			$this->log_reconciliation_error( 'fetch_payments_list_batch', $e );
		}

		return $map;
	}

	/**
	 * Apply ConvesioPay status to WC order (mirror webhook handler behavior).
	 *
	 * @param WC_Order $order        Order (reload to avoid races).
	 * @param string   $status       ConvesioPay status (Succeeded, Failed, Blocked, Refunded, Cancelled, Disputed).
	 * @param array    $payment_data Full GET response.
	 * @param string   $payment_id   Payment ID.
	 */
	private function apply_status_to_order($order, $status, $payment_data, $payment_id)
	{
		try {
			// Re-load from DB: skip if order is no longer pending (e.g. webhook or previous run already updated it).
			$order = wc_get_order($order->get_id());

			// Skip if the order is no longer pending
			if (! $order || ! $order->has_status('pending')) {
				return;
			}

			// Get the payment URL
			$payment_url = peachpay_convesiopay_dashboard_url() . '/payments/' . $payment_id;

			// Derive payment method from GET response (same logic as webhook paymentMethodDetails).
			$payment_method = $this->get_payment_method_from_response( $payment_data );

			// Normalize the status
			$normalized_status = strtolower( $status );
			$order_id         = $order->get_id();

			// Handle different payment statuses (original payment flow)
			switch ( $normalized_status ) {
				case 'succeeded':  // ConvesioPay documentation status
					$this->handle_payment_success( $order, $payment_id, $payment_data, $payment_method );
					break;
				case 'cancelled':  // ConvesioPay documentation status
					$this->handle_payment_cancelled( $order, $payment_id );
					peachpay_log(
						'info',
						'ConvesioPay Reconciliation status apply - cancelled handled.',
						array(
							'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
							'component' => 'payments',
							'feature'   => 'convesiopay_reconciliation',
							'operation' => 'reconciliation',
							'outcome'   => 'success',
							'method'    => __METHOD__,
							'line'      => __LINE__,
							'data'      => array(
								'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' → Cancelled'
					)));
					break;

				case 'failed':     // ConvesioPay documentation status
				case 'blocked':    // ConvesioPay documentation status
				case 'disputed':   // ConvesioPay documentation status
					$this->handle_payment_failure( $order, $payment_id, $payment_data );
					peachpay_log(
						'info',
						'ConvesioPay Reconciliation status apply - failure handled.',
						array(
							'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
							'component' => 'payments',
							'feature'   => 'convesiopay_reconciliation',
							'operation' => 'reconciliation',
							'outcome'   => 'success',
							'method'    => __METHOD__,
							'line'      => __LINE__,
							'data'      => array(
								'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' → Failed (' . $status . ')'
					)));
					break;

				case 'refunded':   // ConvesioPay documentation status - handle as refund
					$this->handle_payment_refund( $order, $payment_id, $payment_data );
					peachpay_log(
						'info',
						'ConvesioPay Reconciliation status apply - refund handled.',
						array(
							'event'     => 'payments.convesiopay_reconciliation.refund.success',
							'component' => 'payments',
							'feature'   => 'convesiopay_reconciliation',
							'operation' => 'refund',
							'outcome'   => 'success',
							'method'    => __METHOD__,
							'line'      => __LINE__,
							'data'      => array(
								'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' → Refunded'
					)));
					break;

				default:
					$payment_url = peachpay_convesiopay_dashboard_url() . '/payments/' . $payment_id;
					$order->add_order_note( "ConvesioPay reconciliation received with unknown status: $status (Payment ID: $payment_id) | <a href=\"$payment_url\" target=\"_blank\">View Payment</a>" );
					break;
			}
		} catch ( \Throwable $e ) {
			$order_id = is_object( $order ) && method_exists( $order, 'get_id' ) ? $order->get_id() : '?';
			$this->log_reconciliation_error( 'apply_status_to_order order_id=' . $order_id . ' status=' . $status, $e );
		}
	}

	/**
	 * Handle payment success.
	 *
	 * @param WC_Order $order          Order.
	 * @param string   $payment_id     Payment ID.
	 * @param array    $payment_data   Payment data.
     * @param string   $payment_method Payment method.
	 */
	private function handle_payment_success($order, $payment_id, $payment_data, $payment_method) {

        // Check if order is already completed or refunded
		if ($order->has_status(array('processing', 'completed', 'refunded'))) {
			peachpay_log(
				'info',
				'ConvesioPay Reconciliation payment success handler - skipped (already processed).',
				array(
					'event'     => 'payments.convesiopay_reconciliation.refund.success',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'refund',
					'outcome'   => 'success',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' already completed or refunded'
			)));
			return;
		}

        // Store payment status as "captured" to prevent duplicate capture attempts
		$order->update_meta_data('_convesiopay_payment_status', 'captured');
		
        // Add payment ID and URL to order notes
		$payment_url = peachpay_convesiopay_dashboard_url() . '/payments/' . $payment_id;
		$note = sprintf(
			'ConvesioPay payment successful. Payment ID: %s | <a href="%s" target="_blank">View Payment</a>',
			$payment_id,
			$payment_url
		);

        // Add amount and currency if available
		if ( isset( $payment_data['amount'] ) && isset( $payment_data['currency'] ) ) {
			$amount = $payment_data['amount'];
			$currency = $payment_data['currency'];
			
			// Convert from minor units if needed (cents to dollars)
			if ( is_numeric( $amount ) && $amount >= 50 ) {
				$formatted_amount = number_format( $amount / 100, 2 );
			} else {
				$formatted_amount = $amount;
			}
			
			$note .= sprintf( ' Amount: %s %s', $formatted_amount, $currency );
		}
		
		$order->add_order_note( $note );
		
        // Check if payment ID has changed and update accordingly
		$existing_payment_id = $order->get_meta( '_convesiopay_payment_id' );
		if ( ! empty( $existing_payment_id ) && $existing_payment_id !== $payment_id ) {
			$order->add_order_note( sprintf( __( 'ConvesioPay Payment ID updated from %s to %s', 'peachpay-for-woocommerce' ), $existing_payment_id, $payment_id ) );
		}

		// Store original payment ID only if not already set (never overwrite - needed for refunds)
		if ( empty( $order->get_meta( '_convesiopay_original_payment_id' ) ) ) {
			$order->update_meta_data( '_convesiopay_original_payment_id', $payment_id );
		}

		// Store payment ID as meta
		$order->update_meta_data( '_convesiopay_payment_id', $payment_id );

		// Store payment method information for future reference
		if ( ! empty( $payment_method ) ) {
			$order->update_meta_data( '_convesiopay_payment_method', $payment_method );
		}

		// Extract and store subscription-related tokens for Card On File renewals
		// customerId is at root level, storedPaymentMethodId is inside paymentMethodDetails
		$customer_id = $payment_data['customerId'] ?? '';
		$payment_method_details = $payment_data['paymentMethodDetails'] ?? array();
		$stored_payment_method_id = $payment_method_details['storedPaymentMethodId'] ?? '';

		if ( ! empty( $customer_id ) ) {
			$order->update_meta_data( '_convesiopay_customer_id', $customer_id );
			// Also store on user meta for future subscription renewals
			$user_id = $order->get_user_id();
			if ( $user_id ) {
				update_user_meta( $user_id, '_convesiopay_customer_id', $customer_id );
			}
		}

		if ( ! empty( $stored_payment_method_id ) ) {
			$order->update_meta_data( '_convesiopay_stored_payment_method_id', $stored_payment_method_id );
			// Also store on user meta for subscription renewals
			$user_id = $order->get_user_id();
			if ( $user_id ) {
				update_user_meta( $user_id, '_convesiopay_stored_payment_method_id', $stored_payment_method_id );
				// Store payment method details for display (card brand, last4, etc.)
				update_user_meta( $user_id, '_convesiopay_payment_method_details', $payment_method_details );
			}
		}

		// Set payment method title based on actual payment method used
		$order->set_payment_method_title( $this->get_payment_method_title( $order ) );

		// Update transaction ID
		$order->set_transaction_id( $payment_id );

		// Update order status to processing - payment is confirmed and captured
		$order->update_status( 'processing', 'Payment completed via ConvesioPay reconciliation.' );
		
		$order->save();
		peachpay_log(
			'info',
			'ConvesioPay Reconciliation payment success handler - completed.',
			array(
				'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
				'component' => 'payments',
				'feature'   => 'convesiopay_reconciliation',
				'operation' => 'reconciliation',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'data'      => array(
					'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' handled payment success'
		)));
	}

    /**
	 * Handle cancelled payment.
	 *
	 * @param WC_Order $order Order object.
	 * @param string   $payment_id Payment ID.
	 */
	private function handle_payment_cancelled( $order, $payment_id ) {

		// Only update if order is not already cancelled
		if ( $order->get_status() === 'cancelled' ) {
			// Order is already cancelled by merchant, just add a note
			$payment_url = peachpay_convesiopay_dashboard_url() . '/payments/' . $payment_id;
			$note = sprintf(
				'ConvesioPay payment cancelled. Payment ID: %s | <a href="%s" target="_blank">View Payment</a>',
				$payment_id,
				$payment_url
			);
			$order->add_order_note( $note );

			// Check if payment ID has changed and update accordingly
			$existing_payment_id = $order->get_meta( '_convesiopay_payment_id' );
			if ( ! empty( $existing_payment_id ) && $existing_payment_id !== $payment_id ) {
				$order->add_order_note( sprintf( __( 'ConvesioPay Payment ID updated from %s to %s', 'peachpay-for-woocommerce' ), $existing_payment_id, $payment_id ) );
			}

			// Store payment ID and update transaction ID
			$order->update_meta_data( '_convesiopay_payment_id', $payment_id );
			$order->set_transaction_id( $payment_id );
			$order->save();

			peachpay_log(
				'info',
				'ConvesioPay Reconciliation payment cancellation handler - note added.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation',
					'outcome'   => 'success',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' handled payment cancelled'
			)));
			return;
		}

		// If order is in a different status, update it to cancelled
		$payment_url = peachpay_convesiopay_dashboard_url() . '/payments/' . $payment_id;
		$note = sprintf(
			'ConvesioPay payment cancelled. Payment ID: %s | <a href="%s" target="_blank">View Payment</a>',
			$payment_id,
			$payment_url
		);

		$order->add_order_note( $note );

		// Check if payment ID has changed and update accordingly
		$existing_payment_id = $order->get_meta( '_convesiopay_payment_id' );
		if ( ! empty( $existing_payment_id ) && $existing_payment_id !== $payment_id ) {
			$order->add_order_note( sprintf( __( 'ConvesioPay Payment ID updated from %s to %s', 'peachpay-for-woocommerce' ), $existing_payment_id, $payment_id ) );
		}

		// Store payment ID and update transaction ID
		$order->update_meta_data( '_convesiopay_payment_id', $payment_id );
		$order->set_transaction_id( $payment_id );

		// Update status to Cancelled
		$order->update_status( 'cancelled', 'Payment cancelled via ConvesioPay reconciliation.' );

		$order->save();
		peachpay_log(
			'info',
			'ConvesioPay Reconciliation payment cancellation handler - completed.',
			array(
				'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
				'component' => 'payments',
				'feature'   => 'convesiopay_reconciliation',
				'operation' => 'reconciliation',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'data'      => array(
					'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' handled payment cancelled'
		)));
	}

    /**
	 * Handle failed payment.
	 *
	 * @param WC_Order $order Order object.
	 * @param string   $payment_id Payment ID.
	 * @param array    $payment_data Payment data.
	 */
	private function handle_payment_failure( $order, $payment_id, $payment_data ) {
		
		// Get error details (GET /payments response may use reason, e.g. "Refused")
		$error_message = $payment_data['errorMessage'] ?? $payment_data['failureReason'] ?? $payment_data['reason'] ?? $payment_data['message'] ?? __( 'Payment failed', 'peachpay-for-woocommerce' );
		$error_code = $payment_data['errorCode'] ?? $payment_data['code'] ?? '';
		
		// Create detailed error note with payment URL
		$payment_url = peachpay_convesiopay_dashboard_url() . '/payments/' . $payment_id;
		$note = sprintf(
			'ConvesioPay payment failed. Payment ID: %s. Error: %s | <a href="%s" target="_blank">View Payment</a>',
			$payment_id,
			$error_message,
			$payment_url
		);
		
		if ( $error_code ) {
			$note .= sprintf( ' (Code: %s)', $error_code );
		}
		
		// Don't fail orders that are already captured/processed
		$payment_status = $order->get_meta( '_convesiopay_payment_status' );
		if ( $payment_status === 'captured' || $order->has_status( array( 'processing', 'completed' ) ) ) {
			$order->add_order_note( $note . ' (Ignored - order already processed)' );
			$order->save();
			peachpay_log(
				'info',
				'ConvesioPay Reconciliation payment failure handler - ignored.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation',
					'outcome'   => 'success',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' handled payment failure (Ignored - order already processed)'
			)));
			return;
		}
		
		$order->add_order_note( $note );
		$order->update_meta_data( '_convesiopay_failure_reason', $error_message );
		$order->update_status( 'failed', 'Payment failed via ConvesioPay reconciliation: ' . $error_message );
		$order->save();

		peachpay_log(
			'info',
			'ConvesioPay Reconciliation payment failure handler - completed.',
			array(
				'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
				'component' => 'payments',
				'feature'   => 'convesiopay_reconciliation',
				'operation' => 'reconciliation',
				'outcome'   => 'success',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'data'      => array(
					'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' handled payment failure'
		)));
	}

    /**
	 * Handle refund
	 *
	 * @param WC_Order $order Order object.
	 * @param string   $payment_id Payment ID.
	 * @param array    $payment_data Payment data.
	 */
	private function handle_payment_refund( $order, $payment_id, $payment_data ) {
		try {
			$order_id = $order && method_exists( $order, 'get_id' ) ? $order->get_id() : 0;

			// Extract refund details from payment data
			$refund_payment_id = $payment_id; // This is the refund transaction ID
			$refund_amount_cents = $payment_data['amount'] ?? 0;
			$currency = $payment_data['currency'] ?? $order->get_currency();

			// Convert from cents to dollars (ConvesioPay uses cents: 1000 = $10.00)
			$refund_amount = $refund_amount_cents / 100;

			// Check if this refund was already processed from WooCommerce admin
			// If a WooCommerce refund with this ConvesioPay refund ID already exists,
			// it means the refund was created from WooCommerce (not from ConvesioPay Dashboard)
			$refunds = $order->get_refunds();
			$woocommerce_initiated_refund = false;

			foreach ( $refunds as $existing_refund ) {
				$stored_refund_id = $existing_refund->get_meta( '_convesiopay_refund_id' );
				if ( $stored_refund_id === $refund_payment_id ) {
					// This reconciliation is a confirmation of a WooCommerce-initiated refund
					$woocommerce_initiated_refund = true;
					break;
				}

				// Also check if refund amount matches (for refunds created before we started tracking IDs)
				$existing_refund_amount = abs( $existing_refund->get_amount() );
				if ( abs( $existing_refund_amount - $refund_amount ) < 0.01 ) {
					// Same amount refund exists, likely from WooCommerce admin
					// Tag it with the ConvesioPay refund ID
					$existing_refund->update_meta_data( '_convesiopay_refund_id', $refund_payment_id );
					$existing_refund->save();
					$woocommerce_initiated_refund = true;
					break;
				}
			}

			// If this was a WooCommerce-initiated refund, just update transaction ID and payment ID, then exit
			if ( $woocommerce_initiated_refund ) {
				// Update transaction ID and payment ID metadata
				$existing_transaction_id = $order->get_transaction_id();
				if ( ! empty( $existing_transaction_id ) && $existing_transaction_id !== $refund_payment_id ) {
					$order->add_order_note( sprintf( __( 'ConvesioPay Transaction ID updated from %s to %s (refunded)', 'peachpay-for-woocommerce' ), $existing_transaction_id, $refund_payment_id ) );
				}

				$order->update_meta_data( '_convesiopay_payment_id', $refund_payment_id );
				$order->update_meta_data( '_convesiopay_refund_id', $refund_payment_id );
				$order->set_transaction_id( $refund_payment_id );
				$order->save();

				// Skip further processing to avoid duplicate notes and status updates
				return;
			}

			// Store original payment ID from order before updating
			$original_payment_id = $order->get_meta( '_convesiopay_payment_id' );

			// Create payment URL for ConvesioPay Dashboard
			$payment_url = peachpay_convesiopay_dashboard_url() . '/payments/' . $refund_payment_id;

			// Create detailed note with refund information and payment URL
			$note = sprintf(
				'ConvesioPay refund processed from Dashboard. Refund Payment ID: %s, Amount: %s %s | <a href="%s" target="_blank">View Payment</a>',
				$refund_payment_id,
				number_format( $refund_amount, 2 ),
				$currency,
				$payment_url
			);

			if ( ! empty( $original_payment_id ) && $original_payment_id !== $refund_payment_id ) {
				$note .= sprintf( ' (Original Payment ID: %s)', $original_payment_id );
			}

			$order->add_order_note( $note );

			// Check if transaction ID has changed (from payment ID to refund ID)
			$existing_transaction_id = $order->get_transaction_id();
			if ( ! empty( $existing_transaction_id ) && $existing_transaction_id !== $refund_payment_id ) {
				$order->add_order_note( sprintf( __( 'ConvesioPay Transaction ID updated from %s to %s (refunded)', 'peachpay-for-woocommerce' ), $existing_transaction_id, $refund_payment_id ) );
			}

			// Update payment ID to refund payment ID
			$order->update_meta_data( '_convesiopay_payment_id', $refund_payment_id );
			$order->update_meta_data( '_convesiopay_refund_id', $refund_payment_id );
			$order->update_meta_data( '_convesiopay_last_refund_amount', $refund_amount );
			$order->set_transaction_id( $refund_payment_id );

			// Save the order to persist payment ID and transaction ID updates
			$order->save();

			// Create WooCommerce refund (reconciliation only gets here if refund doesn't exist from WooCommerce admin)
			$wc_refund = wc_create_refund( array(
				'order_id' => $order->get_id(),
				'amount' => $refund_amount,
				'reason' => sprintf( 'ConvesioPay Dashboard refund - ID: %s', $refund_payment_id ),
			) );

			if ( $wc_refund && ! is_wp_error( $wc_refund ) ) {
				// Store ConvesioPay refund ID in the WooCommerce refund object
				$wc_refund->update_meta_data( '_convesiopay_refund_id', $refund_payment_id );
				$wc_refund->save();
			} else {
				$error_message = is_wp_error( $wc_refund ) ? $wc_refund->get_error_message() : 'Unknown error';
				$order->add_order_note( 'Warning: ConvesioPay refund received but WooCommerce refund creation failed: ' . $error_message );
			}

			// Refresh order data to get updated refund totals
			$order = wc_get_order( $order->get_id() );

			if ( ! $order ) {
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation refund handler - order refresh missing.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.refund.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'refund',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order_id . ' handled payment refund (order not found after refresh)'
				)));
				return;
			}

			// Check if this is a full or partial refund
			$total_refunded = $order->get_total_refunded();
			$order_total = $order->get_total();

			// Update order status based on refund completeness
			if ( $total_refunded >= $order_total ) {
				// Full refund - update order status to refunded
				$order->update_status( 'refunded', 'Order fully refunded via ConvesioPay Dashboard.' );
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation refund handler - full refund applied.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.refund.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'refund',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' handled payment refund (Full refund)'
				)));
			} else {
				// Partial refund - add informative note
				$refund_percentage = ( $total_refunded / $order_total ) * 100;
				$order->add_order_note( sprintf(
					'Partial refund: %s %s of %s %s total (%.1f%% refunded)',
					number_format( $total_refunded, 2 ),
					$currency,
					number_format( $order_total, 2 ),
					$currency,
					$refund_percentage
				) );
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation refund handler - partial refund applied.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.refund.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'refund',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
						'data'      => array(
							'details' => 'PeachPay Reconciliation: Order #' . $order->get_id() . ' handled payment refund (Partial refund)'
				)));
			}

			$order->save();
		} catch ( \Throwable $e ) {
			$this->log_reconciliation_error( 'handle_payment_refund order_id=' . ( isset( $order_id ) ? $order_id : ( $order && method_exists( $order, 'get_id' ) ? $order->get_id() : '?' ) ), $e );
		}
	}

	/**
	 * Derive payment method string from GET /payments response (mirror webhook paymentMethodDetails logic).
	 *
	 * @param array $payment_data GET /payments response or webhook payload.
	 * @return string Payment method (e.g. card, visa, mastercard, applepay, btcpay).
	 */
	private function get_payment_method_from_response( $payment_data ) {
		$details   = $payment_data['paymentMethodDetails'] ?? array();
		$type      = $details['type'] ?? '';
		$brand     = $details['brand'] ?? '';
		$cardholder = $details['cardHolderName'] ?? '';
		$method    = $type;
		if ( $type === 'card' && ! empty( $brand ) ) {
			$method = $brand;
		} elseif ( $type === 'card' && $brand === '' && $cardholder === 'Apple Pay PlaceHolder' ) {
			$method = 'applepay';
		} elseif ( $type === 'card' && $brand === '' && $cardholder === 'Google Pay PlaceHolder' ) {
			$method = 'googlepay';
		}
		return $method;
	}

	/**
	 * Get the appropriate payment method title based on the registered gateway.
	 *
	 * Resolves `peachpay_convesiopay_unified` to the correct sub-gateway via
	 * order meta before looking up the title from the WC gateway registry.
	 *
	 * @param WC_Order $order Order object.
	 * @return string Payment method title.
	 */
	private function get_payment_method_title( $order ) {
		$gateway_id = $order->get_payment_method();

		if ( empty( $gateway_id ) || 'peachpay_convesiopay_unified' === $gateway_id ) {
			$resolved = $this->resolve_sub_gateway_id( $order );
			if ( ! empty( $resolved ) ) {
				$gateway_id = $resolved;
			}
		}

		$wc = function_exists( 'WC' ) ? WC() : null;
		if ( empty( $gateway_id ) || ! $wc || ! $wc->payment_gateways() ) {
			return $order->get_payment_method_title() ?: 'ConvesioPay';
		}

		$gateways = $wc->payment_gateways()->payment_gateways();

		if ( ! isset( $gateways[ $gateway_id ] ) ) {
			return $order->get_payment_method_title() ?: 'ConvesioPay';
		}

		$gateway = $gateways[ $gateway_id ];
		return $gateway ? $gateway->get_title() : ( $order->get_payment_method_title() ?: 'ConvesioPay' );
	}

	/**
	 * Resolve the actual sub-gateway ID from order meta.
	 *
	 * @param WC_Order $order Order object.
	 * @return string Sub-gateway ID or empty string.
	 */
	private function resolve_sub_gateway_id( $order ) {
		$payment_type = $order->get_meta( '_convesiopay_payment_type' );
		if ( empty( $payment_type ) ) {
			$payment_type = $order->get_meta( '_convesiopay_payment_method' );
		}
		if ( empty( $payment_type ) || ! is_string( $payment_type ) ) {
			return '';
		}

		$normalized = strtolower( trim( $payment_type ) );

		if ( in_array( $normalized, array( 'applepay', 'apple_pay', 'apple-pay', 'apple pay' ), true ) ) {
			return 'peachpay_convesiopay_applepay';
		}
		if ( in_array( $normalized, array( 'btcpay', 'btc_pay', 'btc-pay', 'crypto' ), true ) ) {
			return 'peachpay_convesiopay_btcpay';
		}
		if ( in_array( $normalized, array( 'googlepay', 'google_pay', 'google-pay', 'gpay' ), true ) ) {
			return 'peachpay_convesiopay_googlepay';
		}
		$card_values = array( 'card', 'visa', 'mastercard', 'amex', 'discover', 'mc', 'diners', 'jcb', 'unionpay', 'cup', 'elo', 'hiper', 'hipercard', 'cartesbancaires' );
		if ( in_array( $normalized, $card_values, true ) ) {
			return 'peachpay_convesiopay_card';
		}

		if ( strpos( $normalized, 'peachpay_convesiopay_' ) === 0 ) {
			return $payment_type;
		}

		return '';
	}

	/**
	 * Log reconciliation errors (catches both Exception and Error in PHP 7+).
	 *
	 * @param string    $context Context message (e.g. method name, order_id).
	 * @param \Throwable $e      Caught exception or error.
	 */
	private function log_reconciliation_error( $context, \Throwable $e ) {
		$message = 'PeachPay Reconciliation: Exception - ' . $context . ' - ' . $e->getMessage();
		$message .= ' in ' . $e->getFile() . ':' . $e->getLine();
		peachpay_log(
			'error',
			'ConvesioPay Reconciliation exception handler - failed.',
			array(
				'event'     => 'payments.convesiopay_reconciliation.reconciliation_error.failed',
				'component' => 'payments',
				'feature'   => 'convesiopay_reconciliation',
				'operation' => 'reconciliation_error',
				'outcome'   => 'failed',
				'method'    => __METHOD__,
				'line'      => __LINE__,
				'data'      => array(
					'details' => $message . "\n" . $e->getTraceAsString()
		)));
	}

	/**
	 * Unschedule all reconciliation cron events (e.g. on disconnect or plugin deactivation).
	 */
	public static function unschedule_cron()
	{
		try {
			while (wp_next_scheduled(PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK)) {
				$timestamp = wp_next_scheduled(PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK);
				wp_unschedule_event($timestamp, PEACHPAY_CONVESIOPAY_RECONCILE_CRON_HOOK);
				peachpay_log(
					'info',
					'ConvesioPay Reconciliation cron unschedule - completed.',
					array(
						'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
						'component' => 'payments',
						'feature'   => 'convesiopay_reconciliation',
						'operation' => 'reconciliation',
						'outcome'   => 'success',
						'method'    => __METHOD__,
						'line'      => __LINE__,
					)
				);
			}
		} catch ( \Throwable $e ) {
			$message = 'PeachPay Reconciliation: unschedule_cron - ' . $e->getMessage();
			$message .= ' in ' . $e->getFile() . ':' . $e->getLine();
			peachpay_log(
				'error',
				'ConvesioPay Reconciliation cron unschedule - failed.',
				array(
					'event'     => 'payments.convesiopay_reconciliation.reconciliation_error.failed',
					'component' => 'payments',
					'feature'   => 'convesiopay_reconciliation',
					'operation' => 'reconciliation_error',
					'outcome'   => 'failed',
					'method'    => __METHOD__,
					'line'      => __LINE__,
					'data'      => array(
						'details' => $message . "\n" . $e->getTraceAsString()
			)));
		}
	}
}

/**
 * Unschedule PeachPay reconciliation cron (used on disconnect and plugin deactivation).
 */
function peachpay_unschedule_convesiopay_reconcile_cron()
{
	PeachPay_ConvesioPay_Reconciliation::unschedule_cron();
	peachpay_log(
		'info',
		'ConvesioPay Reconciliation deactivation unschedule - completed.',
		array(
			'event'     => 'payments.convesiopay_reconciliation.reconciliation.success',
			'component' => 'payments',
			'feature'   => 'convesiopay_reconciliation',
			'operation' => 'reconciliation',
			'outcome'   => 'success',
			'method'    => __METHOD__,
			'line'      => __LINE__,
		)
	);
}

if (defined('PEACHPAY_PLUGIN_FILE')) {
	register_deactivation_hook(PEACHPAY_PLUGIN_FILE, 'peachpay_unschedule_convesiopay_reconcile_cron');
}
