<?php

/**
 * Refunds Hooks
 *
 * Integrates with WooCommerce native refund system to handle
 * subscription-related refunds.
 *
 * @package ArraySubs\Features\Refunds\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\Refunds\Services;

defined('ABSPATH') || exit;

use ArraySubs\Supports\ActionScheduler;

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

    /**
     * Constructor - Register hooks
     */
    public function __construct()
    {
        // Hook into WooCommerce refund creation
        add_action('woocommerce_order_refunded', [$this, 'onOrderRefunded'], 10, 2);

        // Hook into refund creation for more details
        add_action('woocommerce_refund_created', [$this, 'onRefundCreated'], 10, 2);

        // Handle subscription cancellation after full refund
        add_action('arraysubs_subscription_fully_refunded', [$this, 'handleFullRefund'], 10, 2);

        // Add refund info to order edit screen
        add_action('woocommerce_admin_order_data_after_billing_address', [$this, 'displaySubscriptionRefundInfo']);

        // Add prorated refund button to subscription admin
        add_action('arraysubs_admin_subscription_actions', [$this, 'addProratedRefundAction'], 10, 2);
    }

    /**
     * Handle WooCommerce order refunded event
     *
     * @param int $order_id   Order ID
     * @param int $refund_id  Refund ID
     */
    public function onOrderRefunded(int $order_id, int $refund_id): void
    {
        $order = wc_get_order($order_id);
        if (!$order) {
            return;
        }

        // Resolve via the canonical helper (covers parent/_subscription_ids,
        // renewal/_subscription_id, and order-item _subscription_id).
        $linked = arraysubs_get_subscription_ids_for_order($order);
        $subscription_id = !empty($linked) ? $linked[0] : $this->findSubscriptionByParentOrder($order_id);

        if (!$subscription_id) {
            return;
        }

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

        // Get refund details
        $refund = wc_get_order($refund_id);
        if (!$refund) {
            return;
        }

        $refund_amount = abs($refund->get_total());
        $refund_reason = $refund->get_reason();

        // Log the refund
        $this->logRefund($subscription_id, $order_id, $refund_id, $refund_amount, $refund_reason);

        // Add subscription note
        if (function_exists('arraysubs_add_subscription_note')) {
            arraysubs_add_subscription_note(
                absint($subscription_id),
                sprintf(
                    /* translators: 1: refund amount, 2: order ID, 3: reason */
                    __('Refund processed: %1$s for order #%2$d. Reason: %3$s', 'arraysubs'),
                    wc_price($refund_amount),
                    $order_id,
                    $refund_reason ?: __('No reason provided', 'arraysubs')
                ),
                'system',
                0,
                [],
                'subscription'
            );
        }

        // Check if this is a full refund of the last renewal
        $order_type = $order->get_meta('_arraysubs_order_type');
        if ($order_type === 'renewal' || $this->isLatestRenewalOrder($subscription_id, $order_id)) {
            $this->checkForFullRefund($subscription_id, $order_id, $order, $refund_amount);
        }

        // Fire action for extensions
        do_action('arraysubs_subscription_order_refunded', $subscription_id, $order_id, $refund_id, $refund_amount);
    }

    /**
     * Handle refund created event (more detailed)
     *
     * @param int   $refund_id Refund ID
     * @param array $args      Refund arguments
     */
    public function onRefundCreated(int $refund_id, array $args): void
    {
        $order_id = $args['order_id'] ?? 0;
        if (!$order_id) {
            return;
        }

        $order = wc_get_order($order_id);
        if (!$order) {
            return;
        }

        $linked = arraysubs_get_subscription_ids_for_order($order);
        $subscription_id = !empty($linked) ? $linked[0] : $this->findSubscriptionByParentOrder($order_id);

        if (!$subscription_id) {
            return;
        }

        // Stamp the refund order with its subscription linkage so admin
        // refund views and reports can join back to the subscription.
        $refund = wc_get_order($refund_id);
        if ($refund) {
            $refund->update_meta_data('_subscription_id', absint($subscription_id));
            $refund->save();
        }
    }

    /**
     * Handle full refund of subscription
     *
     * @param int $subscription_id Subscription ID
     * @param int $order_id        Order ID
     */
    public function handleFullRefund(int $subscription_id, int $order_id): void
    {
        $subscription = arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof \WP_Post) {
            return;
        }

        // Get cancellation behavior setting
        $cancellation_behavior = arraysubs_get_setting('refunds.cancellation_behavior', 'immediate');

        if ($cancellation_behavior === 'immediate') {
            // Cancel immediately
            $this->cancelSubscriptionAfterRefund($subscription_id, $order_id);
        } elseif ($cancellation_behavior === 'end_of_period') {
            // Schedule cancellation at end of current period
            $this->scheduleEndOfPeriodCancellation($subscription_id, $order_id);
        }
        // 'none' = don't auto-cancel

        // Fire action for extensions
        do_action('arraysubs_subscription_fully_refunded_processed', $subscription_id, $order_id, $cancellation_behavior);
    }

    /**
     * Check if order is fully refunded
     *
     * @param int       $subscription_id Subscription ID
     * @param int       $order_id        Order ID
     * @param \WC_Order $order           Order object
     * @param float     $refund_amount   Latest refund amount
     */
    private function checkForFullRefund(int $subscription_id, int $order_id, \WC_Order $order, float $refund_amount): void
    {
        $order_total = floatval($order->get_total());
        $total_refunded = floatval($order->get_total_refunded());

        // Check if fully refunded (with small tolerance for rounding)
        if (($order_total - $total_refunded) < 0.01) {
            // Fire full refund action
            do_action('arraysubs_subscription_fully_refunded', $subscription_id, $order_id);
        }
    }

    /**
     * Cancel subscription after full refund
     *
     * Delegates to the canonical helper so the lifecycle meta, scheduled-
     * cancel override detection, retry-state cleanup, and AS unscheduling
     * all happen in one place.
     *
     * @param int $subscription_id Subscription ID
     * @param int $order_id        Order ID
     */
    private function cancelSubscriptionAfterRefund(int $subscription_id, int $order_id): void
    {
        // Refund-specific context (set BEFORE the helper so AutoNotes can
        // observe it during the synchronous status transition).
        update_post_meta($subscription_id, '_refund_cancellation_order_id', $order_id);
        update_post_meta($subscription_id, '_end_date', current_time('mysql', true));

        $cancelled = arraysubs_cancel_subscription_immediately(
            $subscription_id,
            __('Full refund processed', 'arraysubs'),
            'system'
        );

        if (!$cancelled) {
            return;
        }

        // Add a refund-specific note alongside AutoNotes so the order
        // linkage stays visible in the subscription timeline.
        if (function_exists('arraysubs_add_subscription_note')) {
            arraysubs_add_subscription_note(
                $subscription_id,
                sprintf(
                    /* translators: %d: order ID */
                    __('Cancellation triggered by full refund of order #%d.', 'arraysubs'),
                    $order_id
                ),
                'system',
                0,
                [],
                'subscription'
            );
        }
    }

    /**
     * Schedule cancellation at end of current billing period
     *
     * Delegates to the canonical helper so we share lifecycle meta
     * normalization, idempotency, AS deduplication, and the standard
     * `arraysubs_data_waiting_cancellation` action signature.
     *
     * @param int $subscription_id Subscription ID
     * @param int $order_id        Order ID
     */
    private function scheduleEndOfPeriodCancellation(int $subscription_id, int $order_id): void
    {
        // Refund-specific context for downstream display (Refunds admin
        // panel, AutoNotes augmentation, audit logs).
        update_post_meta($subscription_id, '_refund_cancellation_order_id', $order_id);

        $scheduled = arraysubs_schedule_cancellation(
            $subscription_id,
            __('Full refund processed (end of period)', 'arraysubs'),
            'system'
        );

        if (!$scheduled) {
            // Helper bailed (terminal status, no scheduled date, etc.).
            // Refund-cancellation-order-id meta stays so admins can still
            // see the linkage on the refunds panel.
            return;
        }

        // Add a refund-specific note alongside the canonical AutoNotes
        // note, so the linkage to the refunded order ID is explicit in
        // the timeline.
        if (function_exists('arraysubs_add_subscription_note')) {
            $next_payment = get_post_meta($subscription_id, '_cancellation_scheduled_date', true);
            arraysubs_add_subscription_note(
                $subscription_id,
                sprintf(
                    /* translators: 1: order ID, 2: cancellation date */
                    __('Cancellation triggered by full refund of order #%1$d. Will cancel on %2$s.', 'arraysubs'),
                    $order_id,
                    $next_payment ? arraysubs_format_date_local($next_payment, \get_option('date_format')) : __('next payment date', 'arraysubs')
                ),
                'system',
                0,
                [],
                'subscription'
            );
        }
    }

    /**
     * Log refund to subscription meta
     *
     * @param int    $subscription_id Subscription ID
     * @param int    $order_id        Order ID
     * @param int    $refund_id       Refund ID
     * @param float  $amount          Refund amount
     * @param string $reason          Refund reason
     */
    private function logRefund(int $subscription_id, int $order_id, int $refund_id, float $amount, string $reason): void
    {
        $refund_log = get_post_meta($subscription_id, '_refund_history', true) ?: [];

        $refund_log[] = [
            'date' => current_time('mysql', true),
            'order_id' => $order_id,
            'refund_id' => $refund_id,
            'amount' => $amount,
            'reason' => $reason,
        ];

        update_post_meta($subscription_id, '_refund_history', $refund_log);

        // Update total refunded amount
        $total_refunded = floatval(get_post_meta($subscription_id, '_total_refunded', true));
        update_post_meta($subscription_id, '_total_refunded', $total_refunded + $amount);
    }

    /**
     * Find subscription by parent order ID
     *
     * @param int $order_id Parent order ID
     * @return int|null Subscription ID or null
     */
    private function findSubscriptionByParentOrder(int $order_id): ?int
    {
        // Search for subscription with this as parent order
        $subscriptions = get_posts([
            'post_type' => 'arraysubs_data',
            'post_status' => 'any',
            'meta_query' => [
                'relation' => 'OR',
                [
                    'key' => '_original_order_id',
                    'value' => $order_id,
                    'type' => 'NUMERIC',
                ],
                [
                    'key' => '_pending_renewal_order_id',
                    'value' => $order_id,
                    'type' => 'NUMERIC',
                ],
            ],
            'posts_per_page' => 1,
            'fields' => 'ids',
        ]);

        return !empty($subscriptions) ? $subscriptions[0] : null;
    }

    /**
     * Check if order is the latest renewal for a subscription
     *
     * This method determines if a refunded order is the most recent paid renewal,
     * which would trigger automatic cancellation on full refund.
     *
     * @param int $subscription_id Subscription ID
     * @param int $order_id        Order ID
     * @return bool
     */
    private function isLatestRenewalOrder(int $subscription_id, int $order_id): bool
    {
        // Check if it's the pending renewal order
        $pending_order_id = get_post_meta($subscription_id, '_pending_renewal_order_id', true);
        if ($order_id === absint($pending_order_id)) {
            return true;
        }

        // Check if it's the original order (and only if no renewals have happened)
        $original_order_id = get_post_meta($subscription_id, '_original_order_id', true);
        if ($order_id === absint($original_order_id)) {
            // Only return true if this is the only paid order (no renewals yet)
            $completed_payments = absint(get_post_meta($subscription_id, '_completed_payments', true));
            return $completed_payments <= 1;
        }

        // Query for all renewal orders and check if this is the most recent.
        // Renewal orders are stamped with single `_subscription_id` meta
        // (see OrderCreation::createRenewalOrder).
        $renewal_orders = wc_get_orders([
            'meta_key' => '_subscription_id',
            'meta_value' => $subscription_id,
            'status' => ['completed', 'processing'],
            'orderby' => 'date',
            'order' => 'DESC',
            'limit' => 1,
        ]);

        if (!empty($renewal_orders)) {
            return $renewal_orders[0]->get_id() === $order_id;
        }

        return false;
    }

    /**
     * Display subscription refund info on order edit screen
     *
     * @param \WC_Order $order Order object
     */
    public function displaySubscriptionRefundInfo(\WC_Order $order): void
    {
        $linked = arraysubs_get_subscription_ids_for_order($order);
        $subscription_id = !empty($linked) ? $linked[0] : 0;
        if (!$subscription_id) {
            return;
        }

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

?>
        <div class="arraysubs-order-subscription-info">
            <p>
                <strong><?php esc_html_e('ArraySubs Subscription:', 'arraysubs'); ?></strong>
                <a href="<?php echo esc_url(admin_url('admin.php?page=arraysubs#/subscriptions/edit/' . $subscription_id)); ?>">
                    #<?php echo esc_html($subscription_id); ?>
                </a>
                (<?php echo esc_html($subscription->post_status); ?>)
            </p>
            <?php
            $total_refunded = get_post_meta($subscription_id, '_total_refunded', true);
            if ($total_refunded > 0) {
            ?>
                <p>
                    <strong><?php esc_html_e('Total Refunded:', 'arraysubs'); ?></strong>
                    <?php echo wp_kses_post(wc_price($total_refunded)); ?>
                </p>
            <?php
            }
            ?>
        </div>
    <?php
    }

    /**
     * Add prorated refund action to subscription admin
     *
     * @param int      $subscription_id Subscription ID
     * @param \WP_Post $subscription    Subscription post object
     */
    public function addProratedRefundAction(int $subscription_id, \WP_Post $subscription): void
    {
        // Only show for active subscriptions with renewals
        $last_payment_date = get_post_meta($subscription_id, '_last_payment_date', true);
        if (empty($last_payment_date)) {
            return;
        }

        // Calculate prorated refund preview
        $proration = \ArraySubs\Features\PlanSwitching\Services\ProrationCalculator::calculateProrationRefund($subscription_id);

        if (!$proration['success'] || $proration['refund_amount'] <= 0) {
            return;
        }

    ?>
        <div class="arraysubs-admin-action arraysubs-prorated-refund-action">
            <button
                type="button"
                class="button button-secondary"
                data-subscription-id="<?php echo esc_attr($subscription_id); ?>"
                data-refund-amount="<?php echo esc_attr($proration['refund_amount']); ?>"
                data-days-unused="<?php echo esc_attr($proration['days_unused']); ?>">
                <?php
                printf(
                    /* translators: %s: prorated refund amount */
                    esc_html__('Issue Prorated Refund (%s)', 'arraysubs'),
                    wp_kses_post($proration['formatted']['refund'])
                );
                ?>
            </button>
            <span class="description">
                <?php
                printf(
                    /* translators: %d: days unused */
                    esc_html__('%d days unused in current cycle', 'arraysubs'),
                    intval($proration['days_unused'])
                );
                ?>
            </span>
        </div>
<?php
    }
}
