<?php

/**
 * Subscription helper functions
 *
 * @package ArraySubs\Functions
 */

defined('ABSPATH') || exit;

/**
 * Get all subscription statuses
 *
 * @return array Associative array of status slug => label
 */
function arraysubs_get_subscription_statuses(): array
{
    return [
        'arraysubs-pending'    => _x('Pending', 'Subscription status', 'arraysubs'),
        'arraysubs-active'     => _x('Active', 'Subscription status', 'arraysubs'),
        'arraysubs-on-hold'    => _x('On Hold', 'Subscription status', 'arraysubs'),
        'arraysubs-cancelled'  => _x('Cancelled', 'Subscription status', 'arraysubs'),
        'arraysubs-expired'    => _x('Expired', 'Subscription status', 'arraysubs'),
        'arraysubs-trial'      => _x('Trial', 'Subscription status', 'arraysubs'),
    ];
}

/**
 * Get subscription status label
 *
 * @param string $status Status slug (with or without 'arraysubs-' prefix)
 * @return string Status label
 */
function arraysubs_get_status_label(string $status): string
{
    // Add prefix if not present
    if (strpos($status, 'arraysubs-') !== 0) {
        $status = 'arraysubs-' . $status;
    }

    $statuses = arraysubs_get_subscription_statuses();
    return $statuses[$status] ?? ucfirst(str_replace('arraysubs-', '', $status));
}

/**
 * Acquire the mutation lock for a subscription.
 *
 * @param int $subscription_id Subscription ID.
 * @return bool True when acquired.
 */
function arraysubs_acquire_subscription_mutation_lock(int $subscription_id): bool
{
    if ($subscription_id <= 0 || !class_exists(\ArraySubs\Supports\ActionScheduler::class)) {
        return false;
    }

    return \ArraySubs\Supports\ActionScheduler::acquireLock(
        \ArraySubs\Supports\ActionScheduler::HOOK_SUBSCRIPTION_MUTATION,
        [$subscription_id]
    );
}

/**
 * Release the mutation lock for a subscription.
 *
 * @param int $subscription_id Subscription ID.
 * @return void
 */
function arraysubs_release_subscription_mutation_lock(int $subscription_id): void
{
    if ($subscription_id <= 0 || !class_exists(\ArraySubs\Supports\ActionScheduler::class)) {
        return;
    }

    \ArraySubs\Supports\ActionScheduler::releaseLock(
        \ArraySubs\Supports\ActionScheduler::HOOK_SUBSCRIPTION_MUTATION,
        [$subscription_id]
    );
}

/**
 * Create a new subscription
 *
 * IMPORTANT: Price Immutability
 * ---------------------------
 * When a subscription is created, the prices are locked in and stored as:
 * - subscription_price: The first payment amount (may include signup fee)
 * - recurring_amount: The recurring renewal price
 * 
 * These prices will NEVER change for the life of the subscription, even if:
 * - The product's regular price changes
 * - The product goes on sale or sale ends
 * - Sale price schedule dates expire
 * 
 * This ensures existing customers always pay what they originally agreed to.
 * Only NEW subscriptions will use the current product price at time of purchase.
 *
 * @param array $args Subscription arguments
 * @return int|\WP_Error Subscription post ID or WP_Error on failure
 */
function arraysubs_create_subscription(array $args)
{
    $defaults = [
        'customer_id'       => 0,
        'customer_email'    => '',
        'product_id'        => 0,
        'variation_id'      => 0,
        'quantity'          => 1,
        'billing_period'    => 'month',
        'billing_interval'  => 1,
        'subscription_length' => 0,
        'subscription_price' => 0,
        'recurring_amount'  => 0,
        'signup_fee'        => 0,
        'currency'          => get_woocommerce_currency(),
        'start_date'        => current_time('mysql', true),
        'next_payment_date' => '',
        'status'            => 'arraysubs-pending',
        'payment_method'    => '',
        'payment_method_title' => '',
        'trial_length'      => 0,
        'trial_period'      => 'day',
        'parent_order_id'   => 0,
        'enable_different_renewal_price' => false,
        'different_renewal_price' => 0,
        'different_renewal_price_after' => 1,
    ];

    $args = wp_parse_args($args, $defaults);

    // Validate required fields - product is required, customer_id OR customer_email required
    if (empty($args['product_id'])) {
        return new \WP_Error('missing_product', __('Product ID is required', 'arraysubs'));
    }

    // Allow guest checkout - need either customer_id or customer_email
    if (empty($args['customer_id']) && empty($args['customer_email'])) {
        return new \WP_Error('missing_customer', __('Customer ID or Customer Email is required', 'arraysubs'));
    }

    // Get customer email if not provided but customer_id is available
    if (empty($args['customer_email']) && !empty($args['customer_id'])) {
        $user = get_userdata($args['customer_id']);
        if ($user) {
            $args['customer_email'] = $user->user_email;
        }
    }

    // Get product name for title
    $product = wc_get_product($args['product_id']);
    if (!$product) {
        return new \WP_Error('invalid_product', __('Invalid product ID', 'arraysubs'));
    }

    // Get product name with variation suffix if applicable
    $product_to_use_for_name = $args['variation_id'] ? wc_get_product($args['variation_id']) : $product;
    $product_name = $product_to_use_for_name
        ? arraysubs_get_subscription_product_name($product_to_use_for_name)
        : $product->get_name();
    $customer_name = '';

    // Get customer name from user or fallback to email
    if (!empty($args['customer_id'])) {
        $user = get_userdata($args['customer_id']);
        if ($user) {
            $customer_name = $user->display_name ?: $user->user_login;
        }
    }

    // Fallback to email if no customer name
    if (empty($customer_name) && !empty($args['customer_email'])) {
        $customer_name = $args['customer_email'];
    }

    // Create subscription post
    $post_data = [
        'post_type'   => 'arraysubs_data',
        'post_status' => $args['status'],
        'post_title'  => sprintf(
            /* translators: 1: product name, 2: customer name/email */
            __('Subscription for %1$s - %2$s', 'arraysubs'),
            $product_name,
            $customer_name ?: __('Guest', 'arraysubs')
        ),
        'post_author' => $args['customer_id'] ?: 0,
    ];

    $subscription_id = wp_insert_post($post_data);

    if (is_wp_error($subscription_id)) {
        return $subscription_id;
    }

    // Save meta fields
    $meta_fields = [
        '_billing_period'       => $args['billing_period'],
        '_billing_interval'     => $args['billing_interval'],
        '_subscription_length'  => $args['subscription_length'],
        '_start_date'           => $args['start_date'],
        '_next_payment_date'    => $args['next_payment_date'],
        '_customer_id'          => $args['customer_id'],
        '_customer_email'       => $args['customer_email'],
        '_payment_method'       => $args['payment_method'],
        '_payment_method_title' => $args['payment_method_title'],
        '_subscription_price'   => $args['subscription_price'],
        '_recurring_amount'     => $args['recurring_amount'],
        '_signup_fee'           => $args['signup_fee'],
        '_currency'             => $args['currency'],
        '_trial_length'         => $args['trial_length'],
        '_trial_period'         => $args['trial_period'],
        '_product_id'                    => $args['product_id'],
        '_variation_id'                  => $args['variation_id'],
        '_quantity'                      => $args['quantity'],
        '_parent_order_id'               => $args['parent_order_id'],
        '_order_ids'                     => wp_json_encode([$args['parent_order_id']]),
        '_enable_different_renewal_price' => $args['enable_different_renewal_price'] ? 'yes' : '',
        '_different_renewal_price'       => $args['different_renewal_price'], // The different renewal price
        '_different_renewal_price_after' => $args['different_renewal_price_after'], // After how many periods
        '_completed_payments'            => 0, // Track number of completed payments
    ];

    foreach ($meta_fields as $key => $value) {
        update_post_meta($subscription_id, $key, $value);
    }

    // Calculate trial end date if trial is enabled
    if ($args['trial_length'] > 0) {
        $trial_end_date = arraysubs_calculate_trial_end_date(
            $args['start_date'],
            $args['trial_length'],
            $args['trial_period']
        );
        update_post_meta($subscription_id, '_trial_end_date', $trial_end_date);
    }

    return $subscription_id;
}

/**
 * Get subscription by ID
 *
 * @param int $subscription_id Subscription post ID
 * @return \WP_Post|null Subscription post object or null
 */
function arraysubs_get_subscription(int $subscription_id): ?\WP_Post
{
    $subscription = get_post($subscription_id);

    if (!$subscription || $subscription->post_type !== 'arraysubs_data') {
        return null;
    }

    return $subscription;
}

/**
 * Get all subscriptions for a customer
 *
 * @param int   $customer_id Customer user ID
 * @param array $statuses    Array of statuses to filter (default: all active-like statuses)
 * @return array Array of subscription post IDs
 */
function arraysubs_get_customer_subscriptions(int $customer_id, array $statuses = []): array
{
    if (empty($customer_id)) {
        return [];
    }

    // Default to common active-like statuses if none specified
    if (empty($statuses)) {
        $statuses = ['arraysubs-active', 'arraysubs-on-hold', 'arraysubs-trial', 'arraysubs-pending'];
    }

    $args = [
        'post_type'      => 'arraysubs_data',
        'post_status'    => $statuses,
        'posts_per_page' => -1,
        'fields'         => 'ids',
        'meta_query'     => [
            [
                'key'     => '_customer_id',
                'value'   => $customer_id,
                'compare' => '=',
                'type'    => 'NUMERIC',
            ],
        ],
    ];

    $subscriptions = get_posts($args);

    return is_array($subscriptions) ? $subscriptions : [];
}

/**
 * Get subscriptions for a customer email.
 *
 * Useful for guest checkouts or returning customers who have subscriptions
 * stored by email but are not currently logged in.
 *
 * @param string $customer_email Customer email address.
 * @param array  $statuses       Subscription statuses to match.
 * @return array
 */
function arraysubs_get_customer_subscriptions_by_email(string $customer_email, array $statuses = []): array
{
    $customer_email = sanitize_email($customer_email);

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

    if (empty($statuses)) {
        $statuses = ['arraysubs-active', 'arraysubs-on-hold', 'arraysubs-trial', 'arraysubs-pending'];
    }

    $subscriptions = get_posts([
        'post_type'      => 'arraysubs_data',
        'post_status'    => $statuses,
        'posts_per_page' => -1,
        'fields'         => 'ids',
        'meta_query'     => [
            [
                'key'     => '_customer_email',
                'value'   => $customer_email,
                'compare' => '=',
            ],
        ],
    ]);

    return is_array($subscriptions) ? $subscriptions : [];
}

/**
 * Get subscriptions for a checkout identity using customer ID and/or email.
 *
 * @param int    $customer_id    Customer user ID.
 * @param string $customer_email Customer email address.
 * @param array  $statuses       Subscription statuses to match.
 * @return array
 */
function arraysubs_get_customer_subscriptions_for_identity(int $customer_id, string $customer_email = '', array $statuses = []): array
{
    $subscription_ids = [];

    if ($customer_id > 0) {
        $subscription_ids = arraysubs_get_customer_subscriptions($customer_id, $statuses);
    }

    if (!empty($customer_email)) {
        $subscription_ids = array_merge(
            $subscription_ids,
            arraysubs_get_customer_subscriptions_by_email($customer_email, $statuses)
        );
    }

    $subscription_ids = array_values(array_unique(array_map('absint', $subscription_ids)));

    return array_values(array_filter($subscription_ids));
}

/**
 * Get the effective product identity for a subscription.
 *
 * Variation IDs take priority over parent product IDs so duplicate matching and
 * plan switching use the exact purchasable item when available.
 *
 * @param int $subscription_id Subscription ID.
 * @return int
 */
function arraysubs_get_subscription_effective_product_id(int $subscription_id): int
{
    $variation_id = absint(get_post_meta($subscription_id, '_variation_id', true));
    $product_id = absint(get_post_meta($subscription_id, '_product_id', true));

    return $variation_id > 0 ? $variation_id : $product_id;
}

/**
 * Update subscription
 *
 * @param int   $subscription_id Subscription post ID
 * @param array $args            Arguments to update
 * @return bool|WP_Error True on success, WP_Error on failure
 */
function arraysubs_update_subscription(int $subscription_id, array $args)
{
    $subscription = arraysubs_get_subscription($subscription_id);

    if (!$subscription) {
        return new \WP_Error('invalid_subscription', __('Invalid subscription ID', 'arraysubs'));
    }

    // Update post status if provided
    if (isset($args['status'])) {
        wp_update_post([
            'ID'          => $subscription_id,
            'post_status' => $args['status'],
        ]);
        unset($args['status']);
    }

    // Update meta fields
    foreach ($args as $key => $value) {
        // Add underscore prefix if not present
        if (strpos($key, '_') !== 0) {
            $key = '_' . $key;
        }
        update_post_meta($subscription_id, $key, $value);
    }

    do_action('arraysubs_data_updated', $subscription_id, $args);

    return true;
}

/**
 * Delete subscription
 *
 * @param int  $subscription_id Subscription post ID
 * @param bool $force_delete    Whether to permanently delete (true) or trash (false)
 * @return bool True on success, false on failure
 */
function arraysubs_delete_subscription(int $subscription_id, bool $force_delete = false): bool
{
    $subscription = arraysubs_get_subscription($subscription_id);

    if (!$subscription) {
        return false;
    }

    do_action('arraysubs_before_delete_subscription', $subscription_id, $force_delete);

    $result = wp_delete_post($subscription_id, $force_delete);

    if ($result) {
        do_action('arraysubs_data_deleted', $subscription_id, $force_delete);
        return true;
    }

    return false;
}

/**
 * Calculate trial end date
 *
 * @param string $start_date   Start date (Y-m-d H:i:s)
 * @param int    $trial_length Trial length
 * @param string $trial_period Trial period (day, week, month, year)
 * @return string Trial end date (Y-m-d H:i:s)
 */
function arraysubs_calculate_trial_end_date(string $start_date, int $trial_length, string $trial_period): string
{
    $date = new \DateTime($start_date);

    switch ($trial_period) {
        case 'day':
            $date->modify("+{$trial_length} days");
            break;
        case 'week':
            $date->modify("+{$trial_length} weeks");
            break;
        case 'month':
            $date->modify("+{$trial_length} months");
            break;
        case 'year':
            $date->modify("+{$trial_length} years");
            break;
    }

    return $date->format('Y-m-d H:i:s');
}

/**
 * Calculate next payment date
 *
 * @param string $current_date    Current date (Y-m-d H:i:s)
 * @param int    $billing_interval Billing interval
 * @param string $billing_period   Billing period (day, week, month, year)
 * @return string Next payment date (Y-m-d H:i:s)
 */
function arraysubs_calculate_next_payment_date(string $current_date, int $billing_interval, string $billing_period): string
{
    // Lifetime subscriptions have no recurring payments
    if ($billing_period === 'lifetime') {
        return '';
    }

    $date = new \DateTime($current_date);

    switch ($billing_period) {
        case 'day':
            $date->modify("+{$billing_interval} days");
            break;
        case 'week':
            $date->modify("+{$billing_interval} weeks");
            break;
        case 'month':
            $date->modify("+{$billing_interval} months");
            break;
        case 'year':
            $date->modify("+{$billing_interval} years");
            break;
    }

    return $date->format('Y-m-d H:i:s');
}

/**
 * Get billing period options
 *
 * @return array Period slug => label
 */
function arraysubs_get_billing_periods(): array
{
    return [
        'day'      => __('Day', 'arraysubs'),
        'week'     => __('Week', 'arraysubs'),
        'month'    => __('Month', 'arraysubs'),
        'year'     => __('Year', 'arraysubs'),
        'lifetime' => __('Lifetime Deal', 'arraysubs'),
    ];
}

/**
 * Format subscription billing schedule for display
 *
 * @param int    $interval Billing interval
 * @param string $period   Billing period
 * @return string Formatted billing schedule
 */
function arraysubs_get_billing_schedule_string(int $interval, string $period): string
{
    // Lifetime subscriptions have no recurring billing schedule
    if ($period === 'lifetime') {
        return __('Lifetime Deal', 'arraysubs');
    }

    $periods = arraysubs_get_billing_periods();
    $period_label = $periods[$period] ?? $period;

    if ($interval === 1) {
        /* translators: %s: period label (e.g., "week", "month") */
        return sprintf(/* translators: %s: period label (e.g., "week", "month") */__('Every %s', 'arraysubs'), strtolower($period_label));
    }

    /* translators: 1: billing interval number, 2: period name in plural (e.g., "weeks", "months") */
    return sprintf(
        /* translators: 1: billing interval number, 2: period name in plural (e.g., "weeks", "months") */
        __('Every %1$d %2$s', 'arraysubs'),
        $interval,
        strtolower($period_label) . 's'
    );
}

/**
 * Format a lowercase billing schedule phrase for inline sentence copy.
 *
 * Returns strings like "every 1 week" and "every 2 months".
 *
 * @param int    $interval Billing interval.
 * @param string $period   Billing period.
 * @return string Formatted billing schedule phrase.
 */
function arraysubs_get_billing_schedule_phrase(int $interval, string $period): string
{
    if ($period === 'lifetime') {
        return strtolower(__('Lifetime Deal', 'arraysubs'));
    }

    return sprintf(
        /* translators: %s: billing duration such as "1 week" or "2 months". */
        __('every %s', 'arraysubs'),
        arraysubs_get_duration_string(max(1, $interval), $period)
    );
}

/**
 * Format a plain duration string (without "Every" prefix)
 *
 * Used for trial periods, subscription lengths, and other non-recurring durations.
 * Returns strings like "7 days", "1 month", "2 weeks".
 *
 * @param int    $interval Duration interval
 * @param string $period   Duration period (day, week, month, year)
 * @return string Formatted duration string
 */
function arraysubs_get_duration_string(int $interval, string $period): string
{
    $period_labels = [
        'day'   => _n('day', 'days', $interval, 'arraysubs'),
        'week'  => _n('week', 'weeks', $interval, 'arraysubs'),
        'month' => _n('month', 'months', $interval, 'arraysubs'),
        'year'  => _n('year', 'years', $interval, 'arraysubs'),
    ];

    $period_label = $period_labels[$period] ?? $period;

    /* translators: 1: duration number, 2: period name in plural (e.g., "days", "months") */
    return sprintf(
        /* translators: 1: duration number, 2: period name in plural (e.g., "days", "months") */
        __('%1$d %2$s', 'arraysubs'),
        $interval,
        $period_label
    );
}

/**
 * Get count of customer's active subscriptions
 *
 * @param int   $customer_id Customer user ID
 * @param array $statuses    Optional. Statuses to count. Default: active subscriptions only.
 * @return int Number of subscriptions
 */
function arraysubs_get_customer_subscription_count(int $customer_id, array $statuses = []): int
{
    if (empty($customer_id)) {
        return 0;
    }

    // Default to active-like statuses if not specified
    if (empty($statuses)) {
        $statuses = ['arraysubs-active', 'arraysubs-trial'];
    }

    $args = [
        'post_type'      => 'arraysubs_data',
        'post_status'    => $statuses,
        'posts_per_page' => -1,
        'fields'         => 'ids',
        'meta_query'     => [
            [
                'key'     => '_customer_id',
                'value'   => $customer_id,
                'compare' => '=',
                'type'    => 'NUMERIC',
            ],
        ],
    ];

    $query = new \WP_Query($args);
    return $query->found_posts;
}

/**
 * Get subscription product name with variation attributes
 *
 * For variation products, this returns "Parent Product Name - Variation Attributes"
 * For simple products, this returns just the product name
 *
 * @param int|WC_Product $product_or_id Product ID or WC_Product object
 * @return string Product name with variation suffix if applicable
 */
function arraysubs_get_subscription_product_name($product_or_id): string
{
    $product = function_exists('arraysubs_get_wc_product')
        ? arraysubs_get_wc_product($product_or_id)
        : (is_numeric($product_or_id) && function_exists('wc_get_product') ? wc_get_product($product_or_id) : $product_or_id);

    if (!$product instanceof \WC_Product) {
        return '';
    }

    // For variations, get the full name with attributes
    if ($product->is_type('variation')) {
        // WooCommerce's get_name() on variations returns "Parent Name - attr1, attr2"
        // But we can also build it ourselves for better control
        $parent_id = $product->get_parent_id();
        $parent = wc_get_product($parent_id);

        if ($parent) {
            $parent_name = $parent->get_name();
            $attributes = $product->get_attributes();

            if (!empty($attributes)) {
                $attribute_values = [];
                foreach ($attributes as $attribute_name => $attribute_value) {
                    // Get the term name if it's a taxonomy attribute
                    if (taxonomy_exists($attribute_name)) {
                        $term = get_term_by('slug', $attribute_value, $attribute_name);
                        if ($term && !is_wp_error($term)) {
                            $attribute_values[] = $term->name;
                        } else {
                            $attribute_values[] = $attribute_value;
                        }
                    } else {
                        $attribute_values[] = $attribute_value;
                    }
                }

                if (!empty($attribute_values)) {
                    return $parent_name . ' - ' . implode(', ', $attribute_values);
                }
            }

            // Fallback to parent name if no attributes
            return $parent_name;
        }
    }

    // For simple/other products, just return the product name
    return $product->get_name();
}

/**
 * Get subscription product name from subscription meta
 *
 * Gets the product name with variation attributes from subscription metadata.
 * Uses _variation_id if available, otherwise falls back to _product_id.
 *
 * @param int $subscription_id Subscription ID
 * @return string Product name with variation suffix if applicable
 */
function arraysubs_get_subscription_product_name_by_subscription(int $subscription_id): string
{
    $variation_id = get_post_meta($subscription_id, '_variation_id', true);
    $product_id = get_post_meta($subscription_id, '_product_id', true);

    // Use variation ID if available, otherwise use product ID
    $actual_product_id = $variation_id ?: $product_id;

    if (!$actual_product_id) {
        return '';
    }

    return arraysubs_get_subscription_product_name($actual_product_id);
}

/**
 * Check if a subscription is a lifetime deal (no recurring billing)
 *
 * @param int|\WP_Post $subscription Subscription ID or WP_Post object
 * @return bool True if the subscription has a lifetime billing period
 */
function arraysubs_is_lifetime_subscription($subscription): bool
{
    if ($subscription instanceof \WP_Post) {
        $subscription_id = $subscription->ID;
    } else {
        $subscription_id = (int) $subscription;
    }

    if ($subscription_id <= 0) {
        return false;
    }

    $billing_period = get_post_meta($subscription_id, '_billing_period', true);

    return $billing_period === 'lifetime';
}

/**
 * Check if a product is configured as a lifetime deal
 *
 * @param int|\WC_Product $product Product ID or WC_Product object
 * @return bool True if the product has a lifetime billing period
 */
function arraysubs_is_lifetime_product($product): bool
{
    if (is_numeric($product)) {
        $product_id = (int) $product;
    } elseif ($product instanceof \WC_Product) {
        $product_id = $product->get_id();
    } else {
        return false;
    }

    $period = get_post_meta($product_id, '_subscription_period', true);

    return $period === 'lifetime';
}
