<?php
/**
 * Stripe Payment Gateway
 *
 * @package Invizo\SubscriptionsLite\Gateway
 */

namespace Invizo\SubscriptionsLite\Gateway;

use Exception;
use Invizo\SubscriptionsLite\Frontend\Checkout;
use Invizo\SubscriptionsLite\Frontend\Subscription_Email_Verification;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WP_User;

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

class StripeGateway {
    const REST_NAMESPACE = 'invizo-subs/v1';
    const WEBHOOK_ROUTE  = '/stripe-webhook';

    /**
     * Whether checkout AJAX received privacy acceptance when a policy page is configured.
     *
     * @return bool
     */
    private static function checkout_privacy_accepted() {
        $policy_url = class_exists( Checkout::class )
            ? Checkout::get_effective_privacy_policy_url()
            : '';
        if ( $policy_url === '' ) {
            return true;
        }
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- callers (AJAX actions) verify invizo_checkout_nonce before using this.
        $raw = isset( $_POST['privacy_accepted'] ) ? sanitize_text_field( wp_unslash( $_POST['privacy_accepted'] ) ) : '';
        $raw = is_string( $raw ) ? strtolower( trim( $raw ) ) : '';
        return in_array( $raw, array( '1', 'yes', 'true', 'on' ), true );
    }

    public static function init() {
        add_action( 'rest_api_init', array( __CLASS__, 'register_webhook_route' ) );
        add_action( 'wp_ajax_invizo_create_subscription_intent', array( __CLASS__, 'ajax_create_subscription_intent' ) );
        add_action( 'wp_ajax_nopriv_invizo_create_subscription_intent', array( __CLASS__, 'ajax_create_subscription_intent' ) );
        add_action( 'wp_ajax_invizo_confirm_subscription_order', array( __CLASS__, 'ajax_confirm_subscription_order' ) );
        add_action( 'wp_ajax_nopriv_invizo_confirm_subscription_order', array( __CLASS__, 'ajax_confirm_subscription_order' ) );
    }

    /**
     * @return string
     */
    public static function get_webhook_url() {
        return rest_url( self::REST_NAMESPACE . self::WEBHOOK_ROUTE );
    }

    /**
     * Confirm prefixed Stripe SDK classes are loadable (Strauss output under `invizo-vendor/`).
     *
     * @return bool
     */
    public static function load_sdk() {
        return class_exists( '\InvizoSubscriptionsLiteVendor\Stripe\Stripe' )
            && class_exists( '\InvizoSubscriptionsLiteVendor\Stripe\Webhook' );
    }

    /**
     * One-time: copy legacy single-pair options into test or live slots by key prefix.
     */
    public static function maybe_migrate_legacy_stripe_keys() {
        if ( get_option( 'invizo_stripe_pair_keys_migrated', '' ) === '1' ) {
            return;
        }

        $legacy_pub = get_option( 'invizo_stripe_public_key', '' );
        $legacy_sec = get_option( 'invizo_stripe_secret_key', '' );

        if ( $legacy_pub === '' && $legacy_sec === '' ) {
            update_option( 'invizo_stripe_pair_keys_migrated', '1' );
            return;
        }

        if ( strpos( $legacy_pub, 'pk_live' ) === 0 ) {
            update_option( 'invizo_stripe_live_public_key', $legacy_pub );
            update_option( 'invizo_stripe_live_secret_key', $legacy_sec );
        } else {
            update_option( 'invizo_stripe_test_public_key', $legacy_pub );
            update_option( 'invizo_stripe_test_secret_key', $legacy_sec );
        }

        update_option( 'invizo_stripe_pair_keys_migrated', '1' );
    }

    /**
     * Active publishable + secret for the current mode (test vs live), with legacy fallback.
     */
    public static function get_api_keys() {
        self::maybe_migrate_legacy_stripe_keys();

        $mode = get_option( 'invizo_stripe_mode', 'test' ) === 'live' ? 'live' : 'test';

        if ( $mode === 'live' ) {
            $public = get_option( 'invizo_stripe_live_public_key', '' );
            $secret = get_option( 'invizo_stripe_live_secret_key', '' );
        } else {
            $public = get_option( 'invizo_stripe_test_public_key', '' );
            $secret = get_option( 'invizo_stripe_test_secret_key', '' );
        }

        if ( $public === '' && $secret === '' ) {
            $public = get_option( 'invizo_stripe_public_key', '' );
            $secret = get_option( 'invizo_stripe_secret_key', '' );
        }

        return array(
            'public'  => $public,
            'secret'  => $secret,
            'webhook' => get_option( 'invizo_stripe_webhook_secret', '' ),
        );
    }

    public static function register_webhook_route() {
        register_rest_route( self::REST_NAMESPACE, self::WEBHOOK_ROUTE, array(
            'methods'  => 'POST',
            'callback' => array( __CLASS__, 'handle_webhook' ),
            'permission_callback' => '__return_true',
        ) );
    }

    public static function handle_webhook( WP_REST_Request $request ) {
        $payload = $request->get_body();
        $sig_header = $request->get_header( 'stripe-signature' );
        $keys = self::get_api_keys();

        if ( empty( $keys['webhook'] ) ) {
            return new WP_REST_Response( 'Webhook secret not configured', 400 );
        }

        if ( empty( $sig_header ) ) {
            return new WP_REST_Response( 'Missing Stripe signature', 400 );
        }

        if ( ! self::load_sdk() ) {
            return new WP_REST_Response( 'Stripe SDK missing', 500 );
        }

        try {
            \InvizoSubscriptionsLiteVendor\Stripe\Webhook::constructEvent( $payload, $sig_header, $keys['webhook'] );
        } catch ( \UnexpectedValueException $e ) {
            return new WP_REST_Response( 'Invalid payload', 400 );
        } catch ( \InvizoSubscriptionsLiteVendor\Stripe\Exception\SignatureVerificationException $e ) {
            return new WP_REST_Response( 'Invalid signature', 400 );
        }

        $event = json_decode( $payload, true );
        if ( ! isset( $event['type'] ) ) {
            return new WP_REST_Response( 'Invalid payload', 400 );
        }

        switch ( $event['type'] ) {
            case 'invoice.payment_succeeded':
                self::handle_payment_succeeded( $event['data']['object'] );
                break;
            case 'invoice.payment_failed':
                self::handle_payment_failed( $event['data']['object'] );
                break;
            case 'customer.subscription.updated':
            case 'customer.subscription.deleted':
                self::handle_subscription_status_changed( $event['data']['object'] );
                break;
        }

        return new WP_REST_Response( 'Success', 200 );
    }

    private static function handle_payment_succeeded( $invoice ) {
        $subscription_id = $invoice['subscription'] ?? '';
        $customer_id     = $invoice['customer'] ?? '';
        
        if ( ! $subscription_id ) return;

        // Query our CPT logic to find matching order and mark active!
        $args = array(
            'post_type'  => 'invizo_sub',
            'meta_query' => array(
                array(
                    'key'   => '_stripe_subscription_id',
                    'value' => $subscription_id,
                )
            )
        );
        $subs = get_posts( $args );

        if ( ! empty( $subs ) ) {
            $sub_id = $subs[0]->ID;
            if ( class_exists( Subscription_Email_Verification::class, false ) && Subscription_Email_Verification::is_subscription_pending_verification( $sub_id ) ) {
                update_post_meta( $sub_id, '_last_payment_date', current_time( 'mysql' ) );
                return;
            }
            update_post_meta( $sub_id, '_status', 'active' );
            update_post_meta( $sub_id, '_last_payment_date', current_time( 'mysql' ) );
        }
    }

    private static function handle_payment_failed( $invoice ) {
        // Suspend the subscription locally
        $subscription_id = $invoice['subscription'] ?? '';
        if ( ! $subscription_id ) return;

        $args = array(
            'post_type'  => 'invizo_sub',
            'meta_query' => array(
                array(
                    'key'   => '_stripe_subscription_id',
                    'value' => $subscription_id,
                )
            )
        );
        $subs = get_posts( $args );

        if ( ! empty( $subs ) ) {
            $sub_id = $subs[0]->ID;
            update_post_meta( $sub_id, '_status', 'past_due' );
        }
    }

    private static function handle_subscription_status_changed( $subscription ) {
        $status = $subscription['status']; // e.g. active, past_due, canceled
        $end_date = $subscription['current_period_end'] ?? 0;

        $args = array(
            'post_type'  => 'invizo_sub',
            'meta_query' => array(
                array(
                    'key'   => '_stripe_subscription_id',
                    'value' => $subscription['id'],
                )
            )
        );
        $subs = get_posts( $args );

        if ( ! empty( $subs ) ) {
            $sub_id = $subs[0]->ID;
            if ( class_exists( Subscription_Email_Verification::class, false ) && Subscription_Email_Verification::is_subscription_pending_verification( $sub_id ) ) {
                if ( $end_date ) {
                    update_post_meta( $sub_id, '_next_payment_date', gmdate( 'Y-m-d H:i:s', (int) $end_date ) );
                }
                return;
            }
            update_post_meta( $sub_id, '_status', sanitize_text_field( $status ) );
            if ( $end_date ) {
                update_post_meta( $sub_id, '_next_payment_date', gmdate( 'Y-m-d H:i:s', (int) $end_date ) );
            }
        }
    }

    public static function ajax_create_subscription_intent() {
        check_ajax_referer( 'invizo_checkout_nonce', 'nonce' );

        $plan_id = isset( $_POST['plan_id'] ) ? absint( wp_unslash( $_POST['plan_id'] ) ) : 0;
        $email   = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';
        $fname   = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
        $country = isset( $_POST['billing_country'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_POST['billing_country'] ) ) ) : '';
        $city    = isset( $_POST['billing_city'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_city'] ) ) : '';
        $zip     = isset( $_POST['billing_postcode'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_postcode'] ) ) : '';

        if ( ! $plan_id ) {
            wp_send_json_error( array( 'message' => __( 'Please select a subscription plan.', 'invizo-subscriptions-lite' ) ) );
        }

        $keys = self::get_api_keys();
        if ( empty( $keys['secret'] ) ) {
            wp_send_json_error( array( 'message' => 'Stripe is not configured in Settings.' ) );
        }

        $plan_price = get_post_meta( $plan_id, '_price', true );
        $plan_interval = get_post_meta( $plan_id, '_interval', true ) ?: 'month';

        if ( ! self::load_sdk() ) {
            wp_send_json_error( array( 'message' => 'Stripe SDK missing. Run composer install.' ) );
        }

        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiKey( $keys['secret'] );
        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiVersion( '2023-10-16' ); // Force modern SCA-ready API version

        try {
            $stripe_address = array();
            if ( $city !== '' ) {
                $stripe_address['city'] = $city;
            }
            if ( $zip !== '' ) {
                $stripe_address['postal_code'] = $zip;
            }
            if ( $country !== '' && preg_match( '/^[A-Z]{2}$/', $country ) ) {
                $stripe_address['country'] = $country;
            }

            $ip      = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
            $tkey    = 'invizo_chk_' . md5( (string) $plan_id . '|' . ( is_email( $email ) ? $email : 'guest' ) . '|' . $ip );
            $cached  = get_transient( $tkey );
            $user    = is_email( $email ) ? get_user_by( 'email', $email ) : false;
            $stripe_customer_id = '';

            if ( is_string( $cached ) && strpos( $cached, 'cus_' ) === 0 ) {
                try {
                    \InvizoSubscriptionsLiteVendor\Stripe\Customer::retrieve( $cached );
                    $stripe_customer_id = $cached;
                } catch ( Exception $e ) {
                    $stripe_customer_id = '';
                }
            }

            if ( ! $stripe_customer_id && $user ) {
                $stripe_customer_id = get_user_meta( $user->ID, 'invizo_stripe_customer_id', true );
            }

            if ( ! $stripe_customer_id && is_email( $email ) ) {
                $customers = \InvizoSubscriptionsLiteVendor\Stripe\Customer::all( array( 'email' => $email, 'limit' => 1 ) );
                if ( ! empty( $customers->data ) ) {
                    $stripe_customer_id = $customers->data[0]->id;
                }
            }

            if ( ! $stripe_customer_id ) {
                $create_params = array(
                    'metadata' => array(
                        'invizo_plan_id' => (string) $plan_id,
                    ),
                );
                if ( is_email( $email ) ) {
                    $create_params['email'] = $email;
                }
                if ( $fname !== '' ) {
                    $create_params['name'] = $fname;
                }
                if ( ! empty( $stripe_address ) ) {
                    $create_params['address'] = $stripe_address;
                }
                $customer             = \InvizoSubscriptionsLiteVendor\Stripe\Customer::create( $create_params );
                $stripe_customer_id   = $customer->id;
                set_transient( $tkey, $stripe_customer_id, HOUR_IN_SECONDS );
            } elseif ( ! empty( $stripe_address ) || $fname !== '' || is_email( $email ) ) {
                $update = array();
                if ( is_email( $email ) ) {
                    $update['email'] = $email;
                }
                if ( $fname !== '' ) {
                    $update['name'] = $fname;
                }
                if ( ! empty( $stripe_address ) ) {
                    $update['address'] = $stripe_address;
                }
                if ( ! empty( $update ) ) {
                    try {
                        \InvizoSubscriptionsLiteVendor\Stripe\Customer::update( $stripe_customer_id, $update );
                    } catch ( Exception $e ) {
                        /* non-fatal */
                    }
                }
            }

            if ( $user && $stripe_customer_id ) {
                update_user_meta( $user->ID, 'invizo_stripe_customer_id', $stripe_customer_id );
            }

            if ( $stripe_customer_id ) {
                set_transient( $tkey, $stripe_customer_id, HOUR_IN_SECONDS );
            }

            // Provision Product/Price dynamically on Stripe for standalone mode
            $stripe_price_id = get_post_meta( $plan_id, '_stripe_price_id', true );
            if ( ! $stripe_price_id ) {
                $product = \InvizoSubscriptionsLiteVendor\Stripe\Product::create([
                    'name' => get_the_title( $plan_id ) . ' - Invizo Subs',
                ]);
                $price = \InvizoSubscriptionsLiteVendor\Stripe\Price::create([
                    'product' => $product->id,
                    'unit_amount' => intval( floatval( $plan_price ) * 100 ),
                    'currency' => 'usd',
                    'recurring' => ['interval' => $plan_interval],
                ]);
                $stripe_price_id = $price->id;
                update_post_meta( $plan_id, '_stripe_price_id', $stripe_price_id );
            }

            $trial_days = 0;
            $trial_meta = get_post_meta( $plan_id, '_trial_period', true );
            if ( is_numeric( $trial_meta ) && (int) $trial_meta > 0 ) {
                $trial_days = (int) $trial_meta;
            }

            // Create Sub incomplete
            $sub_create = array(
                'customer'           => $stripe_customer_id,
                'items'              => array( array( 'price' => $stripe_price_id ) ),
                'payment_behavior'   => 'default_incomplete',
                'payment_settings'   => array( 'save_default_payment_method' => 'on_subscription' ),
                'expand'             => array( 'latest_invoice.payment_intent', 'pending_setup_intent' ),
            );
            if ( $trial_days > 0 ) {
                $sub_create['trial_period_days'] = $trial_days;
            }
            $subscription = \InvizoSubscriptionsLiteVendor\Stripe\Subscription::create( $sub_create );

            // Validate that we have a payment intent or setup intent
            $pi = $subscription->latest_invoice->payment_intent;
            $si = $subscription->pending_setup_intent;

            if ( ! $pi && ! $si ) {
                if ($subscription->status === 'active') {
                    // It was totally free or covered by balance! We don't need a confirmPayment on frontend!
                    wp_send_json_success( array(
                        'clientSecret'    => 'free_active',
                        'subscription_id' => $subscription->id,
                        'intent_id'       => 'free',
                        'intent_kind'     => 'free',
                        'subscription_dump' => $subscription->jsonSerialize()
                    ) );
                } else {
                    wp_send_json_error( array( 
                        'message' => 'Stripe failed to return a payment intent. (Price was $'.$plan_price.')',
                        'subscription_dump' => $subscription->jsonSerialize()
                    ) );
                }
            }

            $client_secret = $pi ? $pi->client_secret : $si->client_secret;
            $intent_id = $pi ? $pi->id : $si->id;

            wp_send_json_success( array(
                'clientSecret'    => $client_secret,
                'subscription_id' => $subscription->id,
                'intent_id'       => $intent_id,
                'intent_kind'     => $pi ? 'payment' : 'setup',
            ) );

        } catch ( Exception $e ) {
            wp_send_json_error( array( 'message' => $e->getMessage() ) );
        }
    }

    public static function ajax_confirm_subscription_order() {
        check_ajax_referer( 'invizo_checkout_nonce', 'nonce' );

        if ( ! self::checkout_privacy_accepted() ) {
            wp_send_json_error(
                array(
                    'message' => __( 'You must accept the Privacy Policy to subscribe.', 'invizo-subscriptions-lite' ),
                )
            );
        }

        $payment_intent  = isset( $_POST['payment_intent'] ) ? sanitize_text_field( wp_unslash( $_POST['payment_intent'] ) ) : '';
        $setup_intent    = isset( $_POST['setup_intent'] ) ? sanitize_text_field( wp_unslash( $_POST['setup_intent'] ) ) : '';
        $subscription_id = isset( $_POST['subscription_id'] ) ? sanitize_text_field( wp_unslash( $_POST['subscription_id'] ) ) : '';
        $email           = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '';
        $fname           = isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '';
        $plan_id         = isset( $_POST['plan_id'] ) ? absint( wp_unslash( $_POST['plan_id'] ) ) : 0;

        $keys = self::get_api_keys();
        if ( ! self::load_sdk() ) {
            wp_send_json_error( array( 'message' => 'Stripe SDK missing. Run composer install.' ) );
        }
        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiKey( $keys['secret'] );
        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiVersion( '2023-10-16' );

        try {
            if ( $setup_intent !== '' ) {
                $si = \InvizoSubscriptionsLiteVendor\Stripe\SetupIntent::retrieve( $setup_intent );
                if ( $si->status !== 'succeeded' ) {
                    wp_send_json_error( array( 'message' => 'Payment setup has not completed yet.' ) );
                }
            } elseif ( $payment_intent === 'free' && $subscription_id !== '' ) {
                $sub = \InvizoSubscriptionsLiteVendor\Stripe\Subscription::retrieve( $subscription_id );
                if ( $sub->status !== 'active' ) {
                    wp_send_json_error( array( 'message' => 'Subscription is not active yet.' ) );
                }
            } elseif ( $payment_intent !== '' ) {
                $intent = \InvizoSubscriptionsLiteVendor\Stripe\PaymentIntent::retrieve( $payment_intent );
                if ( $intent->status !== 'succeeded' ) {
                    wp_send_json_error( array( 'message' => 'Payment has not succeeded yet.' ) );
                }
            } else {
                wp_send_json_error( array( 'message' => 'Missing payment confirmation.' ) );
            }

            if ( ! is_email( $email ) && $subscription_id !== '' ) {
                $sub_fetch = \InvizoSubscriptionsLiteVendor\Stripe\Subscription::retrieve(
                    array(
                        'id'     => $subscription_id,
                        'expand' => array( 'customer' ),
                    )
                );
                $cust = $sub_fetch->customer;
                if ( is_string( $cust ) ) {
                    $cust = \InvizoSubscriptionsLiteVendor\Stripe\Customer::retrieve( $cust );
                }
                if ( is_object( $cust ) && ! empty( $cust->email ) ) {
                    $email = sanitize_email( $cust->email );
                }
            }

            if ( ! is_email( $email ) ) {
                wp_send_json_error(
                    array(
                        'message' => __( 'A valid email is required to finish. Enter it in the form or in the Stripe payment section.', 'invizo-subscriptions-lite' ),
                    )
                );
            }

            $country = isset( $_POST['billing_country'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_POST['billing_country'] ) ) ) : '';
            $city    = isset( $_POST['billing_city'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_city'] ) ) : '';
            $zip     = isset( $_POST['billing_postcode'] ) ? sanitize_text_field( wp_unslash( $_POST['billing_postcode'] ) ) : '';

            $guest_author = 1;
            $admins       = get_users(
                array(
                    'role'   => 'administrator',
                    'number' => 1,
                    'fields' => array( 'ID' ),
                )
            );
            if ( ! empty( $admins ) ) {
                $guest_author = (int) $admins[0]->ID;
            }

            $user   = get_user_by( 'email', $email );
            $wp_uid = 0;
            $author = $guest_author;

            if ( $user instanceof WP_User ) {
                $wp_uid = (int) $user->ID;
                $author = $wp_uid;
            }

            if ( $wp_uid && $subscription_id !== '' ) {
                $sub_link = \InvizoSubscriptionsLiteVendor\Stripe\Subscription::retrieve( $subscription_id );
                if ( is_string( $sub_link->customer ) ) {
                    update_user_meta( $wp_uid, 'invizo_stripe_customer_id', $sub_link->customer );
                }
            }

            // Create or update invizo_sub record
            $args = array(
                'post_type'   => 'invizo_sub',
                'meta_query'  => array(
                    array(
                        'key'   => '_stripe_subscription_id',
                        'value' => $subscription_id,
                    ),
                ),
                'post_status' => 'any',
            );
            $subs = get_posts( $args );

            if ( empty( $subs ) ) {
                $sub_post_id = wp_insert_post(
                    array(
                        'post_title'  => 'Sub - ' . $email . ' - ' . gmdate( 'M j, Y' ),
                        'post_type'   => 'invizo_sub',
                        'post_status' => 'publish',
                        'post_author' => $author,
                    )
                );
            } else {
                $sub_post_id = $subs[0]->ID;
            }

            if ( is_wp_error( $sub_post_id ) || ! $sub_post_id ) {
                wp_send_json_error( array( 'message' => __( 'Could not save subscription record.', 'invizo-subscriptions-lite' ) ) );
            }

            update_post_meta( $sub_post_id, '_customer_email', $email );
            update_post_meta( $sub_post_id, '_wp_user_id', $wp_uid );
            update_post_meta( $sub_post_id, '_plan_id', $plan_id );
            update_post_meta( $sub_post_id, '_stripe_subscription_id', $subscription_id );
            update_post_meta( $sub_post_id, '_last_payment_date', current_time( 'mysql' ) );
            update_post_meta( $sub_post_id, '_billing_country', $country );
            update_post_meta( $sub_post_id, '_billing_city', $city );
            update_post_meta( $sub_post_id, '_billing_postcode', $zip );

            $pending_email_verification = false;
            if ( class_exists( Subscription_Email_Verification::class, false ) ) {
                $pending_email_verification = Subscription_Email_Verification::finalize_new_subscription_status( $sub_post_id, $email, $fname );
            } else {
                update_post_meta( $sub_post_id, '_status', 'active' );
            }

            self::sync_invizo_sub_from_stripe( $sub_post_id );

            wp_send_json_success(
                array(
                    'logged_in'                   => is_user_logged_in(),
                    'guest_checkout'              => ( $wp_uid === 0 ),
                    'new_account'                 => false,
                    'pending_email_verification'  => $pending_email_verification,
                )
            );

        } catch ( Exception $e ) {
            wp_send_json_error( array( 'message' => $e->getMessage() ) );
        }
    }

    /**
     * Pull billing period and status from Stripe into invizo_sub post meta (fixes missing next payment when webhooks are off).
     *
     * @param int $sub_post_id invizo_sub post ID.
     * @return bool Whether Stripe returned a subscription.
     */
    public static function sync_invizo_sub_from_stripe( $sub_post_id ) {
        $sub_post_id = (int) $sub_post_id;
        if ( $sub_post_id <= 0 ) {
            return false;
        }

        $stripe_sub_id = get_post_meta( $sub_post_id, '_stripe_subscription_id', true );
        if ( ! is_string( $stripe_sub_id ) || strpos( $stripe_sub_id, 'sub_' ) !== 0 ) {
            return false;
        }

        $keys = self::get_api_keys();
        if ( empty( $keys['secret'] ) ) {
            return false;
        }

        if ( ! self::load_sdk() ) {
            return false;
        }

        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiKey( $keys['secret'] );
        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiVersion( '2023-10-16' );

        try {
            $sub = \InvizoSubscriptionsLiteVendor\Stripe\Subscription::retrieve( $stripe_sub_id );
        } catch ( Exception $e ) {
            return false;
        }

        if ( ! empty( $sub->current_period_end ) ) {
            update_post_meta(
                $sub_post_id,
                '_next_payment_date',
                gmdate( 'Y-m-d H:i:s', (int) $sub->current_period_end )
            );
        }

        $pending_ver = false;
        if ( class_exists( Subscription_Email_Verification::class, false ) ) {
            $pending_ver = Subscription_Email_Verification::is_subscription_pending_verification( $sub_post_id );
        }

        if ( ! $pending_ver && ! empty( $sub->status ) ) {
            update_post_meta( $sub_post_id, '_status', sanitize_text_field( $sub->status ) );
        }

        update_post_meta( $sub_post_id, '_cancel_at_period_end', ! empty( $sub->cancel_at_period_end ) ? '1' : '0' );

        return true;
    }

    /**
     * Stop auto-renewal at the end of the current billing period.
     *
     * @param string $stripe_subscription_id sub_xxx.
     * @return true|\WP_Error
     */
    public static function subscription_cancel_at_period_end( $stripe_subscription_id ) {
        if ( ! is_string( $stripe_subscription_id ) || strpos( $stripe_subscription_id, 'sub_' ) !== 0 ) {
            return new WP_Error( 'invizo_sub', __( 'Invalid subscription.', 'invizo-subscriptions-lite' ) );
        }

        $keys = self::get_api_keys();
        if ( empty( $keys['secret'] ) ) {
            return new WP_Error( 'invizo_stripe', __( 'Stripe is not configured.', 'invizo-subscriptions-lite' ) );
        }

        if ( ! self::load_sdk() ) {
            return new WP_Error( 'invizo_stripe', __( 'Stripe SDK missing.', 'invizo-subscriptions-lite' ) );
        }

        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiKey( $keys['secret'] );
        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiVersion( '2023-10-16' );

        try {
            \InvizoSubscriptionsLiteVendor\Stripe\Subscription::update(
                $stripe_subscription_id,
                array( 'cancel_at_period_end' => true )
            );
        } catch ( Exception $e ) {
            return new WP_Error( 'stripe', $e->getMessage() );
        }

        return true;
    }

    /**
     * Set default card on the Stripe customer and on active subscriptions tied to that customer.
     *
     * @param string $customer_id         cus_xxx.
     * @param string $payment_method_id   pm_xxx.
     * @param int[]  $invizo_sub_post_ids Local subscription posts to update (optional; empty = customer only).
     * @return true|\WP_Error
     */
    public static function customer_set_default_payment_method( $customer_id, $payment_method_id, array $invizo_sub_post_ids = array() ) {
        if ( ! is_string( $customer_id ) || strpos( $customer_id, 'cus_' ) !== 0 ) {
            return new WP_Error( 'invizo_customer', __( 'Invalid customer.', 'invizo-subscriptions-lite' ) );
        }
        if ( ! is_string( $payment_method_id ) || strpos( $payment_method_id, 'pm_' ) !== 0 ) {
            return new WP_Error( 'invizo_pm', __( 'Invalid payment method.', 'invizo-subscriptions-lite' ) );
        }

        $keys = self::get_api_keys();
        if ( empty( $keys['secret'] ) ) {
            return new WP_Error( 'invizo_stripe', __( 'Stripe is not configured.', 'invizo-subscriptions-lite' ) );
        }

        if ( ! self::load_sdk() ) {
            return new WP_Error( 'invizo_stripe', __( 'Stripe SDK missing.', 'invizo-subscriptions-lite' ) );
        }

        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiKey( $keys['secret'] );
        \InvizoSubscriptionsLiteVendor\Stripe\Stripe::setApiVersion( '2023-10-16' );

        try {
            $pm = \InvizoSubscriptionsLiteVendor\Stripe\PaymentMethod::retrieve( $payment_method_id );
            $pm_cus = is_string( $pm->customer ) ? $pm->customer : ( $pm->customer->id ?? '' );
            if ( $pm_cus !== $customer_id ) {
                return new WP_Error( 'invizo_pm', __( 'That card is not on your account.', 'invizo-subscriptions-lite' ) );
            }

            \InvizoSubscriptionsLiteVendor\Stripe\Customer::update(
                $customer_id,
                array(
                    'invoice_settings' => array(
                        'default_payment_method' => $payment_method_id,
                    ),
                )
            );

            foreach ( $invizo_sub_post_ids as $pid ) {
                $pid = (int) $pid;
                if ( $pid <= 0 ) {
                    continue;
                }
                $sid = get_post_meta( $pid, '_stripe_subscription_id', true );
                if ( ! is_string( $sid ) || strpos( $sid, 'sub_' ) !== 0 ) {
                    continue;
                }
                $sub = \InvizoSubscriptionsLiteVendor\Stripe\Subscription::retrieve( $sid );
                $sub_cus = is_string( $sub->customer ) ? $sub->customer : ( $sub->customer->id ?? '' );
                if ( $sub_cus !== $customer_id ) {
                    continue;
                }
                if ( ! in_array( $sub->status, array( 'active', 'trialing', 'past_due' ), true ) ) {
                    continue;
                }
                \InvizoSubscriptionsLiteVendor\Stripe\Subscription::update(
                    $sid,
                    array( 'default_payment_method' => $payment_method_id )
                );
            }
        } catch ( Exception $e ) {
            return new WP_Error( 'stripe', $e->getMessage() );
        }

        return true;
    }
}
