<?php

/**
 * Refund Processor
 *
 * Handles refund processing logic for subscriptions.
 *
 * @package ArraySubs\Features\Refunds\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\Refunds\Services;

defined('ABSPATH') || exit;

use ArraySubs\Features\PlanSwitching\Services\ProrationCalculator;

/**
 * Refund processing service
 */
class RefundProcessor
{
    /**
     * Process a prorated refund for a subscription
     *
     * @param int    $subscription_id Subscription ID
     * @param string $reason          Refund reason
     * @param bool   $cancel_after    Whether to cancel after refund
     * @return array Result with 'success', 'refund_id', 'amount', etc.
     */
    public static function processProratedRefund(int $subscription_id, string $reason = '', bool $cancel_after = true): array
    {
        $subscription_id = absint($subscription_id);
        $subscription = arraysubs_get_subscription($subscription_id);

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

        // Calculate prorated refund amount
        $proration = ProrationCalculator::calculateProrationRefund($subscription_id);

        if (!$proration['success']) {
            return [
                'success' => false,
                'error' => $proration['error'] ?? __('Failed to calculate refund', 'arraysubs'),
            ];
        }

        if ($proration['refund_amount'] <= 0) {
            return [
                'success' => false,
                'error' => __('No refund amount available (no unused days)', 'arraysubs'),
            ];
        }

        // Find the order to refund
        $order_id = self::findRefundableOrder($subscription_id);

        if (!$order_id) {
            return [
                'success' => false,
                'error' => __('No refundable order found', 'arraysubs'),
            ];
        }

        $order = wc_get_order($order_id);
        if (!$order) {
            return [
                'success' => false,
                'error' => __('Order not found', 'arraysubs'),
            ];
        }

        // Verify refund amount doesn't exceed what can be refunded
        $remaining_refundable = floatval($order->get_total()) - floatval($order->get_total_refunded());
        $refund_amount = min($proration['refund_amount'], $remaining_refundable);

        if ($refund_amount <= 0) {
            return [
                'success' => false,
                'error' => __('Order already fully refunded', 'arraysubs'),
            ];
        }

        // Build refund reason
        $full_reason = sprintf(
            /* translators: 1: subscription ID, 2: days unused, 3: custom reason */
            __('Prorated refund for subscription #%1$d (%2$d unused days). %3$s', 'arraysubs'),
            $subscription_id,
            $proration['days_unused'],
            $reason
        );

        // Create the refund via WooCommerce
        $refund_result = self::createWooCommerceRefund($order, $refund_amount, $full_reason);

        if (!$refund_result['success']) {
            return $refund_result;
        }

        // Update subscription meta
        update_post_meta($subscription_id, '_last_prorated_refund_date', current_time('mysql', true));
        update_post_meta($subscription_id, '_last_prorated_refund_amount', $refund_amount);
        update_post_meta($subscription_id, '_last_prorated_refund_id', $refund_result['refund_id']);

        // Add subscription note
        if (function_exists('arraysubs_add_subscription_note')) {
            arraysubs_add_subscription_note(
                $subscription_id,
                sprintf(
                    /* translators: 1: refund amount, 2: days unused, 3: reason */
                    __('Prorated refund issued: %1$s for %2$d unused days. %3$s', 'arraysubs'),
                    wc_price($refund_amount),
                    $proration['days_unused'],
                    $reason ?: __('No reason provided', 'arraysubs')
                ),
                'system',
                0,
                [],
                'subscription'
            );
        }

        // Cancel subscription if requested
        if ($cancel_after) {
            self::cancelSubscription($subscription_id, __('Prorated refund issued', 'arraysubs'));
        }

        return [
            'success' => true,
            'refund_id' => $refund_result['refund_id'],
            'order_id' => $order_id,
            'amount' => $refund_amount,
            'days_unused' => $proration['days_unused'],
            'formatted_amount' => wc_price($refund_amount),
            'cancelled' => $cancel_after,
        ];
    }

    /**
     * Process a full refund for a subscription order
     *
     * @param int    $order_id Order ID
     * @param string $reason   Refund reason
     * @return array Result
     */
    public static function processFullRefund(int $order_id, string $reason = ''): array
    {
        $order = wc_get_order($order_id);
        if (!$order) {
            return [
                'success' => false,
                'error' => __('Order not found', 'arraysubs'),
            ];
        }

        $refundable_amount = floatval($order->get_total()) - floatval($order->get_total_refunded());

        if ($refundable_amount <= 0) {
            return [
                'success' => false,
                'error' => __('Order already fully refunded', 'arraysubs'),
            ];
        }

        return self::createWooCommerceRefund($order, $refundable_amount, $reason);
    }

    /**
     * Process a partial refund for a subscription order
     *
     * @param int    $order_id Order ID
     * @param float  $amount   Refund amount
     * @param string $reason   Refund reason
     * @return array Result
     */
    public static function processPartialRefund(int $order_id, float $amount, string $reason = ''): array
    {
        $order = wc_get_order($order_id);
        if (!$order) {
            return [
                'success' => false,
                'error' => __('Order not found', 'arraysubs'),
            ];
        }

        $refundable_amount = floatval($order->get_total()) - floatval($order->get_total_refunded());

        if ($amount > $refundable_amount) {
            return [
                'success' => false,
                'error' => sprintf(
                    /* translators: %s: max refundable amount */
                    __('Amount exceeds maximum refundable: %s', 'arraysubs'),
                    wc_price($refundable_amount)
                ),
            ];
        }

        if ($amount <= 0) {
            return [
                'success' => false,
                'error' => __('Invalid refund amount', 'arraysubs'),
            ];
        }

        return self::createWooCommerceRefund($order, $amount, $reason);
    }

    /**
     * Create a WooCommerce refund
     *
     * @param \WC_Order $order  Order object
     * @param float     $amount Refund amount
     * @param string    $reason Refund reason
     * @return array Result with 'success', 'refund_id', 'amount'
     */
    private static function createWooCommerceRefund(\WC_Order $order, float $amount, string $reason): array
    {
        try {
            // Determine if we should try to refund via payment gateway
            $restock_items = false;
            $refund_payment = self::shouldRefundViaGateway($order);

            $refund = wc_create_refund([
                'amount' => $amount,
                'reason' => $reason,
                'order_id' => $order->get_id(),
                'refund_payment' => $refund_payment,
                'restock_items' => $restock_items,
            ]);

            if (is_wp_error($refund)) {
                return [
                    'success' => false,
                    'error' => $refund->get_error_message(),
                ];
            }

            // Store subscription ID in refund meta if available
            $subscription_id = $order->get_meta('_arraysubs_subscription_id');
            if ($subscription_id) {
                $refund->update_meta_data('_arraysubs_subscription_id', absint($subscription_id));
                $refund->save();

                /**
                 * Fires after a refund is created for a subscription order.
                 * Pro uses this to reconcile external refunds from gateway webhooks.
                 *
                 * @param int    $subscription_id Subscription ID.
                 * @param int    $order_id        Order ID.
                 * @param float  $amount          Refund amount.
                 * @param string $gateway_slug    Payment gateway slug.
                 */
                $gateway_slug = get_post_meta(absint($subscription_id), '_payment_gateway', true);
                if (!empty($gateway_slug)) {
                    do_action(
                        'arraysubs_gateway_refund_created',
                        absint($subscription_id),
                        $order->get_id(),
                        $amount,
                        $gateway_slug
                    );
                }
            }

            return [
                'success' => true,
                'refund_id' => $refund->get_id(),
                'order_id' => $order->get_id(),
                'amount' => $amount,
                'formatted_amount' => wc_price($amount),
                'refunded_via_gateway' => $refund_payment,
            ];
        } catch (\Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage(),
            ];
        }
    }

    /**
     * Check if refund should be processed via payment gateway
     *
     * @param \WC_Order $order Order object
     * @return bool
     */
    private static function shouldRefundViaGateway(\WC_Order $order): bool
    {
        // Check if setting allows automatic gateway refunds
        $auto_refund = arraysubs_get_setting('refunds.auto_gateway_refund', true);
        if (!$auto_refund) {
            return false;
        }

        // Check if payment method supports refunds
        $payment_method = $order->get_payment_method();
        $gateways = WC()->payment_gateways->payment_gateways();

        if (!isset($gateways[$payment_method])) {
            return false;
        }

        $gateway = $gateways[$payment_method];

        // Check if gateway supports refunds
        if (!$gateway->supports('refunds')) {
            return false;
        }

        return true;
    }

    /**
     * Find the best order to refund for a subscription
     *
     * @param int $subscription_id Subscription ID
     * @return int|null Order ID or null
     */
    private static function findRefundableOrder(int $subscription_id): ?int
    {
        // First, try pending renewal order
        $pending_order_id = get_post_meta($subscription_id, '_pending_renewal_order_id', true);
        if ($pending_order_id) {
            $order = wc_get_order($pending_order_id);
            if ($order && $order->is_paid() && self::getRefundableAmount($order) > 0) {
                return absint($pending_order_id);
            }
        }

        // Then, try original order
        $original_order_id = get_post_meta($subscription_id, '_original_order_id', true);
        if ($original_order_id) {
            $order = wc_get_order($original_order_id);
            if ($order && $order->is_paid() && self::getRefundableAmount($order) > 0) {
                return absint($original_order_id);
            }
        }

        // Search for renewal orders
        $renewal_orders = wc_get_orders([
            'meta_key' => '_arraysubs_subscription_id',
            'meta_value' => $subscription_id,
            'status' => ['completed', 'processing'],
            'orderby' => 'date',
            'order' => 'DESC',
            'limit' => 1,
        ]);

        foreach ($renewal_orders as $renewal_order) {
            if (self::getRefundableAmount($renewal_order) > 0) {
                return $renewal_order->get_id();
            }
        }

        return null;
    }

    /**
     * Get refundable amount for an order
     *
     * @param \WC_Order $order Order object
     * @return float
     */
    private static function getRefundableAmount(\WC_Order $order): float
    {
        return floatval($order->get_total()) - floatval($order->get_total_refunded());
    }

    /**
     * Cancel subscription after refund
     *
     * @param int    $subscription_id Subscription ID
     * @param string $reason          Cancellation reason
     */
    private static function cancelSubscription(int $subscription_id, string $reason): void
    {
        $cancelled = arraysubs_cancel_subscription_immediately($subscription_id, $reason, 'system');
        if (!$cancelled) {
            return;
        }

        update_post_meta($subscription_id, '_end_date', current_time('mysql', true));
    }

    /**
     * Get refund history for a subscription
     *
     * @param int $subscription_id Subscription ID
     * @return array Refund history with order details
     */
    public static function getRefundHistory(int $subscription_id): array
    {
        $subscription_id = absint($subscription_id);
        $history = get_post_meta($subscription_id, '_refund_history', true) ?: [];

        // Enrich with additional order/refund details
        foreach ($history as &$entry) {
            $order = wc_get_order($entry['order_id']);
            $refund = wc_get_order($entry['refund_id']);

            $entry['order_status'] = $order ? $order->get_status() : 'unknown';
            $entry['order_total'] = $order ? floatval($order->get_total()) : 0;
            $entry['refund_method'] = $refund ? $refund->get_refund_reason() : '';
            $entry['formatted_amount'] = wc_price($entry['amount']);
            $entry['formatted_date'] = arraysubs_format_date_local(
                $entry['date'],
                \get_option('date_format') . ' ' . \get_option('time_format')
            );
        }

        return $history;
    }

    /**
     * Get total refunded amount for a subscription
     *
     * @param int $subscription_id Subscription ID
     * @return float Total refunded
     */
    public static function getTotalRefunded(int $subscription_id): float
    {
        return floatval(get_post_meta(absint($subscription_id), '_total_refunded', true));
    }

    /**
     * Check if a subscription can be refunded
     *
     * @param int $subscription_id Subscription ID
     * @return array ['can_refund' => bool, 'reason' => string, 'max_amount' => float]
     */
    public static function canRefund(int $subscription_id): array
    {
        $subscription_id = absint($subscription_id);
        $subscription = arraysubs_get_subscription($subscription_id);

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

        // Find refundable order
        $order_id = self::findRefundableOrder($subscription_id);
        if (!$order_id) {
            return [
                'can_refund' => false,
                'reason' => __('No refundable order found', 'arraysubs'),
                'max_amount' => 0,
            ];
        }

        $order = wc_get_order($order_id);
        $max_amount = self::getRefundableAmount($order);

        if ($max_amount <= 0) {
            return [
                'can_refund' => false,
                'reason' => __('Order already fully refunded', 'arraysubs'),
                'max_amount' => 0,
            ];
        }

        return [
            'can_refund' => true,
            'reason' => '',
            'max_amount' => $max_amount,
            'order_id' => $order_id,
        ];
    }
}
