<?php
/**
 * Stripe API wrapper class responsible for all communication with Stripe.
 */

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

class STRP_SUB_Stripe_API {
    /**
     * Define the target Stripe REST API version.
     * Accessible via apply_filters( 'strp_sub_api_version', self::STRIPE_API_VERSION )
     */
    // Last verified: 2024-06-20. Check Stripe API changelog before updating.
    const STRIPE_API_VERSION = '2024-06-20';

    /**
     * Single instance
     */
    private static $instance = null;

    /**
     * Cached resolved API key to avoid redundant calls.
     */
    private $resolved_key = null;
    
    /**
     * Get instance
     */
    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __clone() {}

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

    /**
     * Constructor
     */
    private function __construct() {
        $this->init_stripe();
    }
    
    /**
     * Initialize Stripe with the best available credential.
     *
     * Priority order:
     *  1. OAuth access_token (issued by "Connect with Stripe" flow).
     *  2. Optional legacy secret-key fallback only when explicitly enabled
     *     with the strp_sub_allow_legacy_secret_key filter.
     */
    private function init_stripe() {
        $key = STRP_SUB_Settings::get_runtime_api_key();

        if ( empty( $key ) ) {
            STRP_SUB_Logger::warning( 'Stripe API is unavailable: OAuth connection is required (legacy fallback disabled).' );
            return;
        }

        \STRP_SUB_Vendor\Stripe\Stripe::setApiKey( $key );
        \STRP_SUB_Vendor\Stripe\Stripe::setApiVersion( apply_filters( 'strp_sub_api_version', self::STRIPE_API_VERSION ) );
    }

    /**
     * Re-authenticate Stripe before API calls that need a fresh key.
     * Called internally to ensure the correct key is always in use,
     * especially after the OAuth connection state changes at runtime.
     */
    private function ensure_stripe_key() {
        $key = STRP_SUB_Settings::get_runtime_api_key();
        if ( $key !== $this->resolved_key ) {
            $this->resolved_key = $key;
            $this->init_stripe();
        }
    }

    /**
     * Public method to ensure the Stripe API key is set without making an API call.
     *
     * Call this before using Stripe SDK classes directly (e.g., Coupon::create)
     * to avoid an unnecessary GET call just to "warm" the key.
     */
    public function ensure_api_key_set() {
        $this->init_stripe();
    }
    

    /**
     * Create a Stripe Billing Portal session for a customer.
     *
     * @param string $customer_id Stripe customer ID.
     * @param string $return_url  URL customers return to after portal actions.
     * @return \STRP_SUB_Vendor\Stripe\BillingPortal\Session|false
     */
    public function create_billing_portal_session( $customer_id, $return_url ) {
        if ( empty( $customer_id ) ) {
            return false;
        }

        $this->ensure_stripe_key();

        try {
            return \STRP_SUB_Vendor\Stripe\BillingPortal\Session::create( array(
                'customer'   => $customer_id,
                'return_url' => $return_url,
            ) );
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error creating Billing Portal session: ' . $e->getMessage() );
            return false;
        }
    }

    /**
     * Create a Stripe customer
     */
    public function create_customer( $email, $name = '', $metadata = array(), $address = null ) {
        $this->ensure_stripe_key();
        try {
            $customer_data = array(
                'email' => $email,
                'metadata' => $metadata,
            );
            
            if ( ! empty( $name ) ) {
                $customer_data['name'] = $name;
            }
            
            if ( ! empty( $address ) && isset( $address['city'] ) ) {
                $customer_data['address'] = $address;
            }
            
            $customer = \STRP_SUB_Vendor\Stripe\Customer::create( $customer_data );
            
            STRP_SUB_Logger::log( 'Created Stripe customer: ' . $customer->id );
            
            return $customer;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error creating Stripe customer: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * Get the mode suffix for the current Stripe mode ('_live' or '_test').
     *
     * @return string
     */
    private static function mode_suffix() {
        return STRP_SUB_Settings::is_test_mode() ? '_test' : '_live';
    }

    /**
     * Get the mode suffix for the opposite of the current Stripe mode.
     *
     * @return string
     */
    private static function opposite_mode_suffix() {
        return STRP_SUB_Settings::is_test_mode() ? '_live' : '_test';
    }

    /**
     * Save Stripe customer data to mode-specific meta keys.
     *
     * Writes to both the generic key (for backward compat with all existing code)
     * and the mode-specific key (e.g. _strp_sub_stripe_customer_id_live).
     *
     * @param int    $user_id
     * @param string $customer_id Stripe customer ID.
     */
    private function save_customer_id_for_mode( $user_id, $customer_id ) {
        $suffix = self::mode_suffix();
        update_user_meta( $user_id, '_strp_sub_stripe_customer_id',           $customer_id );
        update_user_meta( $user_id, '_strp_sub_stripe_customer_id' . $suffix, $customer_id );
    }

    /**
     * Handle a test/live mode mismatch for a user.
     *
     * Saves the current (mismatched) customer data to the *opposite* mode's
     * meta keys, then checks whether we already have data for the *current*
     * mode. If so, restores it to the generic keys. If not, clears the generic
     * keys so a new customer will be created.
     *
     * @param int    $user_id
     * @param string $stripe_customer_id The customer ID that caused the mismatch.
     */
    private function handle_mode_mismatch( $user_id, $stripe_customer_id ) {
        $opposite = self::opposite_mode_suffix();
        $current  = self::mode_suffix();

        // Persist the mismatched data under the opposite mode's keys.
        update_user_meta( $user_id, '_strp_sub_stripe_customer_id' . $opposite,     $stripe_customer_id );
        update_user_meta( $user_id, '_strp_sub_active_subscription_id' . $opposite, get_user_meta( $user_id, '_strp_sub_active_subscription_id', true ) );
        update_user_meta( $user_id, '_strp_sub_subscription_status' . $opposite,    get_user_meta( $user_id, '_strp_sub_subscription_status', true ) );
        update_user_meta( $user_id, '_strp_sub_current_period_end' . $opposite,     get_user_meta( $user_id, '_strp_sub_current_period_end', true ) );

        // Check if we already have a customer ID stored for the current mode.
        $current_mode_customer = get_user_meta( $user_id, '_strp_sub_stripe_customer_id' . $current, true );

        if ( ! empty( $current_mode_customer ) ) {
            // Restore the current mode's data to the generic keys.
            update_user_meta( $user_id, '_strp_sub_stripe_customer_id',     $current_mode_customer );
            update_user_meta( $user_id, '_strp_sub_active_subscription_id', get_user_meta( $user_id, '_strp_sub_active_subscription_id' . $current, true ) );
            update_user_meta( $user_id, '_strp_sub_subscription_status',    get_user_meta( $user_id, '_strp_sub_subscription_status' . $current, true ) );
            update_user_meta( $user_id, '_strp_sub_current_period_end',     get_user_meta( $user_id, '_strp_sub_current_period_end' . $current, true ) );
            STRP_SUB_Logger::log( 'Restored ' . $current . ' mode customer ID for user #' . $user_id . ': ' . $current_mode_customer );
        } else {
            // No saved data for the current mode — clear the generic keys.
            delete_user_meta( $user_id, '_strp_sub_stripe_customer_id' );
            delete_user_meta( $user_id, '_strp_sub_active_subscription_id' );
            delete_user_meta( $user_id, '_strp_sub_subscription_status' );
            delete_user_meta( $user_id, '_strp_sub_current_period_end' );
        }

        if ( ! get_transient( 'strp_sub_mode_mismatch_notice' ) ) {
            set_transient( 'strp_sub_mode_mismatch_notice', true, HOUR_IN_SECONDS );
            $mode_label = STRP_SUB_Settings::is_test_mode() ? 'test' : 'live';
            add_action( 'admin_notices', function () use ( $mode_label ) {
                echo '<div class="notice notice-warning"><p><strong>Stripe Subscription Manager:</strong> '
                    . sprintf(
                        esc_html__( 'A Stripe test/live mode mismatch was detected. Customer data has been saved per mode. You are currently in %s mode.', 'subscription-manager-for-stripe' ),
                        '<strong>' . esc_html( $mode_label ) . '</strong>'
                    )
                    . '</p></div>';
            } );
        }
    }

    /**
     * Get or create a Stripe customer for a WooCommerce user
     */
    public function get_or_create_customer( $user_id ) {
        $this->ensure_stripe_key();
        // Check if customer already has a Stripe ID
        $stripe_customer_id = get_user_meta( $user_id, '_strp_sub_stripe_customer_id', true );

        if ( ! empty( $stripe_customer_id ) ) {
            try {
                // Verify customer exists in Stripe
                $customer = \STRP_SUB_Vendor\Stripe\Customer::retrieve( $stripe_customer_id );
                return $customer;
            } catch ( \STRP_SUB_Vendor\Stripe\Exception\InvalidRequestException $e ) {
                $error_msg = $e->getMessage();

                // Detect test/live mode mismatch — save data under mode-specific
                // meta keys so it is never lost, even across multiple mode switches.
                if ( strpos( $error_msg, 'test mode' ) !== false || strpos( $error_msg, 'live mode' ) !== false ) {
                    STRP_SUB_Logger::log( 'Detected test/live mode mismatch for customer ' . $stripe_customer_id . '. Saving mode-specific data for user #' . $user_id );
                    $this->handle_mode_mismatch( $user_id, $stripe_customer_id );

                    // If we restored a customer for the current mode, try to use it.
                    $restored_id = get_user_meta( $user_id, '_strp_sub_stripe_customer_id', true );
                    if ( ! empty( $restored_id ) ) {
                        try {
                            $customer = \STRP_SUB_Vendor\Stripe\Customer::retrieve( $restored_id );
                            return $customer;
                        } catch ( Exception $inner_e ) {
                            STRP_SUB_Logger::log( 'Restored customer ID invalid, will create new: ' . $inner_e->getMessage() );
                            delete_user_meta( $user_id, '_strp_sub_stripe_customer_id' );
                        }
                    }
                    // Continue to create new customer below
                } elseif ( $e->getStripeCode() === 'resource_missing' ) {
                    // Customer was deleted - clear stale meta and create new one
                    STRP_SUB_Logger::log( 'Stripe customer not found (deleted): ' . $stripe_customer_id . '. Creating new customer.' );
                    delete_user_meta( $user_id, '_strp_sub_stripe_customer_id' );
                    // Continue to create new customer below
                } else {
                    // Other API errors (invalid API key, etc.) - don't create duplicate
                    STRP_SUB_Logger::error( 'Stripe API error retrieving customer: ' . $error_msg );
                    return false;
                }
            } catch ( Exception $e ) {
                // Network errors or other exceptions - don't create duplicate customer
                STRP_SUB_Logger::error( 'Error retrieving Stripe customer: ' . $e->getMessage() );
                return false;
            }
        }

        // Create new customer
        $user = get_userdata( $user_id );
        if ( ! $user ) {
            return false;
        }

        // Get user's billing address from WooCommerce
        $billing_city = get_user_meta( $user_id, 'billing_city', true );
        $billing_country = get_user_meta( $user_id, 'billing_country', true );
        $billing_line1 = get_user_meta( $user_id, 'billing_address_1', true );
        $billing_postcode = get_user_meta( $user_id, 'billing_postcode', true );

        $customer_data = array(
            'email' => $user->user_email,
            'name' => $user->display_name,
            'metadata' => array( 'wc_user_id' => $user_id ),
        );

        // Add address if we have at least one meaningful field (some digital downloads only require Country)
        if ( ! empty( $billing_country ) || ! empty( $billing_postcode ) || ! empty( $billing_city ) ) {
            $address = array(
                'city' => $billing_city,
                'country' => $billing_country,
            );

            if ( ! empty( $billing_line1 ) ) {
                $address['line1'] = $billing_line1;
            }
            if ( ! empty( $billing_postcode ) ) {
                $address['postal_code'] = $billing_postcode;
            }

            $customer_data['address'] = $address;
        }

        try {
            $customer = \STRP_SUB_Vendor\Stripe\Customer::create( $customer_data );

            STRP_SUB_Logger::log( 'Created Stripe customer: ' . $customer->id );

            if ( $customer ) {
                $this->save_customer_id_for_mode( $user_id, $customer->id );
            }

            return $customer;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error creating Stripe customer: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * Create a Stripe Checkout session for recurring subscriptions.
     *
     * @param string|null $customer_id Stripe customer identifier.
     * @param string      $price_id    Stripe price identifier.
     * @param string      $success_url URL used after successful checkout.
     * @param string      $cancel_url  URL used when the customer cancels checkout.
     * @param array       $metadata    Metadata forwarded to Stripe objects.
     * @param string|null $coupon_id   Optional Stripe coupon identifier to apply.
     * @param bool        $include_trial Whether to include the trial period (default: true).
     *
     * @return \STRP_SUB_Vendor\Stripe\Checkout\Session|false
     */
    public function create_checkout_session( $customer_id, $price_id, $success_url, $cancel_url, $metadata = array(), $coupon_id = null, $include_trial = true ) {
        $this->ensure_stripe_key();
        STRP_SUB_Logger::debug( 'Creating subscription checkout session for price ' . $price_id );

        try {
            $tax_enabled = false;
            if ( function_exists( 'strp_sub_fs' ) && strp_sub_fs() && strp_sub_fs()->is__premium_only() ) {
                $tax_enabled = STRP_SUB_Settings::get_automatic_tax_enabled();
            }

            $session_data = array(
                'line_items' => array(
                    array(
                        'price' => $price_id,
                        'quantity' => 1,
                    ),
                ),
                'mode' => 'subscription',
                'locale' => 'auto',
                'billing_address_collection' => 'required',
                'tax_id_collection' => array( 'enabled' => $tax_enabled ),
                'automatic_tax' => array( 'enabled' => $tax_enabled ),
                'success_url' => $success_url,
                'cancel_url' => $cancel_url,
            );

            if ( $coupon_id ) {
                $session_data['discounts'] = array(
                    array( 'coupon' => $coupon_id ),
                );
                STRP_SUB_Logger::debug( 'Applied coupon ' . $coupon_id . ' to checkout session.' );
            }

            if ( ! empty( $customer_id ) ) {
                $session_data['customer'] = $customer_id;
                $session_data['customer_update'] = array(
                    'name' => 'auto',
                    'address' => 'auto',
                );
            } else {
                STRP_SUB_Logger::debug( 'Creating new customer as part of checkout session.' );
            }

            $clean_metadata = array();
            if ( ! empty( $metadata ) ) {
                foreach ( $metadata as $key => $value ) {
                    $clean_metadata[ $key ] = (string) $value;
                }
            }

            // Conditionally set trial period for subscriptions
            $session_data['subscription_data'] = array();

            if ( $include_trial ) {
                // Only add trial if explicitly requested and days > 0
                $trial_days = STRP_SUB_Settings::get_trial_period_days();
                if ( $trial_days > 0 ) {
                    $session_data['subscription_data']['trial_period_days'] = $trial_days;
                }
                STRP_SUB_Logger::log( 'Trial period enabled for checkout session.' );
            } else {
                STRP_SUB_Logger::log( 'Trial period DISABLED for checkout session (user has already used trial).' );
            }

            if ( ! empty( $clean_metadata ) ) {
                $session_data['metadata'] = $clean_metadata;
                $session_data['subscription_data']['metadata'] = $clean_metadata;
            }

            // Add payment terms based on trial inclusion and upgrade status
            $is_upgrade = ! empty( $clean_metadata['old_subscription_id'] );

            if ( $is_upgrade ) {
                // User is upgrading from one subscription to another (e.g., monthly to annual)
                $session_data['custom_text'] = array(
                    'submit' => array(
                        'message' => STRP_SUB_Settings::get_checkout_message( 'upgrade' ),
                    ),
                );
                STRP_SUB_Logger::debug( 'Added upgrade payment terms to subscription checkout session.' );
            } elseif ( $include_trial ) {
                $session_data['custom_text'] = array(
                    'submit' => array(
                        'message' => STRP_SUB_Settings::get_checkout_message( 'trial' ),
                    ),
                );
                STRP_SUB_Logger::debug( 'Added trial period payment terms to subscription checkout session.' );
            } else {
                $session_data['custom_text'] = array(
                    'submit' => array(
                        'message' => STRP_SUB_Settings::get_checkout_message( 'no_trial' ),
                    ),
                );
                STRP_SUB_Logger::debug( 'Added no-trial payment terms to subscription checkout session.' );
            }

            /**
             * Filter the Stripe Checkout session data for subscriptions before creation.
             *
             * @param array       $session_data The full session parameters array.
             * @param string      $price_id     Stripe Price ID.
             * @param array       $metadata     Metadata passed to the session.
             * @param string|null $customer_id  Stripe Customer ID (or null for new customers).
             */
            $session_data = apply_filters( 'strp_sub_checkout_session_data', $session_data, $price_id, $metadata, $customer_id );

            try {
                $session = \STRP_SUB_Vendor\Stripe\Checkout\Session::create( $session_data );
                STRP_SUB_Logger::log( 'Created Stripe Checkout Session: ' . $session->id );
                return $session;
            } catch ( \STRP_SUB_Vendor\Stripe\Exception\InvalidRequestException $e ) {
                // If coupon doesn't exist, retry without discount
                if ( $coupon_id && strpos( $e->getMessage(), 'coupon' ) !== false ) {
                    STRP_SUB_Logger::error( 'Coupon ' . $coupon_id . ' not found in Stripe. Retrying checkout without discount.' );
                    unset( $session_data['discounts'] );
                    $session = \STRP_SUB_Vendor\Stripe\Checkout\Session::create( $session_data );
                    STRP_SUB_Logger::log( 'Created Stripe Checkout Session (without discount): ' . $session->id );
                    return $session;
                } else {
                    throw $e;
                }
            }
        } catch ( Exception $e ) {
            $safe_log = array(
                'mode'     => $session_data['mode'] ?? '',
                'customer' => ! empty( $session_data['customer'] ) ? 'set' : 'unset',
                'has_trial' => isset( $session_data['subscription_data']['trial_period_days'] ),
            );
            STRP_SUB_Logger::error( 'Checkout session failed. Context: ' . wp_json_encode( $safe_log ) . ' | Error: ' . $e->getMessage() );
            return false;
        }
    }

    /**
     * Create a Stripe Checkout session for one-time payments.
     *
     * @param string|null $customer_id Stripe customer identifier.
     * @param string      $price_id    Stripe price identifier.
     * @param string      $success_url URL used after successful checkout.
     * @param string      $cancel_url  URL used when the customer cancels checkout.
     * @param array       $metadata    Metadata forwarded to Stripe objects.
     * @param string|null $coupon_id   Optional Stripe coupon identifier to apply.
     *
     * @return \STRP_SUB_Vendor\Stripe\Checkout\Session|false
     */
    public function create_checkout_session_onetime( $customer_id, $price_id, $success_url, $cancel_url, $metadata = array(), $coupon_id = null ) {
        $this->ensure_stripe_key();
        STRP_SUB_Logger::debug( 'Creating one-time checkout session for price ' . $price_id );

        try {
            $tax_enabled = false;
            if ( function_exists( 'strp_sub_fs' ) && strp_sub_fs() && strp_sub_fs()->is__premium_only() ) {
                $tax_enabled = STRP_SUB_Settings::get_automatic_tax_enabled();
            }

            $session_data = array(
                'line_items' => array(
                    array(
                        'price' => $price_id,
                        'quantity' => 1,
                    ),
                ),
                'mode' => 'payment',
                'locale' => 'auto',
                'billing_address_collection' => 'required',
                'tax_id_collection' => array( 'enabled' => $tax_enabled ),
                'automatic_tax' => array( 'enabled' => $tax_enabled ),
                'success_url' => $success_url,
                'cancel_url' => $cancel_url,
                'invoice_creation' => array( 'enabled' => true ),
            );

            // Add payment terms based on whether this is an upgrade or new purchase
            $is_upgrade = ! empty( $metadata['old_subscription_id'] );

            if ( $is_upgrade ) {
                // User is upgrading from a subscription (e.g., monthly/annual to lifetime)
                $session_data['custom_text'] = array(
                    'submit' => array(
                        'message' => STRP_SUB_Settings::get_checkout_message( 'onetime_upgrade' ),
                    ),
                );
                STRP_SUB_Logger::debug( 'Added upgrade payment terms to checkout session.' );
            } else {
                // Fresh user buying lifetime directly
                $session_data['custom_text'] = array(
                    'submit' => array(
                        'message' => STRP_SUB_Settings::get_checkout_message( 'onetime_new' ),
                    ),
                );
                STRP_SUB_Logger::debug( 'Added new purchase payment terms to checkout session.' );
            }

            if ( $coupon_id ) {
                $session_data['discounts'] = array(
                    array( 'coupon' => $coupon_id ),
                );
                STRP_SUB_Logger::debug( 'Applied coupon ' . $coupon_id . ' to one-time checkout session.' );
            }

            if ( ! empty( $customer_id ) ) {
                $session_data['customer'] = $customer_id;
                $session_data['customer_update'] = array(
                    'name' => 'auto',
                    'address' => 'auto',
                );
            } else {
                $session_data['customer_creation'] = 'always';
                STRP_SUB_Logger::debug( 'Configuring checkout session to always create a customer.' );
            }

            $clean_metadata = array();
            if ( ! empty( $metadata ) ) {
                foreach ( $metadata as $key => $value ) {
                    $clean_metadata[ $key ] = (string) $value;
                }
            }

            if ( ! empty( $clean_metadata ) ) {
                $session_data['metadata'] = $clean_metadata;
                $session_data['payment_intent_data'] = array( 'metadata' => $clean_metadata );
                $session_data['invoice_creation']['invoice_data'] = array( 'metadata' => $clean_metadata );
            }

            /**
             * Filter the Stripe Checkout session data for one-time payments before creation.
             *
             * @param array       $session_data The full session parameters array.
             * @param string      $price_id     Stripe Price ID.
             * @param array       $metadata     Metadata passed to the session.
             * @param string|null $customer_id  Stripe Customer ID (or null for new customers).
             */
            $session_data = apply_filters( 'strp_sub_checkout_session_onetime_data', $session_data, $price_id, $metadata, $customer_id );

            try {
                $session = \STRP_SUB_Vendor\Stripe\Checkout\Session::create( $session_data );
                STRP_SUB_Logger::log( 'Created Stripe Checkout Session (one-time): ' . $session->id );
                return $session;
            } catch ( \STRP_SUB_Vendor\Stripe\Exception\InvalidRequestException $e ) {
                // If coupon doesn't exist, retry without discount
                if ( $coupon_id && strpos( $e->getMessage(), 'coupon' ) !== false ) {
                    STRP_SUB_Logger::error( 'Coupon ' . $coupon_id . ' not found in Stripe. Retrying one-time checkout without discount.' );
                    unset( $session_data['discounts'] );
                    $session = \STRP_SUB_Vendor\Stripe\Checkout\Session::create( $session_data );
                    STRP_SUB_Logger::log( 'Created Stripe Checkout Session (one-time, without discount): ' . $session->id );
                    return $session;
                } else {
                    throw $e;
                }
            }
        } catch ( Exception $e ) {
            $safe_log = array(
                'mode'     => $session_data['mode'] ?? '',
                'customer' => ! empty( $session_data['customer'] ) ? 'set' : 'unset',
                'has_trial' => $session_data['mode'] === 'subscription' && isset( $session_data['subscription_data']['trial_period_days'] ),
            );
            STRP_SUB_Logger::error( 'Checkout session failed. Context: ' . wp_json_encode( $safe_log ) . ' | Error: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * Create a Stripe Price
     */
    public function create_price( $product_id, $amount, $currency, $interval = null, $interval_count = 1 ) {
        $this->ensure_stripe_key();
        try {
            $price_data = array(
                'product' => $product_id,
                'unit_amount' => $amount, // Amount in cents
                'currency' => $currency,
            );
            
            if ( $interval ) {
                $price_data['recurring'] = array(
                    'interval' => $interval, // 'month' or 'year'
                    'interval_count' => $interval_count,
                );
            }
            
            $price = \STRP_SUB_Vendor\Stripe\Price::create( $price_data );
            
            STRP_SUB_Logger::log( 'Created Stripe Price: ' . $price->id );
            
            return $price;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error creating Stripe Price: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * Create a Stripe Product
     */
    public function create_product( $name, $description = '' ) {
        $this->ensure_stripe_key();
        try {
            $product = \STRP_SUB_Vendor\Stripe\Product::create( array(
                'name' => $name,
                'description' => $description,
            ) );
            
            STRP_SUB_Logger::log( 'Created Stripe Product: ' . $product->id );
            
            return $product;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error creating Stripe Product: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * Retrieve a Stripe Coupon by ID.
     *
     * @param  string $coupon_id Stripe coupon ID.
     * @return \STRP_SUB_Vendor\Stripe\Coupon|false
     */
    public function retrieve_coupon( $coupon_id ) {
        $this->ensure_stripe_key();
        try {
            $coupon = \STRP_SUB_Vendor\Stripe\Coupon::retrieve( $coupon_id );
            STRP_SUB_Logger::log( 'Retrieved Stripe Coupon: ' . $coupon_id );
            return $coupon;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error retrieving coupon: ' . $e->getMessage() );
            return false;
        }
    }

    /**
     * Cancel a subscription
     */
    public function cancel_subscription( $subscription_id, $at_period_end = true ) {
        $this->ensure_stripe_key();
        try {
            if ( $at_period_end ) {
                $subscription = \STRP_SUB_Vendor\Stripe\Subscription::update( $subscription_id, array(
                    'cancel_at_period_end' => true,
                ) );
            } else {
                $subscription = \STRP_SUB_Vendor\Stripe\Subscription::retrieve( $subscription_id );
                $subscription->cancel();
            }
            
            STRP_SUB_Logger::log( 'Cancelled Stripe subscription: ' . $subscription_id );
            
            return $subscription;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error cancelling subscription: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * Retrieve a subscription
     */
    public function get_subscription( $subscription_id ) {
        $this->ensure_stripe_key();
        try {
            $subscription = \STRP_SUB_Vendor\Stripe\Subscription::retrieve( $subscription_id );
            return $subscription;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error retrieving subscription: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * Retrieve a customer
     */
    public function get_customer( $customer_id ) {
        $this->ensure_stripe_key();
        try {
            $customer = \STRP_SUB_Vendor\Stripe\Customer::retrieve( $customer_id );
            return $customer;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error retrieving customer: ' . $e->getMessage() );
            return false;
        }
    }
    
    /**
     * List customer subscriptions
     */
    public function list_customer_subscriptions( $customer_id, $status = 'all' ) {
        $this->ensure_stripe_key();
        try {
            $subscriptions = \STRP_SUB_Vendor\Stripe\Subscription::all( array(
                'customer' => $customer_id,
                'status'   => $status,
                'limit'    => 100,
            ) );
            return $subscriptions->data;
        } catch ( Exception $e ) {
            $error_msg = $e->getMessage();
            STRP_SUB_Logger::error( 'Error listing customer subscriptions: ' . $error_msg );

            // Detect test/live mode mismatch — save data under mode-specific meta
            // keys so each mode has its own independent customer mapping.
            if ( strpos( $error_msg, 'test mode' ) !== false || strpos( $error_msg, 'live mode' ) !== false ) {
                STRP_SUB_Logger::log( 'Detected test/live mode mismatch for customer ' . $customer_id . '. Saving mode-specific data.' );

                // Find users with this customer ID and handle the mismatch.
                $users = get_users( array(
                    'meta_query' => array( array(
                        'key'   => '_strp_sub_stripe_customer_id',
                        'value' => $customer_id,
                    ) ),
                    'fields'     => 'ID',
                    'number'     => 5,
                ) );

                foreach ( $users as $user_id ) {
                    $this->handle_mode_mismatch( $user_id, $customer_id );
                    STRP_SUB_Logger::log( 'Saved mode-specific customer data for user #' . $user_id );
                }
            }

            return array();
        }
    }

    /**
     * Get card fingerprint from subscription
     * This fingerprint is unique per card and can be used for fraud detection
     *
     * @param string $subscription_id Stripe subscription ID
     * @return string|false Card fingerprint or false on error
     */
    public function get_card_fingerprint_from_subscription( $subscription_id ) {
        $this->ensure_stripe_key();
        try {
            $subscription = \STRP_SUB_Vendor\Stripe\Subscription::retrieve( array(
                'id' => $subscription_id,
                'expand' => array( 'default_payment_method' ),
            ) );

            if ( ! $subscription || ! isset( $subscription->default_payment_method ) ) {
                STRP_SUB_Logger::debug( 'No default payment method found for subscription: ' . $subscription_id );
                return false;
            }

            $payment_method = $subscription->default_payment_method;

            // Check if it's a card payment method
            if ( isset( $payment_method->card ) && isset( $payment_method->card->fingerprint ) ) {
                $fingerprint = $payment_method->card->fingerprint;
                STRP_SUB_Logger::log( 'Card fingerprint retrieved: ' . substr( $fingerprint, 0, 8 ) . '...' );
                return $fingerprint;
            }

            // For other payment methods (iDEAL, Bancontact, etc.), return payment method ID as identifier
            if ( isset( $payment_method->id ) ) {
                STRP_SUB_Logger::log( 'Non-card payment method: ' . $payment_method->type );
                return 'pm_' . $payment_method->type . '_' . substr( $payment_method->id, -8 );
            }

            return false;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error retrieving card fingerprint: ' . $e->getMessage() );
            return false;
        }
    }

    /**
     * Create the DB table for trial fingerprints.
     * Prevents massive memory bloat from the old options array.
     */
    public static function create_fingerprints_table() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'strp_sub_trial_fingerprints';
        $charset_collate = $wpdb->get_charset_collate();

        $sql = "CREATE TABLE $table_name (
            fingerprint varchar(255) NOT NULL,
            user_id bigint(20) unsigned NOT NULL DEFAULT 0,
            used_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
            PRIMARY KEY  (fingerprint)
        ) $charset_collate;";

        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        dbDelta( $sql );
    }

    /**
     * Check if a card fingerprint has been used for a trial before
     *
     * @param string $fingerprint Card fingerprint
     * @return bool True if fingerprint was used for trial before
     */
    public function has_fingerprint_used_trial( $fingerprint ) {
        if ( empty( $fingerprint ) ) {
            return false;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . 'strp_sub_trial_fingerprints';
        
        // Fast, indexed DB check instead of loading an enormous array
        $query = $wpdb->prepare( "SELECT fingerprint FROM {$table_name} WHERE fingerprint = %s", $fingerprint );
        $result = $wpdb->get_var( $query );

        return ! empty( $result );
    }

    /**
     * Store a card fingerprint as having been used for a trial
     *
     * @param string $fingerprint Card fingerprint
     * @param int    $user_id     User ID who used the trial
     */
    public function store_trial_fingerprint( $fingerprint, $user_id = 0 ) {
        if ( empty( $fingerprint ) ) {
            return;
        }

        global $wpdb;
        $table_name = $wpdb->prefix . 'strp_sub_trial_fingerprints';

        // Use replace to handle uniqueness elegantly
        $wpdb->replace(
            $table_name,
            array(
                'fingerprint' => $fingerprint,
                'user_id'     => $user_id,
                'used_at'     => current_time( 'mysql' ),
            ),
            array(
                '%s',
                '%d',
                '%s',
            )
        );

        STRP_SUB_Logger::log( 'Stored trial fingerprint DB record: ' . substr( $fingerprint, 0, 8 ) . '... for user #' . $user_id );
    }

    /**
     * Cancel subscription due to fraud detection (duplicate card)
     * Used when a card fingerprint has already been used for a trial
     *
     * @param string $subscription_id Stripe subscription ID
     * @return \STRP_SUB_Vendor\Stripe\Subscription|false Cancelled subscription or false on error
     */
    public function cancel_subscription_for_fraud( $subscription_id ) {
        $this->ensure_stripe_key();
        try {
            // Cancel subscription immediately (not at period end)
            $subscription = \STRP_SUB_Vendor\Stripe\Subscription::retrieve( $subscription_id );
            $cancelled = $subscription->cancel();

            STRP_SUB_Logger::log( '[FRAUD] Cancelled subscription ' . $subscription_id . ' due to duplicate card detection.' );
            STRP_SUB_Logger::log( '[FRAUD] Customer will NOT get access. They must contact support to resolve.' );

            return $cancelled;
        } catch ( Exception $e ) {
            STRP_SUB_Logger::error( 'Error cancelling subscription for fraud: ' . $e->getMessage() );
            return false;
        }
    }
}