<?php
/**
 * HJCyberX Checkout for Paddle Lite Webhook Listener.
 *
 * Registers a WP REST API endpoint that receives webhooks from
 * Paddle Billing v2. Every inbound request is verified against
 * the HMAC-SHA256 signature before processing.
 *
 * Endpoint: POST /wp-json/paddlekonnect/v1/webhook
 *
 * @package HJCyberX Checkout for Paddle Lite
 */

defined( 'ABSPATH' ) || exit;

/**
 * Class PaddleKonnect_Lite_Webhook.
 */
class PaddleKonnect_Lite_Webhook {

	private const TIMESTAMP_TOLERANCE = 300;

	/**
	 * Function __construct.
	 */
	public function __construct() {
		add_action( 'rest_api_init', array( $this, 'register_route' ) );
	}

	/**
	 * Function register_route.
	 */
	public function register_route(): void {
		register_rest_route(
			'paddlekonnect/v1',
			'/webhook',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_webhook' ),
				'permission_callback' => '__return_true',
			)
		);
	}

	/**
	 * Function handle_webhook.
	 */
	public function handle_webhook( WP_REST_Request $request ): WP_REST_Response {
		$verification = $this->verify_signature( $request );
		if ( is_wp_error( $verification ) ) {
			PaddleKonnect_Lite_Logger::error( 'Webhook signature failed: ' . $verification->get_error_message() );
			return new WP_REST_Response( array( 'error' => $verification->get_error_message() ), 401 );
		}

		$body = json_decode( $request->get_body(), true );
		if ( empty( $body ) || ! isset( $body['event_type'] ) ) {
			PaddleKonnect_Lite_Logger::error( 'Webhook payload empty or missing event_type.' );
			return new WP_REST_Response( array( 'error' => 'Invalid payload.' ), 400 );
		}

		$event_type = sanitize_text_field( $body['event_type'] );
		$event_id   = sanitize_text_field( $body['event_id'] ?? '' );
		$data       = $body['data'] ?? array();

		PaddleKonnect_Lite_Logger::info( "Webhook received — Event: {$event_type} | ID: {$event_id}" );

		match ( $event_type ) {
			'transaction.completed'  => $this->handle_transaction_completed( $data, $event_id ),
			default                  => PaddleKonnect_Lite_Logger::debug( "Unhandled event: {$event_type}" ),
		};

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

	/**
	 * Verify the Paddle-Signature header using HMAC-SHA256.
	 *
	 * 1. Get Paddle-Signature header (ts=<timestamp>;h1=<signature>)
	 * 2. Build signed payload: "{ts}:{raw_body}"
	 * 3. Compute HMAC-SHA256 with webhook secret
	 * 4. Compare using hash_equals()
	 */
	private function verify_signature( WP_REST_Request $request ): true|WP_Error {
		$gateway = PaddleKonnect_Lite_Loader::get_gateway_instance();
		$secret  = $gateway ? trim( (string) $gateway->get_option( 'webhook_secret', '' ) ) : '';

		if ( empty( $secret ) ) {
			return new WP_Error( 'missing_secret', 'Webhook secret not configured.' );
		}

		$sig_header = $request->get_header( 'Paddle-Signature' );
		if ( empty( $sig_header ) ) {
			return new WP_Error( 'missing_header', 'Paddle-Signature header missing.' );
		}

		$ts = '';
		$h1 = '';
		foreach ( explode( ';', $sig_header ) as $part ) {
			$kv = explode( '=', $part, 2 );
			if ( count( $kv ) !== 2 ) {
				continue;
			}
			if ( 'ts' === $kv[0] ) {
				$ts = $kv[1];
			}
			if ( 'h1' === $kv[0] ) {
				$h1 = $kv[1];
			}
		}

		if ( empty( $ts ) || empty( $h1 ) ) {
			return new WP_Error( 'invalid_header', 'Cannot parse Paddle-Signature.' );
		}

		if ( absint( time() - intval( $ts ) ) > self::TIMESTAMP_TOLERANCE ) {
			return new WP_Error( 'expired', 'Webhook timestamp too old.' );
		}

		$raw_body = $request->get_body();
		$computed = hash_hmac( 'sha256', $ts . ':' . $raw_body, $secret );

		if ( ! hash_equals( $computed, $h1 ) ) {
			return new WP_Error( 'invalid_signature', 'Signature mismatch.' );
		}

		return true;
	}

	/**
	 * Function handle_transaction_completed.
	 */
	private function handle_transaction_completed( array $data, string $event_id ): void {
		$order = $this->find_order( $data, $event_id );
		if ( ! $order || $this->is_event_processed( $order, $event_id ) ) {
			return;
		}

		$txn_id = sanitize_text_field( $data['id'] ?? '' );
		if ( $txn_id ) {
			$order->update_meta_data( '_paddlekonnect_transaction_id', $txn_id );
		}

		$order->payment_complete( $txn_id );
		$order->add_order_note(
			sprintf(
			/* translators: 1: Transaction ID, 2: Event ID */
				__( 'HJCyberX Checkout for Paddle Lite: Payment completed. Txn: %1$s (Event: %2$s)', 'hjcyberx-checkout-for-paddle-lite' ),
				$txn_id,
				$event_id
			)
		);

		$this->mark_event_processed( $order, $event_id );
		$order->save();

		do_action( 'paddlekonnect_transaction_completed', $order, $data, $event_id );
		PaddleKonnect_Lite_Logger::info( "Order #{$order->get_id()} completed (txn: {$txn_id})." );
	}



	/**
	 * Function find_order.
	 */
	private function find_order( array $data, string $event_id ): ?WC_Order {
		if ( ! empty( $data['custom_data']['wc_order_id'] ) ) {
			$order = wc_get_order( absint( $data['custom_data']['wc_order_id'] ) );
			if ( $order ) {
				return $order;
			}
		}

		$txn_id = sanitize_text_field( $data['id'] ?? '' );
		if ( $txn_id ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
			$orders = wc_get_orders(
				array(
					// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
					'meta_key'   => '_paddlekonnect_transaction_id',
					// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
					'meta_value' => $txn_id,
					'limit'      => 1,
				)
			);
			if ( ! empty( $orders ) ) {
				return $orders[0];
			}
		}

		if ( ! empty( $data['transaction_id'] ) ) {
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
			$orders = wc_get_orders(
				array(
					// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
					'meta_key'   => '_paddlekonnect_transaction_id',
					// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
					'meta_value' => sanitize_text_field( $data['transaction_id'] ),
					'limit'      => 1,
				)
			);
			if ( ! empty( $orders ) ) {
				return $orders[0];
			}
		}

		PaddleKonnect_Lite_Logger::error( "No WC order found for event {$event_id}." );
		return null;
	}

	/**
	 * Function is_event_processed.
	 */
	private function is_event_processed( WC_Order $order, string $event_id ): bool {
		$processed = $order->get_meta( '_paddlekonnect_processed_events' );
		return is_array( $processed ) && in_array( $event_id, $processed, true );
	}

	/**
	 * Function mark_event_processed.
	 */
	private function mark_event_processed( WC_Order $order, string $event_id ): void {
		$processed = $order->get_meta( '_paddlekonnect_processed_events' );
		if ( ! is_array( $processed ) ) {
			$processed = array();
		}
		$processed[] = $event_id;
		$order->update_meta_data( '_paddlekonnect_processed_events', $processed );
	}
}
