<?php
/**
 * PostGate Stripe Return Handler Class
 *
 * Handles the return flow when users come back from Stripe Checkout.
 * Verifies checkout completion and creates orders when webhooks fail.
 *
 * This class implements Issue #84 solution - Return URL Verification strategy.
 * When users return from Stripe Checkout, we verify the session status via
 * Stripe API and create orders if webhooks haven't arrived yet.
 *
 * @package PostGate
 * @subpackage Payments
 * @since 1.0.0
 * @author QodeSphere
 * @license GPL-2.0+
 */

defined( 'ABSPATH' ) || exit;

/**
 * PostGate Stripe Return Handler
 *
 * This class provides a fallback mechanism for order creation when webhooks fail.
 * It verifies checkout session completion when users return to the site and creates
 * orders if they don't already exist (preventing duplication with webhook-created orders).
 *
 * Flow:
 * 1. User completes payment on Stripe Checkout
 * 2. Stripe redirects user back to site with session_id query parameter
 * 3. This handler intercepts the return
 * 4. Checks if order already exists (webhook may have created it)
 * 5. If no order exists, verifies session status via Stripe API
 * 6. Creates order if session is complete and paid
 * 7. Grants content access to user
 *
 * Key Features:
 * - Duplicate prevention via order existence check
 * - Transient-based session tracking for security
 * - Reuses existing order creation logic from webhooks
 * - Automatic access grant after order creation
 * - Error logging for debugging
 *
 * @since 1.0.0
 */
class PostGate_Stripe_Return_Handler {

	/**
	 * Transient prefix for pending session tracking.
	 *
	 * @since 1.0.0
	 */
	private const TRANSIENT_PREFIX = 'postgate_pending_session_';

	/**
	 * Transient expiration time (24 hours - matches Stripe session lifetime).
	 *
	 * @since 1.0.0
	 */
	private const TRANSIENT_EXPIRATION = DAY_IN_SECONDS;

	/**
	 * Initialize the return handler.
	 *
	 * Registers WordPress hooks for handling Stripe return flows.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public static function init(): void {
		add_action( 'template_redirect', array( __CLASS__, 'handle_stripe_return' ), 5 );
	}

	/**
	 * Track a pending checkout session for later verification.
	 *
	 * Called by PostGate_Stripe_Redirector after creating a Stripe Checkout session.
	 * Stores session metadata in a transient for verification when user returns.
	 *
	 * @since 1.0.0
	 *
	 * @param string $session_id Stripe Checkout Session ID.
	 * @param int    $user_id    WordPress user ID who initiated checkout.
	 * @param array  $metadata   Session metadata (same data stored in Stripe session).
	 *
	 * @return void
	 */
	public static function track_pending_session( string $session_id, int $user_id, array $metadata ): void {
		$pending_data = array(
			'user_id' => $user_id,
			'metadata' => $metadata,
			'created_at' => current_time( 'mysql' ),
		);

		// Store in transient for 24 hours (Stripe session expiration time)
		set_transient(
			self::TRANSIENT_PREFIX . $session_id,
			$pending_data,
			self::TRANSIENT_EXPIRATION
		);
	}

	/**
	 * Handle Stripe return redirect.
	 *
	 * Intercepts when users return from Stripe Checkout and verifies
	 * the session completion, creating orders if webhooks haven't arrived.
	 *
	 * This method runs on every template_redirect, so we do quick validations
	 * first to avoid unnecessary processing on most page loads.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public static function handle_stripe_return(): void {
		// Quick validation: Check if this is a Stripe return
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- This is a return URL from Stripe, not a form submission
		if ( ! isset( $_GET['session_id'] ) || ! is_user_logged_in() ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized on next line
		$session_id = sanitize_text_field( wp_unslash( $_GET['session_id'] ) );

		// Validate session_id format (Stripe sessions start with 'cs_')
		if ( ! str_starts_with( $session_id, 'cs_' ) ) {
			return;
		}

		// Get pending session data
		$pending_data = get_transient( self::TRANSIENT_PREFIX . $session_id );
		if ( ! $pending_data || ! is_array( $pending_data ) ) {
			return; // Not our session or already processed
		}

		// Verify user matches (security check)
		if ( (int) $pending_data['user_id'] !== get_current_user_id() ) {
			return; // Session belongs to different user
		}

		// Check if order already exists (webhook may have created it)
		$existing_order = PostGate_Orders_Repo::get_by_session_id( $session_id );
		if ( $existing_order ) {
			// Order already created by webhook - clean up transient and exit
			delete_transient( self::TRANSIENT_PREFIX . $session_id );
			return;
		}

		// Order doesn't exist - verify session with Stripe and create if needed
		self::verify_and_create_order( $session_id, $pending_data );
	}

	/**
	 * Verify Stripe session status and create order if completed.
	 *
	 * Queries Stripe API to get current session status and creates order
	 * if the session is complete and payment was successful.
	 *
	 * @since 1.0.0
	 *
	 * @param string $session_id   Stripe Checkout Session ID.
	 * @param array  $pending_data Pending session data from transient.
	 *
	 * @return void
	 */
	private static function verify_and_create_order( string $session_id, array $pending_data ): void {
		try {
			// Set Stripe API key
			$stripe_token = PostGate_Merchant_Auth::token();
			if ( empty( $stripe_token ) ) {
				PostGate_Logger::warning( 'Return handler: No Stripe token available', [ 'session_id' => $session_id ] );
				return;
			}

			\Stripe\Stripe::setApiKey( $stripe_token );

			// Retrieve session from Stripe
			$session = \Stripe\Checkout\Session::retrieve( $session_id );

			// Verify session is complete and paid
			if ( $session->status !== 'complete' || $session->payment_status !== 'paid' ) {
				// Session not completed yet or payment failed
				return;
			}

			// Double-check order doesn't exist (race condition protection)
			$existing_order = PostGate_Orders_Repo::get_by_session_id( $session_id );
			if ( $existing_order ) {
				delete_transient( self::TRANSIENT_PREFIX . $session_id );
				return;
			}

			// Create order from completed session
			self::create_order_from_completed_session( $session, $pending_data );

			// Clean up transient
			delete_transient( self::TRANSIENT_PREFIX . $session_id );

		} catch (\Stripe\Exception\ApiErrorException $e) {
			// Stripe API error - log and continue
			PostGate_Logger::error( 'Return handler Stripe API error', [
				'error' => $e->getMessage(),
				'session_id' => $session_id,
			] );
		} catch (\Throwable $e) {
			// Unexpected error - log and continue
			PostGate_Logger::error( 'Return handler unexpected error', [
				'error' => $e->getMessage(),
				'session_id' => $session_id,
			] );
		}
	}

	/**
	 * Create order from completed Stripe session.
	 *
	 * Uses the same order creation logic as webhooks, but marks the order
	 * as created via 'return_url' for tracking purposes.
	 *
	 * @since 1.0.0
	 *
	 * @param object $session      Stripe Checkout Session object.
	 * @param array  $pending_data Pending session data with metadata.
	 *
	 * @return void
	 */
	private static function create_order_from_completed_session( object $session, array $pending_data ): void {
		// Create order from session metadata using existing redirector logic
		$order_id = PostGate_Stripe_Redirector::create_order_from_metadata(
			$session->metadata,
			$session->id
		);

		if ( $order_id <= 0 ) {
			PostGate_Logger::error( 'Return handler: Failed to create order from metadata', [
				'session_id' => $session->id,
			] );
			return;
		}

		// Update order to mark it as created via return_url
		global $wpdb;
		$orders_table = $wpdb->prefix . 'postgate_orders';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$wpdb->update(
			$orders_table,
			array( 'created_via' => 'return_url' ),
			array( 'id' => $order_id ),
			array( '%s' ),
			array( '%d' )
		);

		// Mark order as paid using session ID
		$payment_intent_id = ! empty( $session->payment_intent ) ? (string) $session->payment_intent : null;
		$subscription_id = ! empty( $session->subscription ) ? (string) $session->subscription : null;
		$customer_id = ! empty( $session->customer ) ? (string) $session->customer : null;

		PostGate_Orders_Repo::mark_paid_by_session_or_intent(
			$session->id,
			$payment_intent_id,
			$subscription_id,
			$customer_id
		);

		// Grant content access
		$order = PostGate_Orders_Repo::get_by_id( $order_id );
		if ( $order ) {
			PostGate_Access_Manager::grant_access_from_order( $order );

			// Log successful order creation
			PostGate_Logger::info( 'Order created via return_url', [
				'order_id' => $order_id,
				'user_id' => $order['user_id'],
				'session_id' => $session->id,
			] );
		}
	}
}
