<?php
/**
 * Checkout handler class
 * Manages Stripe Checkout sessions and payment flows
 */

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class STRP_SUB_Checkout_Handler {
    
    /**
     * Single instance
     */
    private static $instance = null;
    
    /**
     * Get instance
     */
    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }
    
    /**
     * Constructor
     */
    private function __construct() {
        // AJAX handler for creating checkout session
        add_action( 'wp_ajax_strp_sub_create_checkout_session', array( $this, 'ajax_create_checkout_session' ) );
        // Intentional: supports guest checkout. Protected by nonce + rate limiting.
        add_action( 'wp_ajax_nopriv_strp_sub_create_checkout_session', array( $this, 'ajax_create_checkout_session' ) );

        // Handle checkout success callback
        add_action( 'template_redirect', array( $this, 'handle_checkout_success' ) );

        // Enqueue Stripe.js
        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_stripe_js' ) );
    }

    private function __clone() {}

    public function __wakeup() {
        throw new \RuntimeException( 'Cannot unserialize singleton.' );
    }

    /**
     * Normalize and validate affiliate referral codes.
     *
     * A valid code must exist in user meta and belong to an active affiliate.
     *
     * @param string $affiliate_ref Raw affiliate code candidate.
     * @return string|null Uppercased code when valid, otherwise null.
     */
    private function normalize_valid_affiliate_ref( $affiliate_ref ) {
        $affiliate_ref = strtoupper( trim( (string) $affiliate_ref ) );
        if ( '' === $affiliate_ref ) {
            return null;
        }

        $matches = get_users(
            array(
                'meta_key'   => '_strp_sub_affiliate_code',
                'meta_value' => $affiliate_ref,
                'number'     => 1,
                'fields'     => 'ids',
            )
        );

        if ( empty( $matches ) ) {
            return null;
        }

        $affiliate_user_id = (int) $matches[0];
        if ( $affiliate_user_id <= 0 ) {
            return null;
        }

        if ( 'yes' === get_user_meta( $affiliate_user_id, '_strp_sub_affiliate_disabled', true ) ) {
            return null;
        }

        return $affiliate_ref;
    }

    /**
     * Enqueue Stripe.js
     */
    public function enqueue_stripe_js() {
        if ( is_product() ) {
            wp_enqueue_script( 'stripe-js', 'https://js.stripe.com/v3/', array(), null, true );
        }
    }
    
    /**
     * Handle the AJAX request that creates a Stripe Checkout session.
     */
    public function ajax_create_checkout_session() {
        check_ajax_referer( 'strp_sub_nonce', 'nonce' );

        // RATE LIMITING: Only apply to guest users to prevent bot abuse
        // Logged-in users are protected by smarter concurrent checkout validation (lines 172-207)
        // Best-effort defense. For production abuse prevention, use server-level rate limiting (Cloudflare, nginx).
        if ( ! is_user_logged_in() ) {
            $user_identifier = $this->get_client_ip();
            $rate_limit_key = 'strp_sub_checkout_limit_' . md5( $user_identifier );

            if ( get_transient( $rate_limit_key ) ) {
                STRP_SUB_Logger::log( 'Rate limit triggered for guest identifier: ' . substr( hash( 'sha256', $user_identifier ), 0, 12 ) );
                wp_send_json_error( array(
                    'message' => __( 'You are trying to start a new payment too quickly. Please wait a moment and try again.', 'subscription-manager-for-stripe' )
                ) );
                return;
            }

            // Set rate limit (10 seconds cooldown for guests only)
            set_transient( $rate_limit_key, true, 10 );
        }

        $product_id        = isset( $_POST['product_id'] ) ? intval( wp_unslash( $_POST['product_id'] ) ) : 0;
        $price_id          = isset( $_POST['price_id'] ) ? sanitize_text_field( wp_unslash( $_POST['price_id'] ) ) : '';
        $subscription_type = isset( $_POST['subscription_type'] ) ? sanitize_text_field( wp_unslash( $_POST['subscription_type'] ) ) : '';
        
        // Validation: Verify standard payload exists
        if ( empty( $product_id ) || empty( $subscription_type ) ) {
            wp_send_json_error( array( 'message' => __( 'Invalid request parameters.', 'subscription-manager-for-stripe' ) ) );
            return;
        }

        // Strict Validation: Restrict to expected types to prevent arbitrary payloads
        $allowed_types = array( 'monthly', 'annual' );
        if ( function_exists( 'strp_sub_fs' ) && strp_sub_fs() && strp_sub_fs()->is__premium_only() ) {
            $allowed_types[] = 'lifetime';
        }
        if ( ! in_array( $subscription_type, $allowed_types, true ) ) {
            wp_send_json_error( array( 'message' => __( 'Invalid subscription type parameter.', 'subscription-manager-for-stripe' ) ) );
            return;
        }

        // Validate price_id matches the stored price for this product
        if ( ! empty( $price_id ) && $subscription_type !== 'lifetime' ) {
            $expected_price_id = STRP_SUB_Product_Handler::get_stripe_price_id( $product_id );
            if ( ! empty( $expected_price_id ) && $price_id !== $expected_price_id ) {
                wp_send_json_error( array( 'message' => __( 'Invalid price configuration.', 'subscription-manager-for-stripe' ) ) );
                return;
            }
        }

        // Initialize metadata array early to prevent data loss during upgrade flow
        $metadata = array(
            'wc_product_id'     => (string) $product_id,
            'subscription_type' => $subscription_type,
        );

        $stripe_customer_id = '';

        if ( is_user_logged_in() ) {
            $customer_id = get_current_user_id();

            // Check if user's Stripe subscription is in a trial period (Stripe's native billing
            // trial feature, not plugin trialware). Trial users should be allowed to upgrade.
            $subscription_status = get_user_meta( $customer_id, '_strp_sub_subscription_status', true );
            $is_trial = ( $subscription_status === 'trial' );

            STRP_SUB_Logger::log( sprintf(
                'Checkout validation for user #%d: status=%s, is_trial=%s',
                $customer_id,
                $subscription_status ?: 'none',
                $is_trial ? 'yes' : 'no'
            ) );

            // Check if user has actual lifetime access (not trial)
            $has_lifetime_meta = get_user_meta( $customer_id, '_strp_sub_has_lifetime_access', true ) === 'yes';

            STRP_SUB_Logger::log( sprintf(
                'Lifetime check for user #%d: has_lifetime_meta=%s, is_trial=%s, will_block=%s',
                $customer_id,
                $has_lifetime_meta ? 'yes' : 'no',
                $is_trial ? 'yes' : 'no',
                (!$is_trial && $has_lifetime_meta) ? 'YES' : 'NO'
            ) );

            // Only block if user has lifetime access AND is not on a trial
            if ( !$is_trial && $has_lifetime_meta ) {
                STRP_SUB_Logger::log( '[BLOCKED] User already has lifetime access (not trial). User ID ' . $customer_id );
                wp_send_json_error( array(
                    'message' => __( 'You already have lifetime access. No additional purchase is necessary.', 'subscription-manager-for-stripe' ),
                ) );
                return;
            }

            STRP_SUB_Logger::log( '[PASSED] Lifetime check for user #' . $customer_id );

            $stripe_customer_id = get_user_meta( $customer_id, '_strp_sub_stripe_customer_id', true );

            if ( ! empty( $stripe_customer_id ) ) {
                $stripe_api    = STRP_SUB_Stripe_API::get_instance();
                $subscriptions = $stripe_api->list_customer_subscriptions( $stripe_customer_id, 'active' );

                $has_active_subscription    = false;
                $current_subscription_type = null;

                foreach ( $subscriptions as $subscription ) {
                    if ( in_array( $subscription->status, array( 'active', 'trialing' ), true ) ) {
                        $has_active_subscription = true;
                        if ( isset( $subscription->metadata->subscription_type ) ) {
                            $current_subscription_type = $subscription->metadata->subscription_type;
                        }
                        break;
                    }
                }

                if ( $has_active_subscription ) {
                    $is_upgrade = false;
                    $active_subscription = null;

                    // Find the active subscription object
                    foreach ( $subscriptions as $subscription ) {
                        if ( in_array( $subscription->status, array( 'active', 'trialing' ), true ) ) {
                            $active_subscription = $subscription;
                            break;
                        }
                    }

                    $current_wc_product_id = isset( $active_subscription->metadata->wc_product_id ) ? intval( $active_subscription->metadata->wc_product_id ) : 0;
                    
                    if ( $current_wc_product_id > 0 ) {
                        $current_tier_weight = STRP_SUB_Product_Handler::get_tier_weight( $current_wc_product_id );
                        $new_tier_weight = STRP_SUB_Product_Handler::get_tier_weight( $product_id );

                        // Allow upgrading to a higher tier, or laterally within the same tier (e.g. billing engine refactor migration)
                        if ( $new_tier_weight >= $current_tier_weight ) {
                            $is_upgrade = true;
                        }
                    } else {
                        // Fallback to legacy string checking if older subscriptions don't have wc_product_id metadata
                        if ( 'monthly' === $current_subscription_type && in_array( $subscription_type, array( 'annual', 'lifetime' ), true ) ) {
                            $is_upgrade = true;
                        } elseif ( 'annual' === $current_subscription_type && 'lifetime' === $subscription_type ) {
                            $is_upgrade = true;
                        }
                    }

                    if ( ! $is_upgrade ) {
                        STRP_SUB_Logger::log( sprintf( 'Blocked checkout attempt: user %d already has an active %s subscription.', $customer_id, $current_subscription_type ?: 'unknown' ) );
                        wp_send_json_error( array(
                            'message' => __( 'You already have an active subscription. Please manage your existing subscription or contact support.', 'subscription-manager-for-stripe' ),
                        ) );
                        return;
                    }

                    STRP_SUB_Logger::log( sprintf( 'Allowing subscription upgrade for user %d from %s to %s.', $customer_id, $current_subscription_type ?: 'unknown', $subscription_type ) );

                    // CONCURRENT CHECKOUT PROTECTION: Check for active checkout session
                    // Note: Transient-based locking is not atomic. For stronger guarantees, use server-level rate limiting.
                    $active_session_key = 'strp_sub_active_checkout_' . $customer_id;
                    $active_session_data = get_transient( $active_session_key );

                    if ( $active_session_data ) {
                        // Time-based check: if session is older than 3 minutes, allow creating a new one
                        // This allows users to retry after closing the modal while protecting against double-clicks
                        $session_age_seconds = time() - ( isset( $active_session_data['created_at'] ) ? $active_session_data['created_at'] : 0 );
                        $max_session_age = 180; // 3 minutes

                        if ( $session_age_seconds > $max_session_age ) {
                            STRP_SUB_Logger::log( sprintf( 'Previous checkout session is %d seconds old (>%d), allowing new session.', $session_age_seconds, $max_session_age ) );
                            delete_transient( $active_session_key );
                        } else {
                            // Smart check: verify the Stripe session is actually still open
                            $session_still_active = false;

                            if ( isset( $active_session_data['session_id'] ) ) {
                                try {
                                    $existing_session = \STRP_SUB_Vendor\Stripe\Checkout\Session::retrieve( $active_session_data['session_id'] );

                                    if ( $existing_session->status === 'open' ) {
                                        $session_still_active = true;
                                        STRP_SUB_Logger::log( 'User #' . $customer_id . ' has an active Stripe checkout session: ' . $existing_session->id );
                                    } else {
                                        STRP_SUB_Logger::log( 'Previous checkout session ' . $existing_session->id . ' is ' . $existing_session->status . ', clearing lock.' );
                                        delete_transient( $active_session_key );
                                    }
                                } catch ( \STRP_SUB_Vendor\Stripe\Exception\InvalidRequestException $e ) {
                                    // Session not found in Stripe, clear the lock
                                    STRP_SUB_Logger::log( 'Previous checkout session not found in Stripe, clearing lock.' );
                                    delete_transient( $active_session_key );
                                } catch ( Exception $e ) {
                                    // Other errors, log but clear lock to avoid blocking user
                                    STRP_SUB_Logger::error( 'Error checking previous session: ' . $e->getMessage() . ', clearing lock.' );
                                    delete_transient( $active_session_key );
                                }
                            }

                            if ( $session_still_active ) {
                                wp_send_json_error( array(
                                    'message' => __( 'You already have an active pending payment session. Please complete it or wait for it to expire before starting a new one.', 'subscription-manager-for-stripe' )
                                ) );
                                return;
                            }
                        }
                    }

                    // Store old subscription ID in metadata for cancellation after upgrade
                    if ( $active_subscription ) {
                        $metadata['old_subscription_id'] = (string) $active_subscription->id;
                        STRP_SUB_Logger::log( 'Marked subscription ' . $active_subscription->id . ' for cancellation after upgrade.' );
                    }
                }
            }
        }

        $coupon_id_to_apply = null;

        // ── Priority 1: One-time discount code (deliberate admin action) ──
        // Checked first because it's a targeted offer for a specific client.
        // If present and valid, skip the affiliate logic entirely.
        $discount_token = isset( $_POST['discount_token'] ) ? sanitize_text_field( wp_unslash( $_POST['discount_token'] ) ) : '';
        $is_eligible_for_discount = false;
        
        if ( ! empty( $discount_token ) ) {
            $discount_coupon_id = apply_filters( 'strp_sub_get_discount_coupon_for_token', null, $discount_token, $product_id );
            if ( $discount_coupon_id ) {
                // Check if it's a 100% discount
                $coupon_object = STRP_SUB_Stripe_API::get_instance()->retrieve_coupon( $discount_coupon_id );
                if ( $coupon_object && isset( $coupon_object->percent_off ) && (int) $coupon_object->percent_off === 100 ) {
                    // Require authentication for free (100% discount) orders
                    if ( ! is_user_logged_in() ) {
                        wp_send_json_error( array( 'message' => __( 'You must be logged in to use this discount.', 'subscription-manager-for-stripe' ) ) );
                        return;
                    }

                    // Add discount token to metadata so order creation can mark it used
                    $metadata['discount_token'] = $discount_token;

                    // Mark token as pending so it isn't used again
                    do_action( 'strp_sub_mark_discount_code_pending', $discount_token, $customer_id ?? 0 );

                    STRP_SUB_Logger::log( 'Applied 100% discount for product ' . $product_id . ' using token ' . $discount_token );

                    // Create the order inside the nonce-protected AJAX handler
                    // so unauthenticated users cannot forge a free checkout URL.
                    $session_data = (object) array(
                        'id'                 => 'free_checkout_' . wp_generate_uuid4(),
                        'customer'           => $stripe_customer_id ?? '',
                        'mode'               => 'subscription',
                        'metadata'           => (object) $metadata,
                        'subscription'       => null,
                        'payment_status'     => 'no_payment_required',
                    );
                    $order = self::create_order_from_session( $session_data, null );
                    if ( $order ) {
                        if ( ! empty( $discount_token ) ) {
                            do_action( 'strp_sub_mark_discount_code_redeemed', $discount_token, $order->get_id() );
                        }
                        do_action( 'strp_sub_order_created_from_session', $order->get_id(), $session_data );
                        wp_send_json_success( array(
                            'session_id' => 'free_checkout',
                            'url'        => add_query_arg( 'strp_sub_thankyou', $order->get_id(), home_url( '/' ) ),
                        ) );
                    }
                    wp_send_json_error( array( 'message' => __( 'Could not create order.', 'subscription-manager-for-stripe' ) ) );
                    return;
                }
                $coupon_id_to_apply = $discount_coupon_id;
                STRP_SUB_Logger::log( sprintf( 'Applying one-time discount coupon %s for token %s (product %d).', $discount_coupon_id, $discount_token, $product_id ) );
            } else {
                STRP_SUB_Logger::log( 'Discount token ' . $discount_token . ' has no valid coupon for product ' . $product_id . '.' );
                $discount_token = null;
            }
        }

        // ── Priority 2: Affiliate discount (passive referral cookie) ──
        // Only evaluated when no one-time discount is active.
        $affiliate_ref = null;
        if ( ! $coupon_id_to_apply ) {
            if ( isset( $_POST['affiliate_ref'] ) && ! empty( $_POST['affiliate_ref'] ) ) {
                $affiliate_ref = sanitize_text_field( wp_unslash( $_POST['affiliate_ref'] ) );
            } elseif ( isset( $_COOKIE['strp_sub_affiliate_ref'] ) ) {
                $affiliate_ref = sanitize_text_field( wp_unslash( $_COOKIE['strp_sub_affiliate_ref'] ) );
            }

            if ( $affiliate_ref ) {
                $affiliate_ref = $this->normalize_valid_affiliate_ref( $affiliate_ref );

                if ( ! $affiliate_ref ) {
                    STRP_SUB_Logger::log( 'Ignored invalid or disabled affiliate referral code during checkout.' );
                } else {
                    $is_eligible_for_discount = true;

                    if ( is_user_logged_in() ) {
                        $customer_id = get_current_user_id();

                        $previous_paid_orders = wc_get_orders( array(
                            'customer_id' => $customer_id,
                            'limit'       => 1,
                            'meta_query'  => array(
                                array(
                                    'key'   => '_affiliate_payment_status',
                                    'value' => 'paid',
                                ),
                            ),
                            'return'      => 'ids',
                        ) );

                        if ( ! empty( $previous_paid_orders ) ) {
                            $is_eligible_for_discount = false;
                            STRP_SUB_Logger::log( sprintf( 'Affiliate code %s ignored: user %d already received affiliate discount on paid order #%d.', $affiliate_ref, $customer_id, $previous_paid_orders[0] ) );
                        } else {
                            STRP_SUB_Logger::log( sprintf( 'User %d eligible for affiliate discount - first paid purchase with code %s.', $customer_id, $affiliate_ref ) );
                        }
                    } else {
                        STRP_SUB_Logger::debug( 'Affiliate discount eligibility: guest user, eligible.' );
                    }

                    if ( $is_eligible_for_discount ) {
                        $monthly_id  = STRP_SUB_Settings::get( 'monthly_product_id', 0 );
                        $annual_id   = STRP_SUB_Settings::get( 'annual_product_id', 0 );
                        $lifetime_id = STRP_SUB_Settings::get( 'lifetime_product_id', 0 );
                        $discount_percent = 0;

                        if ( $product_id === intval( $monthly_id ) ) {
                            $discount_percent = intval( get_option( 'strp_sub_affiliate_discount_monthly', 10 ) );
                        } elseif ( $product_id === intval( $annual_id ) ) {
                            $discount_percent = intval( get_option( 'strp_sub_affiliate_discount_annual', 10 ) );
                        } elseif ( $product_id === intval( $lifetime_id ) ) {
                            $discount_percent = intval( get_option( 'strp_sub_affiliate_discount_lifetime', 15 ) );
                        }

                        if ( $discount_percent > 0 ) {
                            $coupon_id_to_apply = STRP_SUB_Affiliate_Coupon_Helper::get_coupon_id_for_discount( $discount_percent );
                            STRP_SUB_Logger::log( sprintf( 'Applying affiliate coupon %s (%d%%) for ref %s.', $coupon_id_to_apply, $discount_percent, $affiliate_ref ) );
                        }
                    }
                }
            }
        }

        $user_id    = get_current_user_id();
        $stripe_api = STRP_SUB_Stripe_API::get_instance();
        $customer   = null;

        if ( $user_id > 0 ) {
            $customer = $stripe_api->get_or_create_customer( $user_id );
            if ( ! $customer ) {
                wp_send_json_error( array( 'message' => __( 'Failed to create customer.', 'subscription-manager-for-stripe' ) ) );
                return;
            }
        }

        $success_url = add_query_arg(
            array(
                'strp_sub_success' => '1',
                'session_id'    => '{CHECKOUT_SESSION_ID}',
                'product_id'    => $product_id,
                '_wpnonce'      => wp_create_nonce( 'strp_sub_checkout_success' ),
            ),
            home_url( '/' )
        );
        $cancel_url = apply_filters( 'strp_sub_checkout_cancel_url', wc_get_page_permalink( 'shop' ) ?: home_url( '/' ) );

        // Add user_id to metadata if logged in
        if ( $user_id > 0 ) {
            $metadata['wc_user_id'] = (string) $user_id;
            STRP_SUB_Logger::debug( 'Adding wc_user_id to checkout metadata: ' . $user_id );
        }

        if ( ! empty( $affiliate_ref ) ) {
            $metadata['affiliate_ref'] = $affiliate_ref;
        }

        if ( ! empty( $discount_token ) ) {
            $metadata['discount_token'] = $discount_token;
        }

        STRP_SUB_Logger::debug( 'Checkout metadata payload: ' . wp_json_encode( $metadata ) );

        if ( 'lifetime' === $subscription_type && function_exists( 'strp_sub_fs' ) && strp_sub_fs() && strp_sub_fs()->is__premium_only() ) {
            if ( class_exists( 'STRP_SUB_Freemius' ) && STRP_SUB_Freemius::has_feature( 'starter' ) ) {
                $session = $stripe_api->create_checkout_session_onetime(
                    $customer ? $customer->id : null,
                    $price_id,
                    $success_url,
                    $cancel_url,
                    $metadata,
                    $coupon_id_to_apply
                );
            } else {
                wp_send_json_error( array( 'message' => __( 'Lifetime purchases require a Starter plan or higher.', 'subscription-manager-for-stripe' ) ) );
                return;
            }
        } else {
            // SECURITY FIX: Check if user has already used their trial
            $include_trial = true;
            if ( $user_id > 0 ) {
                $has_used_trial = get_user_meta( $user_id, '_strp_sub_has_used_trial', true );
                $include_trial = ( $has_used_trial !== 'yes' );

                if ( ! $include_trial ) {
                    STRP_SUB_Logger::log( 'User #' . $user_id . ' has already used trial - creating subscription without trial period.' );
                }

                // Trial fraud prevention (Starter+): Check if user has been flagged for duplicate card usage
                if ( function_exists( 'strp_sub_fs' ) && strp_sub_fs() && strp_sub_fs()->is__premium_only() ) {
                    $fraud_flag = get_user_meta( $user_id, '_strp_sub_fraud_flag_duplicate_card', true );
                    if ( $fraud_flag === 'yes' && $include_trial ) {
                        STRP_SUB_Logger::log( 'User #' . $user_id . ' was flagged for using duplicate card - blocking trial.' );
                        $include_trial = false;
                    }
                }
            }

            $session = $stripe_api->create_checkout_session(
                $customer ? $customer->id : null,
                $price_id,
                $success_url,
                $cancel_url,
                $metadata,
                $coupon_id_to_apply,
                $include_trial
            );
        }

        if ( ! $session ) {
            STRP_SUB_Logger::error( 'Checkout logic triggered but Stripe API returned a boolean false payload on session creation.' );
            wp_send_json_error( array( 'message' => __( 'Unable to create payment session. Please try again later or contact support.', 'subscription-manager-for-stripe' ) ) );
            return;
        }

        // CONCURRENT CHECKOUT PROTECTION: Track active checkout session (expires in 10 minutes)
        if ( $user_id > 0 ) {
            $active_session_key = 'strp_sub_active_checkout_' . $user_id;
            set_transient( $active_session_key, array(
                'session_id' => $session->id,
                'created_at' => time(),
                'subscription_type' => $subscription_type,
            ), 600 );
            STRP_SUB_Logger::log( 'Tracking active checkout session for user #' . $user_id . ': ' . $session->id );
        }

        wp_send_json_success(
            array(
                'session_id' => $session->id,
                'url'        => $session->url,
            )
        );
    }
    
    /**
     * Handle the return from Stripe Checkout.
     */
    public function handle_checkout_success() {
        if ( ! isset( $_GET['strp_sub_success'] ) || sanitize_text_field( wp_unslash( $_GET['strp_sub_success'] ) ) !== '1' ) {
            return;
        }

        // Verify nonce to confirm this redirect originated from our checkout.
        if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ), 'strp_sub_checkout_success' ) ) {
            return;
        }

        $session_id = isset( $_GET['session_id'] ) ? sanitize_text_field( wp_unslash( $_GET['session_id'] ) ) : '';
        $product_id = isset( $_GET['product_id'] ) ? intval( wp_unslash( $_GET['product_id'] ) ) : 0;

        if ( empty( $session_id ) || $product_id <= 0 ) {
            return;
        }

        // Validate Stripe session ID format (cs_test_* or cs_live_* or cs_*).
        if ( $session_id !== 'free_checkout' && ! preg_match( '/^cs_(test_|live_)?[a-zA-Z0-9]+$/', $session_id ) ) {
            return;
        }

        if ( $session_id === 'free_checkout' ) {
            // Free orders are now created in the AJAX handler. This is only for the redirect.
            return;
        }
        
        // Check if order exists for this session
        $orders = wc_get_orders( array(
            'limit'      => 1,
            'meta_query' => array( array(
                'key'   => '_strp_sub_stripe_session_id',
                'value' => $session_id,
            ) ),
        ) );
        
        if ( ! empty( $orders ) ) {
            $order = $orders[0];

            // Ownership verification: ensure the current user matches the order customer
            if ( is_user_logged_in() ) {
                $order_customer_id = $order->get_customer_id();
                if ( $order_customer_id > 0 && $order_customer_id !== get_current_user_id() ) {
                    wp_safe_redirect( wc_get_checkout_url() );
                    exit;
                }
            }

            // Redirect to the order received page
            wp_safe_redirect( $order->get_checkout_order_received_url() );
            exit;
        }
        
        // Order not created yet by webhook - show "check email" page
        // This works for BOTH subscriptions AND lifetime purchases
        wp_safe_redirect( add_query_arg( 'session_id', $session_id, wc_get_checkout_url() ) );
        exit;
    }

    /**
     * Create a WooCommerce order from the Stripe checkout session payload.
     *
     * @param \STRP_SUB_Vendor\Stripe\Checkout\Session     $session       The completed checkout session.
     * @param \STRP_SUB_Vendor\Stripe\Subscription|null $subscription Optional subscription created by Stripe.
     *
     * @return WC_Order|false
     */
    public static function create_order_from_session( $session, $subscription = null ) {
        if ( ! in_array( $session->payment_status, array( 'paid', 'no_payment_required' ), true ) ) {
            STRP_SUB_Logger::error( 'Session ' . $session->id . ' payment_status is ' . $session->payment_status . ', skipping order creation.' );
            return false;
        }

        $product_id = isset( $session->metadata->wc_product_id ) ? intval( $session->metadata->wc_product_id ) : 0;
        $user_id    = isset( $session->metadata->wc_user_id ) ? intval( $session->metadata->wc_user_id ) : 0;

        if ( $product_id <= 0 ) {
            STRP_SUB_Logger::error( 'Invalid product ID in session metadata.' );
            return false;
        }

        $order = wc_create_order( array( 'customer_id' => $user_id ) );

        if ( is_wp_error( $order ) ) {
            STRP_SUB_Logger::error( 'Failed to create order: ' . $order->get_error_message() );
            return false;
        }

        $product = wc_get_product( $product_id );
        if ( ! $product ) {
            STRP_SUB_Logger::error( 'Product not found: ' . $product_id );
            return false;
        }

        $order->add_product( $product, 1 );

        // Handle discount application for both subscription and payment modes
        $discount_amount = 0;
        $discount_source = null;

        // For subscription mode (monthly/annual with trial): check subscription discount
        if ( $subscription && isset( $subscription->discount ) && ! empty( $subscription->discount ) ) {
            $coupon = $subscription->discount->coupon;
            $product_price = floatval( $product->get_price() );

            if ( isset( $coupon->percent_off ) && $coupon->percent_off > 0 ) {
                $discount_amount = ( $product_price * floatval( $coupon->percent_off ) ) / 100.0;
                $discount_source = sprintf( 'subscription coupon %s (%s%%)', $coupon->id, $coupon->percent_off );
                STRP_SUB_Logger::log( sprintf( 'Calculating discount from subscription: %s%% of %s = %s', $coupon->percent_off, wc_price( $product_price ), wc_price( $discount_amount ) ) );
            } elseif ( isset( $coupon->amount_off ) && $coupon->amount_off > 0 ) {
                $discount_amount = floatval( $coupon->amount_off ) / 100.0; // Convert cents to currency
                $discount_source = sprintf( 'subscription coupon %s (%s)', $coupon->id, wc_price( $discount_amount ) );
                STRP_SUB_Logger::log( sprintf( 'Calculating discount from subscription: fixed amount %s', wc_price( $discount_amount ) ) );
            }
        }
        // For payment mode (lifetime): check session total_details
        elseif ( isset( $session->total_details->amount_discount ) && $session->total_details->amount_discount > 0 ) {
            $discount_amount = floatval( $session->total_details->amount_discount ) / 100.0;
            $discount_source = sprintf( 'session total_details (%s)', wc_price( $discount_amount ) );
            STRP_SUB_Logger::log( sprintf( 'Calculating discount from session.total_details: %s', wc_price( $discount_amount ) ) );
        }

        // Apply the discount if found
        if ( $discount_amount > 0 ) {
            $fee = new WC_Order_Item_Fee();
            $fee->set_name( __( 'Affiliate Discount', 'subscription-manager-for-stripe' ) );
            $fee->set_amount( -$discount_amount );
            $fee->set_tax_status( 'none' );
            $fee->set_total( -$discount_amount );

            $order->add_item( $fee );
            STRP_SUB_Logger::log( sprintf( 'Applied discount of %s to order #%d from %s.', wc_price( $discount_amount ), $order->get_id(), $discount_source ) );
        } else {
            STRP_SUB_Logger::log( sprintf( 'No discount found for order #%d (subscription: %s, session.total_details: %s)',
                $order->get_id(),
                ( $subscription && isset( $subscription->discount ) ) ? 'checked' : 'none',
                isset( $session->total_details->amount_discount ) ? $session->total_details->amount_discount : 'none'
            ) );
        }

        $order->update_meta_data( '_strp_sub_stripe_session_id', $session->id );
        $order->update_meta_data( '_strp_sub_stripe_customer_id', $session->customer );

        // Store payment intent for refund/dispute lookup via find_order_by_charge().
        if ( ! empty( $session->payment_intent ) ) {
            $order->update_meta_data( '_stripe_payment_intent_id', $session->payment_intent );
        }

        if ( isset( $session->metadata->affiliate_ref ) && ! empty( $session->metadata->affiliate_ref ) ) {
            $order->update_meta_data( '_affiliate_ref', sanitize_text_field( $session->metadata->affiliate_ref ) );

            // NOTE: Preliminary payment status for affiliate tracking (safety net).
            // Re-evaluated by the affiliate commission handler when the
            // strp_sub_order_created_from_session action fires below.
            $payment_status = 'paid'; // Default: paid order

            // If it's a subscription with trial, mark as 'trial' (not paid yet)
            if ( $subscription && isset( $subscription->status ) && $subscription->status === 'trialing' ) {
                $payment_status = 'trial';
            }

            $order->update_meta_data( '_affiliate_payment_status', $payment_status );
            STRP_SUB_Logger::log( sprintf( 'Stored affiliate_ref %s on order #%d with preliminary payment status: %s.', $session->metadata->affiliate_ref, $order->get_id(), $payment_status ) );
        }

        if ( $subscription ) {
            $order->update_meta_data( '_strp_sub_stripe_subscription_id', $subscription->id );
            $order->update_meta_data( '_strp_sub_subscription_status', $subscription->status );

            if ( isset( $subscription->current_period_end ) ) {
                $order->update_meta_data( '_strp_sub_current_period_end', $subscription->current_period_end );
            }
        }

        // Mark one-time discount token as redeemed
        if ( isset( $session->metadata->discount_token ) && ! empty( $session->metadata->discount_token ) ) {
            $order->update_meta_data( '_strp_sub_discount_token', sanitize_text_field( $session->metadata->discount_token ) );
            do_action( 'strp_sub_mark_discount_code_redeemed', $session->metadata->discount_token, $order->get_id() );
        }

        $order->calculate_totals();
        $order->update_status( 'completed', __( 'Payment received via Stripe Billing.', 'subscription-manager-for-stripe' ) );
        $order->save();
        
        // Write index row for scalable O(1) webhook resolution
        global $wpdb;
        $result = $wpdb->insert(
            $wpdb->prefix . 'strp_sub_order_index',
            array(
                'session_id'      => sanitize_text_field( $session->id ),
                'subscription_id' => sanitize_text_field( $session->subscription ?? '' ),
                'order_id'        => $order->get_id()
            ),
            array( '%s', '%s', '%d' )
        );
        if ( false === $result ) {
            STRP_SUB_Logger::error( 'Failed to insert order index. Session: ' . sanitize_text_field( $session->id ) . ' Order: ' . $order->get_id() . ' DB Error: ' . $wpdb->last_error );
        }

        STRP_SUB_Logger::log( 'Created order #' . $order->get_id() . ' from Stripe session ' . $session->id );

        return $order;
    }

    /**
     * Get client IP address.
     *
     * Walks the proxy-header trust chain:
     *   1. HTTP_CF_CONNECTING_IP  — Cloudflare
     *   2. HTTP_X_REAL_IP         — Nginx / other single-proxy setups
     *   3. HTTP_X_FORWARDED_FOR   — Standard reverse-proxy chain (first IP is the client)
     *   4. REMOTE_ADDR            — TCP connection (always last resort)
     *
     * Each candidate is validated with filter_var to reject spoofed values.
     * Developers can override via the 'strp_sub_client_ip' filter.
     *
     * @return string Sanitized, validated IP address.
     */
    private function get_client_ip() {
        // Ordered list of server vars to check, most-specific first.
        $candidates = array(
            'HTTP_CF_CONNECTING_IP',  // Cloudflare strips and re-sets this header.
            'HTTP_X_REAL_IP',         // Nginx real_ip_module.
            'HTTP_X_FORWARDED_FOR',   // Standard proxy chain — may be comma-separated.
            'REMOTE_ADDR',            // Raw TCP connection — final fallback.
        );

        $ip = '127.0.0.1';

        foreach ( $candidates as $header ) {
            if ( empty( $_SERVER[ $header ] ) ) {
                continue;
            }
            // X-Forwarded-For can contain a comma-separated list; the first entry is the client.
            $candidate = trim( current( explode( ',', sanitize_text_field( wp_unslash( $_SERVER[ $header ] ) ) ) ) );
            if ( filter_var( $candidate, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
                $ip = $candidate;
                break;
            }
        }

        // Final fallback: if all candidates were private/reserved ranges (e.g. local dev),
        // accept REMOTE_ADDR without the public-IP restriction.
        if ( $ip === '127.0.0.1' && ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
            $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
        }

        /**
         * Filter the resolved client IP address.
         *
         * @param string $ip The detected IP address.
         */
        return (string) apply_filters( 'strp_sub_client_ip', $ip );
    }

}
