<?php

/**
 * Retention Offer Processor
 *
 * Processes retention offers when customers try to cancel.
 * Handles discount coupons, pause subscriptions, downgrades, and skip renewals.
 *
 * @package ArraySubs\Features\CancellationFlow\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\CancellationFlow\Services;

defined('ABSPATH') || exit;

use ArraySubs\Features\SkipRenewal\Services\PauseManager;
use ArraySubs\Features\SkipRenewal\Services\SkipManager;
use ArraySubs\Features\PlanSwitching\Services\ProrationCalculator;

/**
 * Retention offer processing
 */
class RetentionOfferProcessor
{
    /**
     * Process a retention offer acceptance
     *
     * @param int    $subscription_id Subscription ID
     * @param string $offer_type      Type of offer (discount, pause, downgrade, skip)
     * @param array  $offer_data      Additional offer data
     * @return array Result with success status and message
     */
    public static function processOffer(int $subscription_id, string $offer_type, array $offer_data = []): array
    {
        $subscription_id = absint($subscription_id);
        $subscription = arraysubs_get_subscription($subscription_id);
        $offer_id = sanitize_key($offer_data['offer_id'] ?? '');
        $offer_title = sanitize_text_field($offer_data['offer_title'] ?? '');

        if (!$subscription instanceof \WP_Post) {
            return [
                'success' => false,
                'message' => __('Subscription not found', 'arraysubs'),
            ];
        }

        $result = [];

        switch ($offer_type) {
            case 'discount':
            case 'coupon':
                $result = self::processDiscountOffer($subscription, $offer_data);
                break;

            case 'pause':
                $result = self::processPauseOffer($subscription, $offer_data);
                break;

            case 'downgrade':
                $result = self::processDowngradeOffer($subscription, $offer_data);
                break;

            case 'skip':
                $result = self::processSkipOffer($subscription, $offer_data);
                break;

            default:
                $result = [
                    'success' => false,
                    'message' => __('Invalid offer type', 'arraysubs'),
                ];
        }

        if ($result['success']) {
            // Track that offer was accepted
            update_post_meta($subscription_id, '_retention_offer_accepted', true);
            update_post_meta($subscription_id, '_retention_offer_type', sanitize_text_field($offer_type));
            update_post_meta($subscription_id, '_retention_offer_id', $offer_id ?: sanitize_key($offer_type));
            update_post_meta($subscription_id, '_accepted_retention_offer', $offer_id ?: sanitize_key($offer_type));

            $accepted_date = current_time('mysql', true);
            update_post_meta($subscription_id, '_retention_accepted_date', $accepted_date);
            update_post_meta($subscription_id, '_retention_offer_accepted_date', $accepted_date);

            $offer_record = [
                'id' => $offer_id ?: sanitize_key($offer_type),
                'type' => sanitize_text_field($offer_type),
                'title' => $offer_title,
                'accepted_date' => $accepted_date,
                'result' => $result,
            ];

            update_post_meta($subscription_id, '_retention_offer_details', $offer_record);

            $offer_history = get_post_meta($subscription_id, '_retention_offer_history', true);
            $offer_history = is_array($offer_history) ? $offer_history : [];
            $offer_history[] = $offer_record;
            update_post_meta($subscription_id, '_retention_offer_history', $offer_history);

            // Clear waiting cancellation if set
            delete_post_meta($subscription_id, '_waiting_cancellation');
            delete_post_meta($subscription_id, '_cancellation_scheduled_date');

            // Add subscription note
            if (function_exists('arraysubs_add_subscription_note')) {
                arraysubs_add_subscription_note(
                    $subscription_id,
                    self::getOfferAcceptanceNote($offer_id, $offer_type, $offer_title, $result),
                    'system',
                    0,
                    [],
                    'subscription'
                );
            }

            do_action('arraysubs_retention_offer_accepted', $subscription_id, $offer_type, $offer_data);
        }

        return $result;
    }

    /**
     * Process discount/coupon offer
     *
     * Applies a discount to the subscription's renewal orders.
     * Works with the plugin's renewal order flow regardless of payment method.
     *
     * @param \WP_Post $subscription Subscription post object
     * @param array    $offer_data   Offer configuration
     * @return array
     */
    private static function processDiscountOffer(\WP_Post $subscription, array $offer_data): array
    {
        if (!function_exists('wc_get_order')) {
            return [
                'success' => false,
                'message' => __('WooCommerce is required for discount offers', 'arraysubs'),
            ];
        }

        // Get discount configuration
        $discount_type = sanitize_key($offer_data['discount_type'] ?? 'percent');
        if (!in_array($discount_type, ['percent', 'fixed'], true)) {
            $discount_type = 'percent';
        }

        $discount_amount = max(0.0, floatval($offer_data['discount_amount'] ?? 20));
        if ($discount_type === 'percent') {
            $discount_amount = min(100.0, $discount_amount);
        }

        $duration = max(1, absint($offer_data['duration'] ?? 3));
        $offer_id = sanitize_key($offer_data['offer_id'] ?? 'discount');

        if ($discount_amount <= 0) {
            return [
                'success' => false,
                'message' => __('Discount amount must be greater than zero.', 'arraysubs'),
            ];
        }

        if (self::hasUsedDiscountOffer($subscription->ID, $offer_id, 'discount')) {
            return [
                'success' => false,
                'message' => __('This discount offer has already been used for this subscription.', 'arraysubs'),
            ];
        }

        $base_amount = self::getCurrentRecurringBaseAmount($subscription->ID);
        if ($base_amount <= 0) {
            return [
                'success' => false,
                'message' => __('This subscription does not have a renewable amount to discount.', 'arraysubs'),
            ];
        }

        $effective_amount = self::calculateDiscountedAmount($base_amount, $discount_type, $discount_amount);

        // Store retention discount info on subscription meta
        // This will be used by OrderCreation when generating renewal orders
        update_post_meta($subscription->ID, '_retention_discount_type', $discount_type);
        update_post_meta($subscription->ID, '_retention_discount_amount', $discount_amount);
        update_post_meta($subscription->ID, '_retention_discount_remaining', $duration);
        update_post_meta($subscription->ID, '_retention_discount_applied_date', current_time('mysql', true));
        update_post_meta($subscription->ID, '_retention_discount_offer_id', $offer_id);
        update_post_meta($subscription->ID, '_retention_discount_base_amount', $base_amount);
        update_post_meta($subscription->ID, '_retention_discount_effective_amount', $effective_amount);

        // Check if there's already a pending renewal order - apply discount to it
        $pending_order_id = get_post_meta($subscription->ID, '_pending_renewal_order_id', true);
        if ($pending_order_id) {
            $pending_order = wc_get_order($pending_order_id);
            if ($pending_order && $pending_order->has_status(['pending', 'on-hold'])) {
                self::applyDiscountToOrder($pending_order, $discount_type, $discount_amount, $subscription->ID);
            }
        }

        // Format success message
        if ($discount_type === 'percent') {
            $message = sprintf(
                /* translators: 1: discount percentage, 2: number of renewals */
                __('Great! You\'ll get %1$s%% off your next %2$d renewals.', 'arraysubs'),
                $discount_amount,
                $duration
            );
        } else {
            $message = sprintf(
                /* translators: 1: discount amount, 2: number of renewals */
                __('Great! You\'ll get %1$s off your next %2$d renewals.', 'arraysubs'),
                wc_price($discount_amount),
                $duration
            );
        }

        return [
            'success' => true,
            'message' => $message,
            'offer_id' => $offer_id,
            'discount_type' => $discount_type,
            'discount_amount' => $discount_amount,
            'duration' => $duration,
            'base_amount' => $base_amount,
            'effective_amount' => $effective_amount,
        ];
    }

    /**
     * Calculate a discounted amount from a base renewal amount.
     *
     * @param float  $base_amount     Base amount before discount.
     * @param string $discount_type   Discount type.
     * @param float  $discount_amount Discount amount.
     * @return float
     */
    public static function calculateDiscountedAmount(float $base_amount, string $discount_type, float $discount_amount): float
    {
        $base_amount = max(0.0, $base_amount);

        if ($discount_type === 'percent') {
            $discount_value = ($base_amount * $discount_amount) / 100;
        } else {
            $discount_value = min($discount_amount, $base_amount);
        }

        return max(0.0, round($base_amount - $discount_value, wc_get_price_decimals()));
    }

    /**
     * Apply retention discount to a WooCommerce order
     *
     * @param \WC_Order $order           Order to apply discount to
     * @param string    $discount_type   'percent' or 'fixed'
     * @param float     $discount_amount Amount to discount
     * @param int       $subscription_id Subscription ID for reference
     * @return bool
     */
    public static function applyDiscountToOrder(\WC_Order $order, string $discount_type, float $discount_amount, int $subscription_id): bool
    {
        try {
            $discount_type = sanitize_key($discount_type);
            if (!in_array($discount_type, ['percent', 'fixed'], true)) {
                $discount_type = 'percent';
            }

            $discount_amount = max(0.0, $discount_amount);
            if ($discount_type === 'percent') {
                $discount_amount = min(100.0, $discount_amount);
            }

            if ($discount_amount <= 0) {
                return false;
            }

            // Remove any existing retention discount fee items before recalculating the cap.
            $removed_existing = false;
            foreach ($order->get_items('fee') as $item_id => $item) {
                if ($item->get_meta('_retention_discount') === 'yes') {
                    $order->remove_item($item_id);
                    $removed_existing = true;
                }
            }

            if ($removed_existing) {
                $order->calculate_totals();
            }

            // Calculate discount based on type
            $order_total = $order->get_subtotal();

            if ($discount_type === 'percent') {
                $discount_value = ($order_total * $discount_amount) / 100;
            } else {
                $discount_value = min($discount_amount, $order_total); // Don't exceed order total
            }

            $discount_value = min($discount_value, max(0.0, (float) $order->get_total()));

            if ($discount_value <= 0) {
                return false;
            }

            // Add negative fee for discount
            $fee = new \WC_Order_Item_Fee();
            $fee->set_name(__('Retention Discount', 'arraysubs'));
            $fee->set_amount(-$discount_value);
            $fee->set_total(-$discount_value);
            $fee->set_tax_status('none');
            $fee->add_meta_data('_retention_discount', 'yes', true);
            $fee->add_meta_data('_subscription_id', $subscription_id, true);
            $fee->add_meta_data('_discount_type', $discount_type, true);
            $fee->add_meta_data('_discount_amount', $discount_amount, true);

            $order->add_item($fee);
            $order->calculate_totals();
            $order->save();

            // Add order note
            $order->add_order_note(
                sprintf(
                    /* translators: 1: discount value, 2: subscription ID */
                    __('Retention discount of %1$s applied (Subscription #%2$d)', 'arraysubs'),
                    wc_price($discount_value),
                    $subscription_id
                )
            );

            return true;
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Check whether an order actually received a retention discount fee.
     *
     * @param \WC_Order $order           Order object.
     * @param int       $subscription_id Subscription ID.
     * @return bool
     */
    public static function orderHasRetentionDiscount(\WC_Order $order, int $subscription_id): bool
    {
        foreach ($order->get_items('fee') as $item) {
            if (
                $item->get_meta('_retention_discount') === 'yes'
                && absint($item->get_meta('_subscription_id')) === $subscription_id
            ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Decrement remaining discount cycles after successful payment
     *
     * Should be called when a renewal order is paid.
     *
     * @param int $subscription_id Subscription ID
     * @return int Remaining discount cycles
     */
    public static function decrementDiscountCycles(int $subscription_id): int
    {
        $remaining = absint(get_post_meta($subscription_id, '_retention_discount_remaining', true));

        if ($remaining > 0) {
            --$remaining;
            update_post_meta($subscription_id, '_retention_discount_remaining', $remaining);

            // If no more discount cycles, clean up
            if ($remaining === 0) {
                delete_post_meta($subscription_id, '_retention_discount_type');
                delete_post_meta($subscription_id, '_retention_discount_amount');
                delete_post_meta($subscription_id, '_retention_discount_base_amount');
                delete_post_meta($subscription_id, '_retention_discount_effective_amount');

                // Add note
                if (function_exists('arraysubs_add_subscription_note')) {
                    arraysubs_add_subscription_note(
                        $subscription_id,
                        __('Retention discount period has ended', 'arraysubs'),
                        'system',
                        0,
                        [],
                        'subscription'
                    );
                }
            }
        }

        return $remaining;
    }

    /**
     * Check if subscription has active retention discount
     *
     * @param int $subscription_id Subscription ID
     * @return array|false Discount info or false if no active discount
     */
    public static function getActiveDiscount(int $subscription_id): array|false
    {
        $remaining = absint(get_post_meta($subscription_id, '_retention_discount_remaining', true));

        if ($remaining <= 0) {
            return false;
        }

        $type = get_post_meta($subscription_id, '_retention_discount_type', true) ?: 'percent';
        $amount = floatval(get_post_meta($subscription_id, '_retention_discount_amount', true));
        $base_amount = self::getCurrentRecurringBaseAmount($subscription_id);

        return [
            'type' => $type,
            'amount' => $amount,
            'remaining' => $remaining,
            'offer_id' => get_post_meta($subscription_id, '_retention_discount_offer_id', true) ?: 'discount',
            'base_amount' => $base_amount,
            'effective_amount' => self::calculateDiscountedAmount($base_amount, $type, $amount),
        ];
    }

    /**
     * Get the active renewal base amount for a subscription before discount.
     *
     * @param int $subscription_id Subscription ID.
     * @return float
     */
    public static function getCurrentRecurringBaseAmount(int $subscription_id): float
    {
        $subscription_id = absint($subscription_id);

        if ($subscription_id <= 0) {
            return 0.0;
        }

        $recurring_amount = floatval(get_post_meta($subscription_id, '_recurring_amount', true));
        $different_renewal_price = floatval(get_post_meta($subscription_id, '_different_renewal_price', true));
        $different_renewal_price_after = absint(get_post_meta($subscription_id, '_different_renewal_price_after', true)) ?: 1;
        $completed_payments = absint(get_post_meta($subscription_id, '_completed_payments', true));

        if ($different_renewal_price > 0 && $completed_payments >= $different_renewal_price_after) {
            return $different_renewal_price;
        }

        return $recurring_amount;
    }

    /**
     * Get the effective displayed recurring amount for a subscription.
     *
     * @param int $subscription_id Subscription ID.
     * @return float
     */
    public static function getEffectiveRecurringAmount(int $subscription_id): float
    {
        $base_amount = self::getCurrentRecurringBaseAmount($subscription_id);
        $active_discount = self::getActiveDiscount($subscription_id);

        if (!is_array($active_discount)) {
            return $base_amount;
        }

        return floatval($active_discount['effective_amount'] ?? $base_amount);
    }

    /**
     * Process pause offer
     *
     * @param \WP_Post $subscription Subscription post object
     * @param array    $offer_data   Offer configuration
     * @return array
     */
    private static function processPauseOffer(\WP_Post $subscription, array $offer_data): array
    {
        $pause_duration = absint($offer_data['pause_duration'] ?? 30);
        $max_pause = absint($offer_data['max_pause_duration'] ?? 90);

        // Limit pause duration
        $pause_duration = max(1, min($pause_duration, $max_pause));
        $pause_manager = new PauseManager();
        $paused = $pause_manager->pause($subscription->ID, $pause_duration, 0, 'retention_offer');

        if (!$paused) {
            return [
                'success' => false,
                'message' => __('Unable to pause this subscription.', 'arraysubs'),
            ];
        }

        $resume_date = get_post_meta($subscription->ID, '_pause_end_date', true);

        return [
            'success' => true,
            'message' => sprintf(
                /* translators: 1: number of days, 2: resume date */
                __('Your subscription is now paused for %1$d days. It will automatically resume on %2$s.', 'arraysubs'),
                $pause_duration,
                arraysubs_format_date_local($resume_date, get_option('date_format'))
            ),
            'resume_date' => $resume_date,
            'pause_duration' => $pause_duration,
        ];
    }

    /**
     * Process downgrade offer
     *
     * Redirects to plan switching with downgrade options
     *
     * @param \WP_Post $subscription Subscription post object
     * @param array    $offer_data   Offer configuration
     * @return array
     */
    private static function processDowngradeOffer(\WP_Post $subscription, array $offer_data): array
    {
        // Get available downgrade options for this subscription's product
        $product_id = get_post_meta($subscription->ID, '_product_id', true);
        $variation_id = get_post_meta($subscription->ID, '_variation_id', true);

        $current_product_id = $variation_id ?: $product_id;

        if (!$current_product_id) {
            return [
                'success' => false,
                'message' => __('Unable to find current subscription product', 'arraysubs'),
            ];
        }

        // Get downgrade products
        $downgrade_products = array_map('absint', (array) get_post_meta($current_product_id, '_arraysubs_downgrade_products', true));
        $downgrade_products = array_values(array_filter($downgrade_products));

        if (empty($downgrade_products)) {
            return [
                'success' => false,
                'message' => __('No downgrade options available for your current plan', 'arraysubs'),
            ];
        }

        // If a specific target product was specified, initiate the switch
        if (!empty($offer_data['target_product_id'])) {
            $target_product_id = absint($offer_data['target_product_id']);

            // Verify it's in the allowed downgrade list
            if (!in_array($target_product_id, $downgrade_products, true)) {
                return [
                    'success' => false,
                    'message' => __('Invalid downgrade product', 'arraysubs'),
                ];
            }

            if (function_exists('arraysubs_has_pending_switch') && arraysubs_has_pending_switch($subscription->ID)) {
                return [
                    'success' => false,
                    'message' => __('This subscription already has a pending plan switch.', 'arraysubs'),
                ];
            }

            $target_product = wc_get_product($target_product_id);
            if (!$target_product) {
                return [
                    'success' => false,
                    'message' => __('Downgrade product not found', 'arraysubs'),
                ];
            }

            $proration = ProrationCalculator::calculate(
                $subscription->ID,
                $target_product_id,
                ProrationCalculator::APPLY_AT_RENEWAL
            );

            if (empty($proration['success'])) {
                return [
                    'success' => false,
                    'message' => $proration['error'] ?? __('Failed to schedule downgrade.', 'arraysubs'),
                ];
            }

            $is_variation = $target_product->is_type('variation');
            $stored = function_exists('arraysubs_set_pending_switch') && arraysubs_set_pending_switch($subscription->ID, [
                'target_product_id'    => $is_variation ? $target_product->get_parent_id() : $target_product_id,
                'target_variation_id'  => $is_variation ? $target_product_id : 0,
                'quantity'             => 1,
                'switch_type'          => $proration['switch_type'] ?? 'downgrade',
                'switch_fee'           => isset($proration['switch_fee']) ? (float) $proration['switch_fee'] : 0,
                'new_price'            => ProrationCalculator::resolveProductPrice($target_product),
                'new_billing_period'   => $proration['new_plan']['billing_period'] ?? '',
                'new_billing_interval' => (int) ($proration['new_plan']['billing_interval'] ?? 0),
                'requested_by'         => 'customer',
            ]);

            if (!$stored) {
                return [
                    'success' => false,
                    'message' => __('Failed to schedule downgrade.', 'arraysubs'),
                ];
            }

            return [
                'success' => true,
                'message' => __('Downgrade scheduled for the next renewal.', 'arraysubs'),
                'action' => 'pending_switch_created',
                'pending_switch' => function_exists('arraysubs_get_pending_switch_details')
                    ? arraysubs_get_pending_switch_details($subscription->ID)
                    : arraysubs_get_pending_switch($subscription->ID),
            ];
        }

        // Return available downgrade options for the frontend to display
        return [
            'success' => true,
            'message' => __('Here are your available downgrade options:', 'arraysubs'),
            'action' => 'show_downgrade_options',
            'downgrade_products' => $downgrade_products,
        ];
    }

    /**
     * Process skip renewal offer
     *
     * @param \WP_Post $subscription Subscription post object
     * @param array    $offer_data   Offer configuration
     * @return array
     */
    private static function processSkipOffer(\WP_Post $subscription, array $offer_data): array
    {
        $next_payment_date = get_post_meta($subscription->ID, '_next_payment_date', true);
        $skip_cycles = absint($offer_data['skip_cycles'] ?? 1);
        $max_cycles = absint($offer_data['max_skip_cycles'] ?? 3);
        $skip_cycles = max(1, min($skip_cycles, $max_cycles));

        $skip_manager = new SkipManager();
        $skipped = $skip_manager->skip($subscription->ID, $skip_cycles, 0);

        if (!$skipped) {
            return [
                'success' => false,
                'message' => __('Unable to skip this renewal.', 'arraysubs'),
            ];
        }

        $new_next_payment = get_post_meta($subscription->ID, '_next_payment_date', true);

        return [
            'success' => true,
            'message' => sprintf(
                /* translators: %s: new payment date */
                __('Your next renewal has been skipped. Your new next payment date is %s.', 'arraysubs'),
                arraysubs_format_date_local($new_next_payment, get_option('date_format'))
            ),
            'new_next_payment_date' => $new_next_payment,
            'skipped_date' => $next_payment_date,
        ];
    }

    /**
     * Get available retention offers for a subscription
     *
     * @param int         $subscription_id  Subscription ID
     * @param string|null $selected_reason  Selected cancellation reason (optional)
     * @return array
     */
    public static function getAvailableOffers(int $subscription_id, ?string $selected_reason = null): array
    {
        $subscription_id = absint($subscription_id);
        $subscription = arraysubs_get_subscription($subscription_id);

        if (!$subscription instanceof \WP_Post) {
            return [];
        }

        if (
            function_exists('arraysubs_subscription_supports_retention_offers')
            && !arraysubs_subscription_supports_retention_offers($subscription_id)
        ) {
            return [];
        }

        // Get configured offers
        $offers = apply_filters('arraysubs_default_retention_offers', []);

        // Get settings for which offers are enabled
        $enabled_offers = arraysubs_get_setting('cancellation.retention_offers', []);

        $available = [];

        foreach ($offers as $offer_id => $offer) {
            // Check if globally enabled
            if (!$offer['enabled']) {
                continue;
            }

            // Check if offer matches the selected reason
            if (!self::offerMatchesReason($offer, $selected_reason)) {
                continue;
            }

            // Check product-specific rules
            if (!self::offerAppliesTo($offer, $subscription)) {
                continue;
            }

            // Check customer eligibility
            if (!self::customerEligibleFor($offer, $subscription)) {
                continue;
            }

            if (
                in_array($offer['type'], ['discount', 'coupon'], true)
                && self::hasUsedDiscountOffer($subscription->ID, (string) $offer_id, (string) $offer['type'])
            ) {
                continue;
            }

            // For downgrade offers, check if downgrade products exist
            if ($offer['type'] === 'downgrade') {
                $product_id = get_post_meta($subscription_id, '_product_id', true);
                $variation_id = get_post_meta($subscription_id, '_variation_id', true);
                $downgrade_products = array_filter(array_map('absint', (array) get_post_meta($variation_id ?: $product_id, '_arraysubs_downgrade_products', true)));

                if (empty($downgrade_products)) {
                    continue;
                }
            }

            if ($offer['type'] === 'pause') {
                $pause_manager = new PauseManager();
                $can_pause = $pause_manager->canPause($subscription_id);

                if (empty($can_pause['can_pause'])) {
                    continue;
                }
            }

            if ($offer['type'] === 'skip') {
                $skip_manager = new SkipManager();
                $can_skip = $skip_manager->canSkip($subscription_id);

                if (empty($can_skip['can_skip'])) {
                    continue;
                }
            }

            // For contact support offers, check if URL is configured
            if ($offer['type'] === 'contact') {
                $support_url = $offer['config']['contact_url'] ?? '';
                if (empty($support_url)) {
                    continue;
                }
            }

            $available[$offer_id] = $offer;
        }

        return apply_filters('arraysubs_available_retention_offers', $available, $subscription_id, $selected_reason);
    }

    /**
     * Check if offer matches the selected cancellation reason
     *
     * @param array       $offer           Offer configuration
     * @param string|null $selected_reason Selected reason key
     * @return bool
     */
    private static function offerMatchesReason(array $offer, ?string $selected_reason): bool
    {
        // If no reason is selected yet, show all offers
        if (empty($selected_reason)) {
            return true;
        }

        // If offer has no trigger_reasons configured (or empty), show for all reasons
        $trigger_reasons = $offer['trigger_reasons'] ?? [];
        if (empty($trigger_reasons)) {
            return true;
        }

        // Check if "_all" is in trigger_reasons (show for all)
        if (in_array('_all', $trigger_reasons, true)) {
            return true;
        }

        // Check if selected reason is in the trigger_reasons
        return in_array($selected_reason, $trigger_reasons, true);
    }

    /**
     * Check if offer applies to subscription's product
     *
     * @param array    $offer        Offer configuration
     * @param \WP_Post $subscription Subscription post
     * @return bool
     */
    private static function offerAppliesTo(array $offer, \WP_Post $subscription): bool
    {
        // Check product restrictions
        if (!empty($offer['config']['product_ids'])) {
            $product_id = get_post_meta($subscription->ID, '_product_id', true);
            $variation_id = get_post_meta($subscription->ID, '_variation_id', true);

            $current_product = $variation_id ?: $product_id;

            if (!in_array($current_product, $offer['config']['product_ids'], true)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Check if customer is eligible for offer
     *
     * @param array    $offer        Offer configuration
     * @param \WP_Post $subscription Subscription post
     * @return bool
     */
    private static function customerEligibleFor(array $offer, \WP_Post $subscription): bool
    {
        $config = $offer['config'] ?? [];

        // Check minimum subscription age
        $min_age = $config['min_subscription_age_days'] ?? 0;
        if ($min_age > 0) {
            $start_date = get_post_meta($subscription->ID, '_start_date', true);
            if (empty($start_date)) {
                return false;
            }

            $start_timestamp = strtotime($start_date);
            if (false === $start_timestamp) {
                return false;
            }

            $age_days = (int) ((time() - $start_timestamp) / DAY_IN_SECONDS);
            if ($age_days < $min_age) {
                return false;
            }
        }

        // Check subscription value condition (recurring price)
        if (!self::checkSubscriptionValueCondition($config, $subscription)) {
            return false;
        }

        // Check user total value condition (lifetime spend)
        if (!self::checkUserTotalValueCondition($config, $subscription)) {
            return false;
        }

        // Check remaining days condition
        if (!self::checkRemainingDaysCondition($config, $subscription)) {
            return false;
        }

        return true;
    }

    /**
     * Check subscription value condition
     *
     * Compares the subscription's recurring price against configured min/max thresholds.
     *
     * @param array    $config       Offer config with min/max_subscription_value.
     * @param \WP_Post $subscription Subscription post.
     * @return bool
     */
    private static function checkSubscriptionValueCondition(array $config, \WP_Post $subscription): bool
    {
        $min_value = floatval($config['min_subscription_value'] ?? 0);
        $max_value = floatval($config['max_subscription_value'] ?? 0);

        if ($min_value <= 0 && $max_value <= 0) {
            return true;
        }

        $raw_subscription_price = get_post_meta($subscription->ID, '_subscription_price', true);
        if ('' === $raw_subscription_price || null === $raw_subscription_price || !is_numeric($raw_subscription_price)) {
            return false;
        }

        $subscription_price = floatval($raw_subscription_price);

        if ($min_value > 0 && $subscription_price < $min_value) {
            return false;
        }

        if ($max_value > 0 && $subscription_price > $max_value) {
            return false;
        }

        return true;
    }

    /**
     * Check user total value condition
     *
     * Compares the customer's total WooCommerce order spend against configured thresholds.
     *
     * @param array    $config       Offer config with min/max_user_total_value.
     * @param \WP_Post $subscription Subscription post.
     * @return bool
     */
    private static function checkUserTotalValueCondition(array $config, \WP_Post $subscription): bool
    {
        $min_value = floatval($config['min_user_total_value'] ?? 0);
        $max_value = floatval($config['max_user_total_value'] ?? 0);

        if ($min_value <= 0 && $max_value <= 0) {
            return true;
        }

        $customer_id = absint(get_post_meta($subscription->ID, '_customer_id', true));
        if ($customer_id <= 0) {
            return false;
        }

        $customer = new \WC_Customer($customer_id);
        if (0 === $customer->get_id()) {
            return false;
        }

        $total_spent = floatval($customer->get_total_spent());

        if ($min_value > 0 && $total_spent < $min_value) {
            return false;
        }

        if ($max_value > 0 && $total_spent > $max_value) {
            return false;
        }

        return true;
    }

    /**
     * Check remaining days condition
     *
     * Compares the subscription's remaining days (until next payment or end date)
     * against configured min/max thresholds. Uses _end_date for fixed-period
     * subscriptions and _next_payment_date otherwise.
     *
     * @param array    $config       Offer config with min/max_remaining_days.
     * @param \WP_Post $subscription Subscription post.
     * @return bool
     */
    private static function checkRemainingDaysCondition(array $config, \WP_Post $subscription): bool
    {
        $min_days = absint($config['min_remaining_days'] ?? 0);
        $max_days = absint($config['max_remaining_days'] ?? 0);

        if ($min_days <= 0 && $max_days <= 0) {
            return true;
        }

        $remaining_days = self::getSubscriptionRemainingDays($subscription);

        if (false === $remaining_days) {
            return false;
        }

        if ($min_days > 0 && $remaining_days < $min_days) {
            return false;
        }

        if ($max_days > 0 && $remaining_days > $max_days) {
            return false;
        }

        return true;
    }

    /**
     * Get remaining days for a subscription
     *
     * For fixed-period subscriptions, uses the end date.
     * For recurring subscriptions, uses the next payment date.
     *
     * @param \WP_Post $subscription Subscription post.
     * @return int|false Remaining days (floored) or false if no date available.
     */
    private static function getSubscriptionRemainingDays(\WP_Post $subscription): int|false
    {
        $end_date = get_post_meta($subscription->ID, '_end_date', true);
        $next_payment = get_post_meta($subscription->ID, '_next_payment_date', true);
        $target_timestamp = false;

        if (!empty($end_date) && '0000-00-00 00:00:00' !== $end_date) {
            $end_timestamp = strtotime($end_date);
            if (false !== $end_timestamp) {
                $target_timestamp = $end_timestamp;
            }
        }

        if (!empty($next_payment)) {
            $next_payment_timestamp = strtotime($next_payment);
            if (false !== $next_payment_timestamp && (false === $target_timestamp || $next_payment_timestamp < $target_timestamp)) {
                $target_timestamp = $next_payment_timestamp;
            }
        }

        if (false === $target_timestamp) {
            return false;
        }

        $now = time();

        $diff_seconds = $target_timestamp - $now;

        return max(0, (int) floor($diff_seconds / DAY_IN_SECONDS));
    }

    /**
     * Check whether a discount offer has already been used by this subscription.
     *
     * @param int    $subscription_id Subscription ID.
     * @param string $offer_id        Offer ID.
     * @param string $offer_type      Offer type.
     * @return bool
     */
    private static function hasUsedDiscountOffer(int $subscription_id, string $offer_id, string $offer_type): bool
    {
        if (!empty(get_post_meta($subscription_id, '_retention_discount_applied_date', true))) {
            return true;
        }

        $accepted_offer = get_post_meta($subscription_id, '_accepted_retention_offer', true);
        $accepted_type = get_post_meta($subscription_id, '_retention_offer_type', true);

        if ($accepted_offer === $offer_id || $accepted_type === $offer_type || $accepted_type === 'coupon') {
            return true;
        }

        $history = get_post_meta($subscription_id, '_retention_offer_history', true);
        $history = is_array($history) ? $history : [];

        foreach ($history as $item) {
            if (!is_array($item)) {
                continue;
            }

            if (($item['id'] ?? '') === $offer_id || ($item['type'] ?? '') === $offer_type || ($item['type'] ?? '') === 'coupon') {
                return true;
            }
        }

        return false;
    }

    /**
     * Build a detailed note for accepted retention offers.
     *
     * @param string $offer_id    Offer ID.
     * @param string $offer_type  Offer type.
     * @param string $offer_title Offer title.
     * @param array  $result      Result data.
     * @return string
     */
    private static function getOfferAcceptanceNote(string $offer_id, string $offer_type, string $offer_title, array $result): string
    {
        $label = $offer_title ?: ucfirst(str_replace(['_', '-'], ' ', $offer_id ?: $offer_type));

        if (($offer_type === 'coupon' || $offer_type === 'discount') && isset($result['discount_type'], $result['discount_amount'], $result['duration'])) {
            $discount_value = $result['discount_type'] === 'percent'
                ? sprintf('%s%%', $result['discount_amount'])
                : wp_strip_all_tags(wc_price((float) $result['discount_amount']));

            return sprintf(
                /* translators: 1: offer label, 2: discount value, 3: number of renewals */
                __('Customer accepted retention offer "%1$s" (%2$s off for the next %3$d renewals).', 'arraysubs'),
                $label,
                $discount_value,
                absint($result['duration'])
            );
        }

        return sprintf(
            /* translators: %s: offer label */
            __('Customer accepted retention offer "%s".', 'arraysubs'),
            $label
        );
    }
}
