<?php

/**
 * Pending Switch Helper Functions
 *
 * Stores and retrieves a deferred plan switch ("Apply at Next Renewal").
 * The switch is captured immediately when the customer requests it but
 * is NOT applied to the subscription until the next renewal order is
 * paid. The renewal order itself is built using the target plan, and on
 * payment success the subscription product/entitlement transitions.
 *
 * Storage: a single subscription post meta `_pending_switch` holding a
 * JSON-encoded array. Single key keeps reads cheap and removes the
 * possibility of orphaned partial state.
 *
 * @package ArraySubs
 * @since 1.0.0
 */

defined('ABSPATH') || exit;

/**
 * Set (or replace) the pending plan switch for a subscription.
 *
 * The helper can replace an existing payload for administrative/canonical
 * callers, but user-facing controllers must require explicit replacement
 * intent before calling it when a pending switch already exists.
 *
 * @param int   $subscription_id Subscription post ID.
 * @param array $data            Pending-switch data. Required keys:
 *                               - target_product_id (int)
 *                               - target_variation_id (int|0)
 *                               - quantity (int, default 1)
 *                               - switch_type ('upgrade'|'downgrade'|'crossgrade')
 *                               - switch_fee (float, default 0)
 *                               - new_price (float, per-unit recurring price after switch)
 *                               - new_billing_period (string, optional)
 *                               - new_billing_interval (int, optional)
 *                               - requested_by (string, e.g. 'customer' or 'admin:42')
 * @return bool True on success.
 */
function arraysubs_set_pending_switch(int $subscription_id, array $data): bool
{
    if ($subscription_id <= 0) {
        return false;
    }

    $payload = [
        'target_product_id'    => isset($data['target_product_id']) ? absint($data['target_product_id']) : 0,
        'target_variation_id'  => isset($data['target_variation_id']) ? absint($data['target_variation_id']) : 0,
        'quantity'             => isset($data['quantity']) ? max(1, absint($data['quantity'])) : 1,
        'switch_type'          => isset($data['switch_type']) ? sanitize_key($data['switch_type']) : '',
        'switch_fee'           => isset($data['switch_fee']) ? max(0.0, (float) $data['switch_fee']) : 0.0,
        'new_price'            => isset($data['new_price']) ? max(0.0, (float) $data['new_price']) : 0.0,
        'new_billing_period'   => isset($data['new_billing_period']) ? sanitize_key($data['new_billing_period']) : '',
        'new_billing_interval' => isset($data['new_billing_interval']) ? max(1, absint($data['new_billing_interval'])) : 0,
        'requested_by'         => isset($data['requested_by']) ? sanitize_text_field($data['requested_by']) : 'system',
        'requested_at'         => current_time('mysql', true),
        'effective_renewal_date' => (string) get_post_meta($subscription_id, '_next_payment_date', true),
    ];

    if ($payload['target_product_id'] <= 0 || $payload['switch_type'] === '') {
        return false;
    }

    update_post_meta($subscription_id, '_pending_switch', wp_json_encode($payload));

    /**
     * Fires after a pending switch is stored.
     *
     * @param int   $subscription_id Subscription post ID.
     * @param array $payload         The stored pending-switch payload.
     */
    do_action('arraysubs_pending_switch_set', $subscription_id, $payload);

    return true;
}

/**
 * Retrieve the pending switch payload, or null if none exists.
 *
 * @param int $subscription_id Subscription post ID.
 * @return array|null
 */
function arraysubs_get_pending_switch(int $subscription_id): ?array
{
    if ($subscription_id <= 0) {
        return null;
    }

    $raw = get_post_meta($subscription_id, '_pending_switch', true);
    if (empty($raw)) {
        return null;
    }

    $decoded = is_string($raw) ? json_decode($raw, true) : $raw;
    return is_array($decoded) ? $decoded : null;
}

/**
 * Clear any pending switch on a subscription.
 *
 * Called when the customer undoes the switch, when the subscription is
 * cancelled (the renewal that would have applied it is gone), or when
 * the switch has been successfully applied at renewal payment.
 *
 * @param int $subscription_id Subscription post ID.
 * @return void
 */
function arraysubs_clear_pending_switch(int $subscription_id): void
{
    if ($subscription_id <= 0) {
        return;
    }

    if (!metadata_exists('post', $subscription_id, '_pending_switch')) {
        return;
    }

    delete_post_meta($subscription_id, '_pending_switch');

    do_action('arraysubs_pending_switch_cleared', $subscription_id);
}

/**
 * Check whether a subscription has a pending plan switch.
 *
 * @param int $subscription_id Subscription post ID.
 * @return bool
 */
function arraysubs_has_pending_switch(int $subscription_id): bool
{
    return arraysubs_get_pending_switch($subscription_id) !== null;
}

/**
 * Retrieve an enriched pending switch payload for UI and REST responses.
 *
 * @param int $subscription_id Subscription post ID.
 * @return array|null
 */
function arraysubs_get_pending_switch_details(int $subscription_id): ?array
{
    $payload = arraysubs_get_pending_switch($subscription_id);
    if ($payload === null) {
        return null;
    }

    $target_product_id = absint($payload['target_product_id'] ?? 0);
    $target_variation_id = absint($payload['target_variation_id'] ?? 0);
    $target_item_id = $target_variation_id ?: $target_product_id;
    $target_product = $target_item_id > 0 ? wc_get_product($target_item_id) : null;
    $quantity = max(1, absint($payload['quantity'] ?? 1));
    $target_unit_price = isset($payload['new_price']) ? max(0.0, (float) $payload['new_price']) : 0.0;
    $target_line_total = $target_unit_price * $quantity;
    $switch_fee = isset($payload['switch_fee']) ? max(0.0, (float) $payload['switch_fee']) : 0.0;
    $currency = get_post_meta($subscription_id, '_currency', true) ?: get_woocommerce_currency();
    $effective_date = (string) ($payload['effective_renewal_date'] ?? '');

    $can_manage = current_user_can('manage_woocommerce') || current_user_can('manage_options');
    $customer_id = absint(get_post_meta($subscription_id, '_customer_id', true));
    $can_customer_manage = is_user_logged_in() && $customer_id > 0 && $customer_id === get_current_user_id();

    return [
        'raw' => $payload,
        'target_product_id' => $target_product_id,
        'target_variation_id' => $target_variation_id,
        'target_product_name' => $target_product ? arraysubs_get_subscription_product_name($target_product) : __('Unknown plan', 'arraysubs'),
        'quantity' => $quantity,
        'switch_type' => sanitize_key($payload['switch_type'] ?? ''),
        'effective_renewal_date' => $effective_date,
        'effective_renewal_date_display' => $effective_date !== ''
            ? arraysubs_format_date_local($effective_date, get_option('date_format'))
            : __('next renewal', 'arraysubs'),
        'requested_at' => (string) ($payload['requested_at'] ?? ''),
        'requested_at_display' => !empty($payload['requested_at'])
            ? arraysubs_format_date_local((string) $payload['requested_at'], get_option('date_format') . ' ' . get_option('time_format'))
            : '',
        'requested_by' => sanitize_text_field($payload['requested_by'] ?? 'system'),
        'target_unit_price' => $target_unit_price,
        'target_line_total' => $target_line_total,
        'switch_fee_total' => $switch_fee,
        'formatted_unit_price' => wc_price($target_unit_price, ['currency' => $currency]),
        'formatted_line_total' => wc_price($target_line_total, ['currency' => $currency]),
        'formatted_switch_fee' => wc_price($switch_fee, ['currency' => $currency]),
        'can_cancel' => $can_manage || $can_customer_manage,
        'can_replace' => $can_manage || $can_customer_manage,
    ];
}
