<?php

/**
 * CancellationFlow Hooks
 *
 * Handles cancellation reasons collection, retention offers display,
 * and form builder functionality for the cancellation flow.
 *
 * @package ArraySubs\Features\CancellationFlow\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\CancellationFlow\Services;

defined('ABSPATH') || exit;

/**
 * CancellationFlow hooks integration
 */
class Hooks
{
    /**
     * Enable auto-loading via AbstractLoader
     *
     * @var bool
     */
    public static $loadable = true;

    /**
     * Constructor - Register hooks
     */
    public function __construct()
    {
        // Hook into subscription cancellation to collect reasons
        add_action('arraysubs_data_cancelled', [$this, 'onSubscriptionCancelled'], 10, 3);
        add_action('arraysubs_data_waiting_cancellation', [$this, 'onWaitingCancellation'], 10, 2);

        // Hook into cancellation undo to clear retention data
        add_action('arraysubs_data_cancellation_undone', [$this, 'onCancellationUndone'], 10, 1);

        // Block renewal-invoice generation while a subscription is in the
        // pending-cancellation window. This is the canonical gate the
        // bulk renewal cron (RecurringBilling\Services\Hooks::doGenerateUpcomingRenewals)
        // and the per-subscription scheduled invoice job both consult.
        add_filter('arraysubs_should_generate_renewal_invoice', [$this, 'blockRenewalDuringPendingCancellation'], 10, 2);

        // Register default retention offers
        add_filter('arraysubs_default_retention_offers', [$this, 'getDefaultRetentionOffers']);

        // Handle scheduled subscription resume (from pause retention offer)
        add_action(\ArraySubs\Supports\ActionScheduler::HOOK_RESUME_SUBSCRIPTION, [$this, 'handleScheduledResume'], 10, 1);

        // Decrement retention discount cycles when renewal is paid
        add_action('arraysubs_renewal_payment_complete', [$this, 'decrementRetentionDiscount'], 10, 2);
    }

    /**
     * Handle subscription cancelled event
     *
     * @param int      $subscription_id Subscription ID.
     * @param \WP_Post $subscription    Subscription post.
     * @param string   $cancel_type     'immediate' or 'end_of_period'.
     */
    public function onSubscriptionCancelled(int $subscription_id, \WP_Post $subscription, string $cancel_type): void
    {
        $subscription_id = absint($subscription_id);
        if ($subscription_id <= 0) {
            return;
        }

        $reason = (string) get_post_meta($subscription_id, '_cancellation_reason', true);

        // Log the cancellation with reason for analytics
        $this->logCancellation($subscription_id, $cancel_type, $reason);

        // Fire action for email notifications
        do_action('arraysubs_cancellation_reason_collected', $subscription_id, $reason, $cancel_type);

        /**
         * Fires when a subscription is cancelled, allowing pro gateways
         * to clean up remote billing context.
         *
         * @param int    $subscription_id Subscription ID.
         * @param string $gateway_slug    Payment gateway slug (may be empty).
         */
        $gateway_slug = get_post_meta($subscription_id, '_payment_gateway', true);
        if (!empty($gateway_slug)) {
            do_action('arraysubs_gateway_subscription_cancelled', $subscription_id, $gateway_slug);
        }
    }

    /**
     * Handle waiting cancellation event
     *
     * @param int    $subscription_id Subscription ID
     * @param string $reason          Cancellation reason
     */
    public function onWaitingCancellation(int $subscription_id, string $reason): void
    {
        $subscription_id = absint($subscription_id);
        if ($subscription_id <= 0) {
            return;
        }

        // Log scheduled cancellation
        $this->logCancellation($subscription_id, 'end_of_period', $reason);
    }

    /**
     * Handle cancellation undone event
     *
     * @param int $subscription_id Subscription ID
     */
    public function onCancellationUndone(int $subscription_id): void
    {
        $subscription_id = absint($subscription_id);
        if ($subscription_id <= 0) {
            return;
        }

        // Track that a retention offer was effectively accepted (they kept the subscription)
        $retention_offer_shown = get_post_meta($subscription_id, '_retention_offer_shown', true);
        if ($retention_offer_shown) {
            update_post_meta($subscription_id, '_retention_offer_accepted', true);
            update_post_meta($subscription_id, '_retention_accepted_date', current_time('mysql', true));

            // Clear the cancellation reason since they're staying
            delete_post_meta($subscription_id, '_cancellation_reason');
            delete_post_meta($subscription_id, '_cancellation_reason_details');

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

    /**
     * Prevent renewal-invoice generation while a subscription is in the
     * pending-cancellation window.
     *
     * Without this gate, the hourly renewal cron generates invoices up to
     * `invoice_hours_before_due` hours before the next payment date.
     * Because the cancellation is scheduled for that same date, the
     * renewal would be created (and auto-charged) hours before the
     * cancellation cron fires — billing the customer for a period they
     * have already chosen to cancel. The gate is gateway-agnostic: every
     * gateway's renewal flow ultimately funnels through the local
     * RenewalProcessor pipeline, so blocking here covers Stripe, PayPal,
     * Paddle, and any future gateway uniformly.
     *
     * The complementary protections live in cancellation-helpers.php
     * (which unschedules in-flight per-subscription jobs at the moment
     * the cancel is scheduled) and RenewalProcessor (which short-circuits
     * if a charge attempt slips through anyway).
     *
     * Hook signature: arraysubs_should_generate_renewal_invoice ($should_generate, $subscription)
     *
     * @param bool     $should_generate Current decision from upstream filters.
     * @param \WP_Post $subscription    Subscription post.
     * @return bool
     */
    public function blockRenewalDuringPendingCancellation(bool $should_generate, \WP_Post $subscription): bool
    {
        if (!$should_generate) {
            return false;
        }

        return !\arraysubs_is_waiting_cancellation($subscription->ID);
    }

    /**
     * Get default retention offers with settings override
     *
     * @param array $offers Existing offers
     * @return array
     */
    public function getDefaultRetentionOffers(array $offers): array
    {
        // Get offer settings
        $settings_offers = arraysubs_get_setting('cancellation.retention_offers', []);

        // Helper to get non-empty value or default
        $get_text = function ($offer_key, $field, $default) use ($settings_offers) {
            $value = $settings_offers[$offer_key][$field] ?? '';
            return !empty(trim($value)) ? $value : $default;
        };

        $format_text = static function (string $text, array $replacements): string {
            return strtr($text, $replacements);
        };

        // Helper to pull retention condition config for an offer.
        $get_conditions = function ($offer_key) use ($settings_offers) {
            $offer_settings = $settings_offers[$offer_key] ?? [];
            return [
                'min_subscription_age_days' => absint($offer_settings['min_subscription_age_days'] ?? 0),
                'min_subscription_value'    => floatval($offer_settings['min_subscription_value'] ?? 0),
                'max_subscription_value'    => floatval($offer_settings['max_subscription_value'] ?? 0),
                'min_user_total_value'      => floatval($offer_settings['min_user_total_value'] ?? 0),
                'max_user_total_value'      => floatval($offer_settings['max_user_total_value'] ?? 0),
                'min_remaining_days'        => absint($offer_settings['min_remaining_days'] ?? 0),
                'max_remaining_days'        => absint($offer_settings['max_remaining_days'] ?? 0),
            ];
        };

        $discount_percent = (int) ($settings_offers['discount']['discount_percent'] ?? 20);
        $discount_cycles = (int) ($settings_offers['discount']['discount_cycles'] ?? 2);
        $discount_replacements = [
            '{percent}' => (string) $discount_percent,
            '{cycles}' => (string) $discount_cycles,
        ];

        $defaults = [
            'discount' => [
                'id' => 'discount',
                'type' => 'coupon',
                'title' => $format_text(
                    $get_text('discount', 'headline', __('Stay and Save!', 'arraysubs')),
                    $discount_replacements
                ),
                'description' => $format_text(
                    $get_text(
                        'discount',
                        'description',
                        __('We\'ll give you {percent}% off for the next {cycles} billing cycles.', 'arraysubs')
                    ),
                    $discount_replacements
                ),
                'enabled' => (bool) ($settings_offers['discount']['enabled'] ?? false),
                'trigger_reasons' => $settings_offers['discount']['trigger_reasons'] ?? [],
                'config' => array_merge(
                    [
                        'discount_type' => 'percent',
                        'discount_amount' => $discount_percent,
                        'duration' => $discount_cycles,
                    ],
                    $get_conditions('discount')
                ),
            ],
            'pause' => [
                'id' => 'pause',
                'type' => 'pause',
                'title' => $get_text('pause', 'headline', __('Pause your subscription', 'arraysubs')),
                'description' => $get_text('pause', 'description', __('Take a break without losing your subscription. Resume anytime.', 'arraysubs')),
                'enabled' => (bool) ($settings_offers['pause']['enabled'] ?? false),
                'trigger_reasons' => $settings_offers['pause']['trigger_reasons'] ?? [],
                'config' => array_merge(
                    [
                        'max_pause_duration' => (int) ($settings_offers['pause']['max_pause_days'] ?? 30),
                    ],
                    $get_conditions('pause')
                ),
            ],
            'downgrade' => [
                'id' => 'downgrade',
                'type' => 'downgrade',
                'title' => $get_text('downgrade', 'headline', __('Switch to a smaller plan', 'arraysubs')),
                'description' => $get_text('downgrade', 'description', __('Keep using our service with a more affordable plan.', 'arraysubs')),
                'enabled' => (bool) ($settings_offers['downgrade']['enabled'] ?? false),
                'trigger_reasons' => $settings_offers['downgrade']['trigger_reasons'] ?? [],
                'config' => $get_conditions('downgrade'),
            ],
            'contact_support' => [
                'id' => 'contact_support',
                'type' => 'contact',
                'title' => $get_text('contact_support', 'headline', __('Talk to us first', 'arraysubs')),
                'description' => $get_text('contact_support', 'description', __("Let's see if we can help before you go.", 'arraysubs')),
                'enabled' => (bool) ($settings_offers['contact_support']['enabled'] ?? false),
                'trigger_reasons' => $settings_offers['contact_support']['trigger_reasons'] ?? [],
                'config' => array_merge(
                    [
                        'contact_url' => $settings_offers['contact_support']['support_url'] ?? '',
                        'button_text' => $get_text('contact_support', 'button_text', __('Contact Support', 'arraysubs')),
                    ],
                    $get_conditions('contact_support')
                ),
            ],
        ];

        return array_merge($defaults, $offers);
    }

    /**
     * Log cancellation data for analytics
     *
     * @param int    $subscription_id Subscription ID
     * @param string $cancel_type     Type of cancellation
     * @param string $reason          Reason for cancellation
     */
    private function logCancellation(int $subscription_id, string $cancel_type, string $reason): void
    {
        // Store cancellation timestamp
        update_post_meta($subscription_id, '_cancelled_date', current_time('mysql', true));
        update_post_meta($subscription_id, '_cancellation_type', sanitize_text_field($cancel_type));

        // Get additional analytics data
        $subscription = arraysubs_get_subscription($subscription_id);
        if ($subscription instanceof \WP_Post) {
            $start_date = get_post_meta($subscription_id, '_start_date', true);
            $completed_payments = (int) get_post_meta($subscription_id, '_completed_payments', true);

            // Calculate subscription age in days
            if ($start_date) {
                $age_days = (int) ((time() - strtotime($start_date)) / DAY_IN_SECONDS);
                update_post_meta($subscription_id, '_subscription_age_at_cancel', $age_days);
            }

            update_post_meta($subscription_id, '_payments_at_cancel', $completed_payments);
        }
    }

    /**
     * Handle scheduled subscription resume (from pause retention offer)
     *
     * @param int $subscription_id Subscription ID
     */
    public function handleScheduledResume(int $subscription_id): void
    {
        $subscription_id = absint($subscription_id);

        // Use lock to prevent concurrent execution
        if (!\ArraySubs\Supports\ActionScheduler::acquireLock(
            \ArraySubs\Supports\ActionScheduler::HOOK_RESUME_SUBSCRIPTION,
            [$subscription_id]
        )) {
            return;
        }

        try {
            $subscription = arraysubs_get_subscription($subscription_id);
            if (!$subscription instanceof \WP_Post) {
                return;
            }

            // Only resume if subscription is currently on-hold
            if ($subscription->post_status !== 'arraysubs-on-hold') {
                return;
            }

            // Check if this was a pause retention offer
            $pause_until = get_post_meta($subscription_id, '_pause_until', true);
            if (empty($pause_until)) {
                return;
            }

            // Update subscription status back to active
            $result = wp_update_post([
                'ID' => $subscription_id,
                'post_status' => 'arraysubs-active',
            ]);

            if (is_wp_error($result)) {
                return;
            }

            // Clear pause meta
            delete_post_meta($subscription_id, '_pause_until');
            delete_post_meta($subscription_id, '_pause_reason');

            // Calculate and set next payment date
            $billing_period = get_post_meta($subscription_id, '_billing_period', true) ?: 'month';
            $billing_interval = absint(get_post_meta($subscription_id, '_billing_interval', true)) ?: 1;
            $next_payment = gmdate('Y-m-d H:i:s', strtotime("+{$billing_interval} {$billing_period}"));
            update_post_meta($subscription_id, '_next_payment_date', $next_payment);
            update_post_meta($subscription_id, '_last_payment_date', current_time('mysql', true));

            // Add subscription note
            if (function_exists('arraysubs_add_subscription_note')) {
                arraysubs_add_subscription_note(
                    $subscription_id,
                    __('Subscription automatically resumed after pause period ended', 'arraysubs'),
                    'system',
                    0,
                    [],
                    'subscription'
                );
            }

            // Fire status change action
            do_action('arraysubs_data_status_changed', $subscription_id, 'arraysubs-on-hold', 'arraysubs-active');
            do_action('arraysubs_data_reactivated', $subscription_id, 'arraysubs-on-hold');
        } finally {
            \ArraySubs\Supports\ActionScheduler::releaseLock(
                \ArraySubs\Supports\ActionScheduler::HOOK_RESUME_SUBSCRIPTION,
                [$subscription_id]
            );
        }
    }

    /**
     * Decrement retention discount cycles when a renewal is paid
     *
     * @param int $subscription_id Subscription ID
     * @param int $order_id        Renewal order ID
     */
    public function decrementRetentionDiscount(int $subscription_id, int $order_id): void
    {
        $order = wc_get_order($order_id);

        if (!$order || !RetentionOfferProcessor::orderHasRetentionDiscount($order, $subscription_id)) {
            return;
        }

        RetentionOfferProcessor::decrementDiscountCycles($subscription_id);
    }
}
