<?php
/**
 * PeachPay Square webhook handler.
 *
 * Handles direct webhooks from Square using HMAC-SHA256 signature verification.
 *
 * @package PeachPay
 */

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

require_once PEACHPAY_ABSPATH . 'core/payments/square/utils/class-peachpay-square-order-data.php';

/**
 * Handles incoming Square webhook events.
 *
 * @param WP_REST_Request $request The webhook request.
 * @return WP_REST_Response|WP_Error
 */
function peachpay_rest_api_square_webhook( $request ) {
	$payload   = $request->get_body();
	$sig_header = isset( $_SERVER['HTTP_X_SQUARE_HMACSHA256_SIGNATURE'] ) ? trim( wp_unslash( $_SERVER['HTTP_X_SQUARE_HMACSHA256_SIGNATURE'] ) ) : '';

	$webhook_secret = PeachPay_Square_Credentials::get_webhook_secret();

	if ( empty( $webhook_secret ) || empty( $sig_header ) ) {
		peachpay_log( 'error', '[Square] webhook: Missing webhook secret or signature header' );
		return new WP_Error( 'peachpay_square_webhook_missing_secret_or_signature', 'Webhook secret not configured', array( 'status' => 400 ) );
	}

	$notification_urls = peachpay_square_webhook_signature_urls();

	$is_valid = false;
	foreach ( $notification_urls as $notification_url ) {
		if ( peachpay_square_webhook_verify_signature( $payload, $sig_header, $webhook_secret, $notification_url ) ) {
			$is_valid = true;
			break;
		}
	}

	if ( ! $is_valid ) {
		peachpay_log( 'error', '[Square] webhook: Invalid HMAC signature' );
		return new WP_Error( 'peachpay_square_webhook_invalid_signature', 'Invalid signature', array( 'status' => 403 ) );
	}

	$body = json_decode( $payload, true );

	// Idempotency: bail early only if this event was already completed.
	$event_id = isset( $body['event_id'] ) ? sanitize_text_field( $body['event_id'] ) : '';
	if ( ! empty( $event_id ) ) {
		if ( get_transient( 'pp_sq_evt_' . $event_id ) ) {
			peachpay_log( 'debug', '[Square] webhook: duplicate event skipped | ' . json_encode( array( 'event_id' => $event_id ) ) );
			return new WP_REST_Response( array( 'received' => true ), 200 );
		}
	}

	// Environment guard: reject webhooks from wrong environment.
	$sq_env = isset( $_SERVER['HTTP_SQUARE_ENVIRONMENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_SQUARE_ENVIRONMENT'] ) ) : '';
	if ( ! empty( $sq_env ) ) {
		$expected_env = 'test' === PeachPay_Square_Credentials::current_mode() ? 'Sandbox' : 'Production';
		if ( $sq_env !== $expected_env ) {
			peachpay_log( 'warning', '[Square] webhook: environment mismatch. Got: ' . $sq_env . ', expected: ' . $expected_env );
			return new WP_REST_Response( array( 'error' => 'Environment mismatch' ), 400 );
		}
	} else {
		peachpay_log( 'debug', '[Square] webhook: square-environment header not present, allowing through' );
	}

	$type = isset( $body['type'] ) ? $body['type'] : '';
	$data = isset( $body['data'] ) ? $body['data'] : array();

	$processed_successfully = true;

	switch ( $type ) {
		case 'payment.created':
			$processed_successfully = peachpay_handle_square_payment_created_webhook( $data );
			break;

		case 'payment.updated':
			$processed_successfully = peachpay_handle_square_payment_webhook( $data );
			break;

		case 'refund.created':
		case 'refund.updated':
			$processed_successfully = peachpay_handle_square_refund_webhook( $data );
			break;

		case 'dispute.created':
		case 'dispute.state.updated':
			peachpay_handle_square_dispute_webhook( $data, $type );
			break;

		default:
			peachpay_log( 'debug', '[Square] webhook: unrecognized event type: ' . $type );
			break;
	}

	if ( ! $processed_successfully ) {
		return new WP_Error( 'peachpay_square_webhook_processing_failed', 'Webhook processing failed', array( 'status' => 500 ) );
	}

	if ( ! empty( $event_id ) ) {
		set_transient( 'pp_sq_evt_' . $event_id, 1, DAY_IN_SECONDS );
	}

	return new WP_REST_Response( array( 'received' => true ), 200 );
}

/**
 * Verifies Square webhook signature against raw payload and callback URL.
 *
 * @param string $raw_body Raw request body.
 * @param string $provided_signature Signature from X-Square-HmacSha256-Signature.
 * @param string $webhook_secret Square webhook signature key.
 * @param string $notification_url Canonical callback URL.
 * @return bool
 */
function peachpay_square_webhook_verify_signature( $raw_body, $provided_signature, $webhook_secret, $notification_url ) {
	if ( empty( $raw_body ) || empty( $provided_signature ) || empty( $webhook_secret ) || empty( $notification_url ) ) {
		return false;
	}

	$computed_signature = base64_encode( hash_hmac( 'sha256', $notification_url . $raw_body, $webhook_secret, true ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode

	return hash_equals( $computed_signature, $provided_signature );
}

/**
 * Gets candidate notification URLs for Square webhook signature verification.
 *
 * @return string[]
 */
function peachpay_square_webhook_signature_urls() {
	$urls = array();

	$rest_url = get_rest_url( null, PEACHPAY_ROUTE_BASE . '/square/webhook' );
	if ( ! empty( $rest_url ) ) {
		$urls[] = $rest_url;
	}

	$forwarded_proto = isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) ? strtolower( trim( wp_unslash( explode( ',', $_SERVER['HTTP_X_FORWARDED_PROTO'] )[0] ) ) ) : '';
	$forwarded_host  = isset( $_SERVER['HTTP_X_FORWARDED_HOST'] ) ? trim( wp_unslash( explode( ',', $_SERVER['HTTP_X_FORWARDED_HOST'] )[0] ) ) : '';
	$host            = ! empty( $forwarded_host ) ? $forwarded_host : ( isset( $_SERVER['HTTP_HOST'] ) ? trim( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : '' );
	$request_uri     = isset( $_SERVER['REQUEST_URI'] ) ? trim( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
	$scheme          = in_array( $forwarded_proto, array( 'http', 'https' ), true ) ? $forwarded_proto : ( is_ssl() ? 'https' : 'http' );

	if ( ! empty( $host ) && ! empty( $request_uri ) ) {
		$urls[] = $scheme . '://' . $host . $request_uri;
	}

	$normalized_urls = array();
	foreach ( $urls as $url ) {
		if ( empty( $url ) ) {
			continue;
		}

		$normalized_urls[] = $url;

		if ( false === strpos( $url, '?' ) ) {
			$normalized_urls[] = untrailingslashit( $url );
			$normalized_urls[] = trailingslashit( untrailingslashit( $url ) );
		}
	}

	return array_values( array_unique( $normalized_urls ) );
}

/**
 * Handles payment.updated webhook events.
 *
 * @param array $data The event data object.
 * @return bool
 */
function peachpay_handle_square_payment_webhook( $data ) {
	$payment = isset( $data['object']['payment'] ) ? $data['object']['payment'] : null;
	if ( ! $payment || empty( $payment['id'] ) ) {
		return true;
	}

	$payment_id = $payment['id'];
	$order      = peachpay_find_order_by_square_payment( $payment_id );

	if ( ! $order ) {
		peachpay_log( 'warning', '[Square] webhook payment.updated: No order found for payment ' . $payment_id );
		return false;
	}

	if ( ! PeachPay_Square_Integration::is_payment_gateway( $order->get_payment_method() ) ) {
		return true;
	}

	$stored_id = PeachPay_Square_Order_Data::get_payment( $order, 'id' );
	if ( $stored_id !== $payment_id ) {
		return true;
	}

	$mode            = PeachPay_Square_Order_Data::get_payment( $order, 'mode' );
	$payment_details = peachpay_square_get_canonical_payment_details( $payment_id, $mode, 'payment.updated' );
	if ( null === $payment_details ) {
		return false;
	}

	PeachPay_Square::calculate_payment_state( $order, $payment_details );

	return true;
}

/**
 * Handles payment.created webhook events.
 * Refreshes payment details and recalculates order/payment state.
 *
 * @param array $data The event data object.
 * @return bool
 */
function peachpay_handle_square_payment_created_webhook( $data ) {
	$payment = isset( $data['object']['payment'] ) ? $data['object']['payment'] : null;
	if ( ! $payment || empty( $payment['id'] ) ) {
		return true;
	}

	$payment_id = $payment['id'];
	$order      = peachpay_find_order_by_square_payment( $payment_id );

	if ( ! $order ) {
		peachpay_log( 'debug', '[Square] webhook payment.created: No order found for payment | ' . json_encode( array( 'payment_id' => $payment_id ) ) );
		return false;
	}

	if ( ! PeachPay_Square_Integration::is_payment_gateway( $order->get_payment_method() ) ) {
		return true;
	}

	$stored_id = PeachPay_Square_Order_Data::get_payment( $order, 'id' );
	if ( ! empty( $stored_id ) && $stored_id !== $payment_id ) {
		return true;
	}

	$mode            = PeachPay_Square_Order_Data::get_payment( $order, 'mode' );
	$payment_details = peachpay_square_get_canonical_payment_details( $payment_id, $mode, 'payment.created' );
	if ( null === $payment_details ) {
		return false;
	}

	PeachPay_Square::calculate_payment_state( $order, $payment_details );

	peachpay_log( 'info', '[Square] webhook payment.created: refreshed payment state | ' . json_encode( array( 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'payment_id' => $payment_id ) ) );

	return true;
}

/**
 * Fetches canonical Square payment details from REST for webhook processing.
 *
 * @param string $payment_id Square payment id.
 * @param string $mode       Square mode from stored order payment details.
 * @param string $event_type Webhook event type for logging context.
 * @return array|null
 */
function peachpay_square_get_canonical_payment_details( $payment_id, $mode, $event_type ) {
	$resolved_mode = empty( $mode ) ? 'detect' : $mode;
	$payment       = PeachPay_Square::get_payment( $payment_id, $resolved_mode );

	if ( empty( $payment ) || ! is_array( $payment ) ) {
		peachpay_log( 'warning', '[Square] webhook ' . $event_type . ': failed to fetch canonical payment details for payment ' . $payment_id );
		return null;
	}

	$payment['mode'] = PeachPay_Square_Integration::mode( $resolved_mode );

	return $payment;
}

/**
 * Handles refund.created and refund.updated webhook events.
 *
 * @param array $data The event data object.
 * @return bool
 */
function peachpay_handle_square_refund_webhook( $data ) {
	$refund = isset( $data['object']['refund'] ) ? $data['object']['refund'] : null;
	if ( ! $refund || empty( $refund['payment_id'] ) ) {
		return true;
	}

	$payment_id = $refund['payment_id'];
	$order      = peachpay_find_order_by_square_payment( $payment_id );

	if ( ! $order ) {
		peachpay_log( 'warning', '[Square] webhook refund: No order found for payment ' . $payment_id );
		return false;
	}

	if ( ! PeachPay_Square_Integration::is_payment_gateway( $order->get_payment_method() ) ) {
		return true;
	}

	$mode = PeachPay_Square_Order_Data::get_payment( $order, 'mode' );

	try {
		$updated_details = PeachPay_Square::get_payment( $payment_id, $mode );
		if ( empty( $updated_details ) || ! is_array( $updated_details ) ) {
			return false;
		}
		$updated_details['mode'] = $mode;

		PeachPay_Square_Order_Data::set_payment_details( $order, $updated_details );

		// Only add order note if this refund was NOT initiated from WooCommerce.
		$refund_amount = isset( $refund['amount_money']['amount'] ) ? absint( $refund['amount_money']['amount'] ) : 0;
		$currency      = $order->get_currency();
		$refund_id_wh  = isset( $refund['id'] ) ? $refund['id'] : '';
		$is_wc_refund  = ! empty( $refund_id_wh ) && in_array( $refund_id_wh, PeachPay_Square_Order_Data::get_wc_initiated_refund_ids( $order ), true );

		peachpay_log( 'debug', '[Square] webhook refund: refund origin identified | ' . json_encode( array( 'refund_id' => $refund_id_wh, 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number(), 'is_wc_initiated' => $is_wc_refund ) ) );

		if ( ! $is_wc_refund && $refund_amount > 0 ) {
			$refund_display = wc_price(
				PeachPay_Square::display_amount( $refund_amount, $currency ),
				array( 'currency' => $currency )
			);
			// translators: %1$s Refund amount, %2$s Refund Id.
			$order->add_order_note(
				sprintf(
					__( 'Square refund received via webhook %1$s (Refund Id: %2$s)', 'peachpay-for-woocommerce' ),
					$refund_display,
					$refund_id_wh
				)
			);
			$order->save();
		}

		return true;
	} catch ( \Exception $e ) {
		peachpay_log( 'error', '[Square] webhook refund handler exception (payment ' . $payment_id . '): ' . $e->getMessage() );
		return false;
	}
}

/**
 * Handles dispute.created and dispute.state.updated webhook events.
 *
 * @param array  $data The event data object.
 * @param string $type The webhook event type.
 */
function peachpay_handle_square_dispute_webhook( $data, $type ) {
	$dispute = isset( $data['object']['dispute'] ) ? $data['object']['dispute'] : null;
	if ( ! $dispute ) {
		return;
	}

	$payment_id = isset( $dispute['disputed_payment']['payment_id'] ) ? $dispute['disputed_payment']['payment_id'] : '';
	if ( empty( $payment_id ) ) {
		return;
	}

	$order = peachpay_find_order_by_square_payment( $payment_id );
	if ( ! $order ) {
		peachpay_log( 'warning', '[Square] webhook dispute: No order found for payment ' . $payment_id );
		return;
	}

	if ( ! PeachPay_Square_Integration::is_payment_gateway( $order->get_payment_method() ) ) {
		return;
	}

	$dispute_id = isset( $dispute['id'] ) ? $dispute['id'] : '';
	$reason     = isset( $dispute['reason'] ) ? $dispute['reason'] : '';
	$state      = isset( $dispute['state'] ) ? $dispute['state'] : '';

	peachpay_log( 'warning', '[Square] webhook ' . $type . ': dispute ' . $dispute_id . ' (reason: ' . $reason . ', state: ' . $state . ', order #' . $order->get_id() . ')' );

	if ( 'dispute.created' === $type ) {
		// translators: %1$s Dispute ID, %2$s Dispute reason.
		$order->set_status(
			'on-hold',
			sprintf(
				__( 'Square dispute opened (Dispute Id: %1$s, Reason: %2$s). Respond to this dispute in your Square Dashboard.', 'peachpay-for-woocommerce' ),
				$dispute_id,
				$reason
			)
		);
		$order->save();
	} elseif ( 'dispute.state.updated' === $type ) {
		if ( 'WON' === $state ) {
			// translators: %s Dispute ID.
			$order->set_status(
				'processing',
				sprintf(
					__( 'Square dispute resolved in your favor (Dispute Id: %s).', 'peachpay-for-woocommerce' ),
					$dispute_id
				)
			);
			$order->save();
		} elseif ( 'LOST' === $state ) {
			// translators: %s Dispute ID.
			$order->set_status(
				'refunded',
				sprintf(
					__( 'Square dispute lost — funds returned to buyer (Dispute Id: %s).', 'peachpay-for-woocommerce' ),
					$dispute_id
				)
			);
			$order->save();
		} else {
			// Intermediate dispute states — add order note only, no status change.
			peachpay_log( 'warning', '[Square] webhook dispute.state.updated: intermediate state | ' . json_encode( array( 'state' => $state, 'dispute_id' => $dispute_id, 'order_id' => $order->get_id(), 'order_number' => $order->get_order_number() ) ) );
			$order->add_order_note(
				sprintf(
					// translators: %1$s Dispute state, %2$s Dispute ID.
					__( 'Square dispute state updated: %1$s (Dispute Id: %2$s).', 'peachpay-for-woocommerce' ),
					$state,
					$dispute_id
				)
			);
			$order->save();
		}
	}
}

/**
 * Finds a WooCommerce order by Square payment ID.
 *
 * @param string $payment_id The Square payment ID.
 * @return WC_Order|null The order or null if not found.
 */
function peachpay_find_order_by_square_payment( $payment_id ) {
	$orders = wc_get_orders(
		array(
			'limit'          => 1,
			'transaction_id' => $payment_id,
		)
	);

	if ( ! empty( $orders ) ) {
		return $orders[0];
	}

	$orders = wc_get_orders(
		array(
			'limit'        => 1,
			'meta_key'     => '_pp_square_payment_id',
			'meta_value'   => $payment_id,
			'meta_compare' => '=',
		)
	);

	if ( ! empty( $orders ) ) {
		return $orders[0];
	}

	return null;
}
