<?php

/**
 * Order Creation
 *
 * Handles creation of renewal orders for subscriptions.
 *
 * @package ArraySubs\Features\RecurringBilling\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\RecurringBilling\Services;

defined('ABSPATH') || exit;

/**
 * Order creation service
 */
class OrderCreation
{
    /**
     * Create a renewal order for a subscription
     *
     * @param int $subscription_id Subscription ID
     * @return \WC_Order|false Order object or false on failure
     * @throws \Exception If order creation or product lookup fails.
     */
    public function createRenewalOrder(int $subscription_id)
    {
        $subscription = arraysubs_get_subscription($subscription_id);

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

        try {
            // Get subscription meta data
            $customer_id = get_post_meta($subscription_id, '_customer_id', true);
            $product_id = get_post_meta($subscription_id, '_product_id', true);
            $variation_id = get_post_meta($subscription_id, '_variation_id', true);
            $quantity = (int) get_post_meta($subscription_id, '_quantity', true) ?: 1;
            $recurring_amount = get_post_meta($subscription_id, '_recurring_amount', true);
            $subscription_price = get_post_meta($subscription_id, '_subscription_price', true);
            $payment_method = get_post_meta($subscription_id, '_payment_method', true);
            $payment_method_title = get_post_meta($subscription_id, '_payment_method_title', true);
            $payment_token_id = get_post_meta($subscription_id, '_payment_token_id', true);

            // Get billing cycle info for order notes
            $billing_interval = get_post_meta($subscription_id, '_billing_interval', true) ?: 1;
            $billing_period = get_post_meta($subscription_id, '_billing_period', true) ?: 'month';
            $next_payment_date = get_post_meta($subscription_id, '_next_payment_date', true);
            $completed_payments = (int) get_post_meta($subscription_id, '_completed_payments', true);

            // This will be renewal payment number (completed + 1)
            $renewal_cycle_number = $completed_payments + 1;

            // Pending plan switch (deferred APPLY_AT_RENEWAL).
            // If present, the renewal order is built using the target plan
            // and the switch fee is added as a separate fee line. The
            // subscription itself is NOT modified here — that happens
            // after the renewal order is paid (see
            // OrderIntegration::processRenewalPayment).
            $pending_switch = function_exists('arraysubs_get_pending_switch')
                ? arraysubs_get_pending_switch($subscription_id)
                : null;

            if ($pending_switch !== null) {
                $variation_id = (int) ($pending_switch['target_variation_id'] ?? 0);
                $product_id = (int) ($pending_switch['target_product_id'] ?? $product_id);
                $quantity = max(1, (int) ($pending_switch['quantity'] ?? $quantity));
                // Force the target price; ignore stored recurring/subscription prices.
                $recurring_amount = (string) ($pending_switch['new_price'] ?? $recurring_amount);
                $subscription_price = '';
            }

            // Create new order
            $order = wc_create_order([
                'customer_id' => $customer_id,
                'status' => 'pending',
            ]);

            if (!$order) {
                throw new \Exception(__('Failed to create order', 'arraysubs'));
            }

            // Get product - use variation ID if available for variation products
            $order_product_id = !empty($variation_id) ? $variation_id : $product_id;
            $product = wc_get_product($order_product_id);
            if (!$product) {
                throw new \Exception(__('Subscription product not found', 'arraysubs'));
            }

            // Add product to order
            $item = new \WC_Order_Item_Product();
            $item->set_product($product);
            $item->set_quantity($quantity);

            // Determine which price to use based on billing cycle count
            $different_renewal_price = (float) get_post_meta($subscription_id, '_different_renewal_price', true);
            $different_renewal_price_after = (int) get_post_meta($subscription_id, '_different_renewal_price_after', true) ?: 1;

            // CRITICAL: Use the stored prices from subscription meta
            // This ensures existing subscriptions are NOT affected by product price changes
            // The prices are locked at subscription creation time.
            //
            // When a pending switch is in effect, `recurring_amount` was
            // overridden above to the target plan's per-unit price, and
            // `different_renewal_price` does not apply (the switch resets
            // pricing to the new plan).

            $use_different_renewal_price = (
                $pending_switch === null
                && $different_renewal_price > 0
                && $completed_payments >= $different_renewal_price_after
            );

            if ($use_different_renewal_price) {
                // Use the different renewal price after threshold
                $price = $different_renewal_price;
                $price_type = 'different renewal price';
            } elseif (!empty($recurring_amount)) {
                // Use regular recurring amount (from product price at checkout)
                $price = floatval($recurring_amount);
                $price_type = 'recurring amount';
            } elseif (!empty($subscription_price)) {
                $price = floatval($subscription_price);
                $price_type = 'subscription price (fallback)';
            } else {
                // Last resort: use current product price (shouldn't happen)
                $price = floatval($product->get_price());
                $price_type = 'current product price (ERROR)';
            }

            $item->set_subtotal($price * $quantity);
            $item->set_total($price * $quantity);

            $order->add_item($item);

            // Pending-switch fee, charged once at renewal alongside the new
            // plan price. The fee value is captured at the time the switch
            // was requested so a settings change later does not retroactively
            // alter what was promised to the customer.
            if ($pending_switch !== null) {
                $switch_fee = isset($pending_switch['switch_fee']) ? (float) $pending_switch['switch_fee'] : 0;
                if ($switch_fee > 0) {
                    $fee_item = new \WC_Order_Item_Fee();
                    $fee_item->set_name(sprintf(
                        /* translators: %s: switch type label */
                        __('Plan %s switch fee', 'arraysubs'),
                        ucfirst((string) ($pending_switch['switch_type'] ?? 'switch'))
                    ));
                    $fee_item->set_amount((string) $switch_fee);
                    $fee_item->set_total((string) $switch_fee);
                    $order->add_item($fee_item);
                }

                // Stamp the order so the post-payment handler can finalize
                // the subscription update.
                $order->update_meta_data('_pending_switch_payload', $pending_switch);
            }

            // Copy addresses from subscription (stored as JSON)
            $billing_address_json = get_post_meta($subscription_id, '_billing_address', true);
            if (!empty($billing_address_json)) {
                $billing = json_decode($billing_address_json, true);
                if ($billing && is_array($billing)) {
                    $order->set_address($billing, 'billing');
                }
            }

            $shipping_address_json = get_post_meta($subscription_id, '_shipping_address', true);
            if (!empty($shipping_address_json)) {
                $shipping = json_decode($shipping_address_json, true);
                if ($shipping && is_array($shipping)) {
                    $order->set_address($shipping, 'shipping');
                }
            }

            // Add shipping to renewal order if applicable
            $this->addShippingToRenewalOrder($order, $subscription);

            // Calculate totals
            $order->calculate_totals();

            // Apply retention discount if active (CancellationFlow may not exist in free builds).
            $retention_processor_class = '\\ArraySubs\\Features\\CancellationFlow\\Services\\RetentionOfferProcessor';

            if (class_exists($retention_processor_class)) {
                $active_discount = $retention_processor_class::getActiveDiscount($subscription_id);

                if (
                    is_array($active_discount)
                    && isset($active_discount['type'])
                    && isset($active_discount['amount'])
                ) {
                    $retention_processor_class::applyDiscountToOrder(
                        $order,
                        $active_discount['type'],
                        (float) $active_discount['amount'],
                        $subscription_id
                    );
                }
            }

            // Set order meta
            $order->update_meta_data('_is_renewal_order', 'yes');
            $order->update_meta_data('_subscription_id', $subscription_id);
            $order->update_meta_data('_subscription_renewal', $subscription_id);
            $order->update_meta_data('_renewal_cycle_number', $renewal_cycle_number);

            // Store the scheduled renewal date (when payment was supposed to be due)
            // This may differ from order creation date if machine was off or job ran late
            if (!empty($next_payment_date)) {
                $order->update_meta_data('_renewal_scheduled_date', $next_payment_date);
            }

            // Copy payment method
            if (!empty($payment_method)) {
                $order->set_payment_method($payment_method);

                if (!empty($payment_method_title)) {
                    $order->set_payment_method_title($payment_method_title);
                }
            }

            // Copy payment token
            if (!empty($payment_token_id)) {
                $order->update_meta_data('_payment_tokens', [$payment_token_id]);
            }

            // Format scheduled date for display
            $scheduled_date_display = !empty($next_payment_date)
                ? arraysubs_format_date_local($next_payment_date, get_option('date_format') . ' ' . get_option('time_format'))
                : __('N/A', 'arraysubs');

            // Format billing period for display
            $period_display = sprintf(
                /* translators: %1$d: interval, %2$s: period (day/week/month/year) */
                _n('every %1$d %2$s', 'every %1$d %2$ss', $billing_interval, 'arraysubs'),
                $billing_interval,
                $billing_period
            );

            // Add detailed order note
            $order->add_order_note(
                sprintf(
                    /* translators: %1$d: subscription ID, %2$d: cycle number, %3$s: scheduled date, %4$s: billing period, %5$s: actual creation time */
                    __('Renewal order for subscription #%1$d (Cycle #%2$d, scheduled for %3$s, billing %4$s). Order generated on %5$s.', 'arraysubs'),
                    $subscription_id,
                    $renewal_cycle_number,
                    $scheduled_date_display,
                    $period_display,
                    arraysubs_format_timestamp_local(time(), get_option('date_format') . ' ' . get_option('time_format'))
                )
            );

            $order->save();

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

    /**
     * Calculate renewal order total
     *
     * @param int $subscription_id Subscription ID
     * @return float Total amount
     */
    public function calculateRenewalTotal(int $subscription_id): float
    {
        $subscription = arraysubs_get_subscription($subscription_id);

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

        // Use recurring amount (excludes signup fee)
        $recurring_amount = get_post_meta($subscription_id, '_recurring_amount', true);
        $price = !empty($recurring_amount) ? floatval($recurring_amount) : 0.0;

        $quantity = (int) get_post_meta($subscription_id, '_quantity', true) ?: 1;

        return $price * $quantity;
    }

    /**
     * Add shipping to renewal order
     *
     * Adds shipping line item to renewal order based on subscription shipping settings.
     * Respects shipping type (one-time vs recurring) and uses stored shipping data.
     *
     * @param \WC_Order $order Renewal order being created
     * @param \WP_Post  $subscription Subscription post object
     * @return void
     */
    private function addShippingToRenewalOrder(\WC_Order $order, \WP_Post $subscription): void
    {
        $subscription_id = $subscription->ID;
        $pending_switch = function_exists('arraysubs_get_pending_switch')
            ? arraysubs_get_pending_switch($subscription_id)
            : null;
        $target_product = null;

        if (is_array($pending_switch)) {
            $target_product_id = (int) ($pending_switch['target_variation_id'] ?? 0)
                ?: (int) ($pending_switch['target_product_id'] ?? 0);
            $target_product = $target_product_id > 0 ? wc_get_product($target_product_id) : null;
        }

        $renewal_shipping_total = get_post_meta($subscription_id, '_arraysubs_renewal_shipping_total', true);
        $renewal_shipping_total = !empty($renewal_shipping_total) ? floatval($renewal_shipping_total) : 0.0;

        if ($renewal_shipping_total <= 0) {
            $initial_shipping_total = get_post_meta($subscription_id, '_arraysubs_shipping_total', true);
            $renewal_shipping_total = !empty($initial_shipping_total) ? floatval($initial_shipping_total) : 0.0;
        }

        $shipping_context = [
            'needs_shipping' => get_post_meta($subscription_id, '_arraysubs_needs_shipping', true) === 'yes',
            'shipping_type' => get_post_meta($subscription_id, '_arraysubs_shipping_type', true) ?: 'recurring',
            'renewal_shipping_total' => $renewal_shipping_total,
            'shipping_method_id' => get_post_meta($subscription_id, '_arraysubs_shipping_method_id', true),
            'shipping_method_title' => get_post_meta($subscription_id, '_arraysubs_shipping_method_title', true),
        ];

        /**
         * Filter the effective renewal shipping context before a renewal order is built.
         *
         * Pro shipping uses this to charge target-plan shipping when a deferred
         * pending switch is applied at the renewal order.
         *
         * @param array            $shipping_context Shipping context.
         * @param \WP_Post         $subscription     Subscription post.
         * @param array|null       $pending_switch   Pending switch payload.
         * @param \WC_Product|null $target_product   Target product when pending switch exists.
         */
        $shipping_context = apply_filters(
            'arraysubs_renewal_shipping_context',
            $shipping_context,
            $subscription,
            $pending_switch,
            $target_product
        );

        if (empty($shipping_context['needs_shipping'])) {
            return;
        }

        if (($shipping_context['shipping_type'] ?? '') === 'one-time') {
            return;
        }

        $renewal_shipping_total = (float) ($shipping_context['renewal_shipping_total'] ?? 0);

        // Skip if no shipping cost
        if ($renewal_shipping_total <= 0) {
            return;
        }

        // Create shipping line item
        $shipping_item = new \WC_Order_Item_Shipping();
        $shipping_item->set_method_id($shipping_context['shipping_method_id'] ?: 'flat_rate');
        $shipping_item->set_method_title($shipping_context['shipping_method_title'] ?: __('Subscription Shipping', 'arraysubs'));
        $shipping_item->set_total($renewal_shipping_total);

        // Add meta to identify as subscription renewal shipping
        $shipping_item->add_meta_data('_subscription_renewal_shipping', 'yes', true);
        $shipping_item->add_meta_data('_subscription_id', $subscription_id, true);

        $order->add_item($shipping_item);

        /**
         * Action fired after shipping is added to renewal order
         *
         * @param \WC_Order $order The renewal order
         * @param \WP_Post $subscription The subscription
         * @param float $renewal_shipping_total The shipping amount added
         */
        do_action('arraysubs_renewal_shipping_added', $order, $subscription, $renewal_shipping_total);
    }
}
