<?php

/**
 * Order and Subscription linking helper functions
 *
 * @package ArraySubs\Functions
 */

defined('ABSPATH') || exit;

/**
 * Resolve all subscription IDs a WooCommerce order is linked to.
 *
 * Single source of truth for "which subscription does this order belong
 * to?". Handles every variant of the order ↔ subscription contract:
 *
 *   - parent (initial) order: `_subscription_ids` (array on order meta)
 *   - renewal order: `_subscription_id` (single on order meta)
 *   - per-line-item: `_subscription_id` (on order item meta)
 *   - plan-switch / migration order: `_arraysubs_subscription_id`
 *     (legacy single-key written by PlanManager and CheckoutMigrationTrait)
 *   - reverse lookup via subscription `_parent_order_id` and
 *     `_pending_renewal_order_id`
 *
 * Use this anywhere a refund, webhook, or admin tool needs to find the
 * subscription(s) a WC order belongs to. Returns a unique, integer-cast
 * list (may be empty).
 *
 * @param int|\WC_Order $order Order ID or object
 * @return int[] Unique subscription IDs (may be empty)
 */
function arraysubs_get_subscription_ids_for_order($order): array
{
    if (is_numeric($order)) {
        $order = wc_get_order($order);
    }

    if (!$order instanceof \WC_Order) {
        return [];
    }

    $order_id = $order->get_id();
    $ids = [];

    // Parent (initial) order — `_subscription_ids` is an array.
    $parent_ids = $order->get_meta('_subscription_ids', true);
    if (is_array($parent_ids)) {
        foreach ($parent_ids as $id) {
            $ids[] = (int) $id;
        }
    }

    // Renewal order — `_subscription_id` is a single ID.
    $renewal_id = (int) $order->get_meta('_subscription_id', true);
    if ($renewal_id > 0) {
        $ids[] = $renewal_id;
    }

    // Plan-switch / migration order — `_arraysubs_subscription_id` is a
    // legacy single key written by PlanManager and CheckoutMigrationTrait.
    $legacy_id = (int) $order->get_meta('_arraysubs_subscription_id', true);
    if ($legacy_id > 0) {
        $ids[] = $legacy_id;
    }

    // Order items — each line may carry `_subscription_id`.
    foreach ($order->get_items() as $item) {
        $line_id = (int) $item->get_meta('_subscription_id', true);
        if ($line_id > 0) {
            $ids[] = $line_id;
        }
    }

    // Reverse lookup: subscriptions whose parent or pending-renewal order
    // points back to this order. Handles cases where the order was created
    // out-of-band (gateway-driven, admin manual, etc.) and the order meta
    // was never stamped.
    if (empty($ids)) {
        $linked_subs = get_posts([
            'post_type'      => 'arraysubs_data',
            'post_status'    => 'any',
            'posts_per_page' => 50,
            'fields'         => 'ids',
            'meta_query'     => [
                'relation' => 'OR',
                ['key' => '_parent_order_id', 'value' => $order_id, 'type' => 'NUMERIC'],
                ['key' => '_pending_renewal_order_id', 'value' => $order_id, 'type' => 'NUMERIC'],
            ],
        ]);
        foreach ($linked_subs as $sub_id) {
            $ids[] = (int) $sub_id;
        }
    }

    return array_values(array_unique(array_filter($ids, fn($id) => $id > 0)));
}

/**
 * Get subscriptions from an order
 *
 * @param int|\WC_Order $order Order ID or object
 * @return array Array of subscription post objects
 */
function arraysubs_get_order_subscriptions($order): array
{
    if (is_numeric($order)) {
        $order = wc_get_order($order);
    }

    if (!$order) {
        return [];
    }

    $subscription_ids = arraysubs_get_subscription_ids_for_order($order);

    if (empty($subscription_ids)) {
        return [];
    }

    $subscriptions = [];

    foreach ($subscription_ids as $subscription_id) {
        $subscription = arraysubs_get_subscription($subscription_id);
        if ($subscription) {
            $subscriptions[] = $subscription;
        }
    }

    return $subscriptions;
}

/**
 * Get all orders for a subscription
 *
 * @param int    $subscription_id Subscription post ID
 * @param string $order_by Sort order: 'desc' (newest first) or 'asc' (oldest first)
 * @return array Array of WC_Order objects
 */
function arraysubs_get_subscription_orders(int $subscription_id, string $order_by = 'desc'): array
{
    $order_ids_json = get_post_meta($subscription_id, '_order_ids', true);
    $order_ids = !empty($order_ids_json) ? json_decode($order_ids_json, true) : [];

    if (empty($order_ids) || !is_array($order_ids)) {
        // Fallback to parent order
        $parent_order_id = get_post_meta($subscription_id, '_parent_order_id', true);
        if ($parent_order_id) {
            $order_ids = [$parent_order_id];
        } else {
            return [];
        }
    }

    $orders = [];

    foreach ($order_ids as $order_id) {
        $order = wc_get_order($order_id);
        if ($order) {
            $orders[] = $order;
        }
    }

    // Sort orders by date
    usort($orders, function ($a, $b) use ($order_by) {
        $date_a = $a->get_date_created() ? $a->get_date_created()->getTimestamp() : 0;
        $date_b = $b->get_date_created() ? $b->get_date_created()->getTimestamp() : 0;

        return $order_by === 'desc' ? ($date_b - $date_a) : ($date_a - $date_b);
    });

    return $orders;
}

/**
 * Get parent order for a subscription
 *
 * @param int $subscription_id Subscription post ID
 * @return \WC_Order|null Order object or null
 */
function arraysubs_get_subscription_parent_order(int $subscription_id): ?\WC_Order
{
    $parent_order_id = get_post_meta($subscription_id, '_parent_order_id', true);

    if (empty($parent_order_id)) {
        return null;
    }

    $order = wc_get_order($parent_order_id);

    return $order ?: null;
}

/**
 * Add order to subscription's order history
 *
 * @param int $subscription_id Subscription post ID
 * @param int $order_id        Order ID to add
 * @return bool True on success
 */
function arraysubs_add_subscription_order(int $subscription_id, int $order_id): bool
{
    $order_ids_json = get_post_meta($subscription_id, '_order_ids', true);
    $order_ids = !empty($order_ids_json) ? json_decode($order_ids_json, true) : [];

    if (!is_array($order_ids)) {
        $order_ids = [];
    }

    // Add order if not already present
    if (!in_array($order_id, $order_ids, true)) {
        $order_ids[] = $order_id;
        update_post_meta($subscription_id, '_order_ids', wp_json_encode($order_ids));
        return true;
    }

    return false;
}

/**
 * Check if order contains subscription products
 *
 * @param int|\WC_Order $order Order ID or object
 * @return bool True if order has subscriptions
 */
function arraysubs_order_contains_subscription($order): bool
{
    if (is_numeric($order)) {
        $order = wc_get_order($order);
    }

    if (!$order) {
        return false;
    }

    return !empty(arraysubs_get_subscription_ids_for_order($order));
}

/**
 * Get subscription by order and product
 *
 * @param int|\WC_Order $order Order ID or object
 * @param int           $product_id Product ID
 * @return \WP_Post|null Subscription post object or null
 */
function arraysubs_get_order_subscription_by_product($order, int $product_id): ?\WP_Post
{
    $subscriptions = arraysubs_get_order_subscriptions($order);

    foreach ($subscriptions as $subscription) {
        $sub_product_id = get_post_meta($subscription->ID, '_product_id', true);
        $sub_variation_id = get_post_meta($subscription->ID, '_variation_id', true);

        if ((int) $sub_product_id === $product_id || (int) $sub_variation_id === $product_id) {
            return $subscription;
        }
    }

    return null;
}

/**
 * Check if cart contains subscription products
 *
 * @return bool True if cart has subscriptions
 */
function arraysubs_cart_contains_subscription(): bool
{
    $cart = WC()->cart;

    if (!$cart) {
        return false;
    }

    foreach ($cart->get_cart() as $cart_item) {
        $product_id = $cart_item['product_id'];
        $variation_id = $cart_item['variation_id'] ?? 0;
        $check_id = $variation_id > 0 ? $variation_id : $product_id;

        if (arraysubs_is_subscription_product($check_id)) {
            return true;
        }
    }

    return false;
}
