<?php

/**
 * Renewal Scheduler
 *
 * Handles scheduling of subscription renewal actions using the centralized ActionScheduler.
 * This is a convenience wrapper for renewal-specific scheduling operations.
 *
 * @package ArraySubs\Features\RecurringBilling\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\RecurringBilling\Services;

defined('ABSPATH') || exit;

use ArraySubs\Supports\ActionScheduler;

/**
 * Renewal scheduler service
 *
 * IMPORTANT: This class now uses the centralized ActionScheduler system.
 * All scheduling operations go through ActionScheduler to ensure:
 * - Consistent argument format (indexed arrays)
 * - Consistent group naming
 * - Duplicate prevention
 * - Proper logging
 */
class RenewalScheduler
{
    /**
     * Enable auto-loading
     *
     * @var bool
     */
    public static $loadable = true;

    /**
     * Schedule a subscription renewal
     *
     * @param int $subscription_id Subscription ID
     * @param int $timestamp Unix timestamp for when to process renewal (UTC)
     * @return int|bool Action ID or false on failure
     */
    public static function schedule(int $subscription_id, int $timestamp)
    {
        self::scheduleInvoice($subscription_id, $timestamp);

        return ActionScheduler::scheduleSingle(
            ActionScheduler::HOOK_PROCESS_RENEWAL,
            $timestamp,
            [$subscription_id],
            ActionScheduler::GROUP_RENEWALS
        );
    }

    /**
     * Check if renewal is already scheduled
     *
     * @param int $subscription_id Subscription ID
     * @return bool True if scheduled
     */
    public static function isScheduled(int $subscription_id): bool
    {
        return ActionScheduler::isScheduled(
            ActionScheduler::HOOK_PROCESS_RENEWAL,
            [$subscription_id],
            ActionScheduler::GROUP_RENEWALS
        );
    }

    /**
     * Unschedule a subscription renewal
     *
     * @param int $subscription_id Subscription ID
     * @return void
     */
    public static function unschedule(int $subscription_id): void
    {
        ActionScheduler::unscheduleAll(
            ActionScheduler::HOOK_PROCESS_RENEWAL,
            [$subscription_id],
            ActionScheduler::GROUP_RENEWALS
        );

        ActionScheduler::unscheduleAll(
            ActionScheduler::HOOK_GENERATE_RENEWAL_INVOICE,
            [$subscription_id],
            ActionScheduler::GROUP_BILLING
        );
    }

    /**
     * Get next scheduled renewal time
     *
     * @param int $subscription_id Subscription ID
     * @return int|false Timestamp or false if not scheduled
     */
    public static function getNextScheduledTime(int $subscription_id)
    {
        return ActionScheduler::getNextScheduledTime(
            ActionScheduler::HOOK_PROCESS_RENEWAL,
            [$subscription_id],
            ActionScheduler::GROUP_RENEWALS
        );
    }

    /**
     * Schedule exact renewal invoice generation for a subscription.
     *
     * The hourly batch remains as a recovery net, but this keeps merchant
     * configured invoice lead times precise for normal operation.
     *
     * @param int $subscription_id Subscription ID.
     * @param int $renewal_timestamp Renewal due timestamp in UTC.
     * @return int|bool Action ID or false on failure.
     */
    public static function scheduleInvoice(int $subscription_id, int $renewal_timestamp)
    {
        $invoice_timestamp = self::getInvoiceTimestamp($subscription_id, $renewal_timestamp);

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

        return ActionScheduler::scheduleSingle(
            ActionScheduler::HOOK_GENERATE_RENEWAL_INVOICE,
            $invoice_timestamp,
            [$subscription_id],
            ActionScheduler::GROUP_BILLING
        );
    }

    /**
     * Get the invoice generation timestamp for a renewal timestamp.
     *
     * @param int $subscription_id Subscription ID.
     * @param int $renewal_timestamp Renewal due timestamp in UTC.
     * @return int Invoice generation timestamp.
     */
    private static function getInvoiceTimestamp(int $subscription_id, int $renewal_timestamp): int
    {
        if ($renewal_timestamp <= 0) {
            return 0;
        }

        $lead_hours = function_exists('arraysubs_get_invoice_hours_before_due')
            ? arraysubs_get_invoice_hours_before_due()
            : 6;

        $cycle_hours = self::getBillingCycleInHours($subscription_id);

        if ($cycle_hours > 0 && $lead_hours >= $cycle_hours) {
            $lead_hours = 6;
        }

        $invoice_timestamp = $renewal_timestamp - ($lead_hours * HOUR_IN_SECONDS);

        if ($invoice_timestamp <= time() && $renewal_timestamp > time()) {
            return time() + MINUTE_IN_SECONDS;
        }

        return $invoice_timestamp;
    }

    /**
     * Reschedule pending invoice-generation jobs for all live subscriptions
     * after the merchant changes the invoice-before-due lead time. The new
     * lead time is read by getInvoiceTimestamp() and applied per-subscription.
     *
     * Walks live subscriptions with a future _next_payment_date and:
     *   - unschedules any pending HOOK_GENERATE_RENEWAL_INVOICE
     *   - schedules a new one using the current lead-time settings
     *
     * Capped to a sane batch limit. Larger sites should run this via a
     * background job (Action Scheduler) rather than synchronously.
     *
     * @param int $limit Max subscriptions to process in one pass.
     * @return int Number of subscriptions whose invoice job was rescheduled.
     */
    public static function rescheduleAllInvoicesForSettingsChange(int $limit = 500): int
    {
        $subscription_ids = get_posts([
            'post_type'      => 'arraysubs_data',
            'post_status'    => ['arraysubs-active', 'arraysubs-on-hold', 'arraysubs-trial'],
            'posts_per_page' => max(1, $limit),
            'fields'         => 'ids',
            'meta_query'     => [
                [
                    'key'     => '_next_payment_date',
                    'value'   => '',
                    'compare' => '!=',
                ],
            ],
        ]);

        $count = 0;
        $now = time();
        foreach ($subscription_ids as $subscription_id) {
            $next_payment = get_post_meta($subscription_id, '_next_payment_date', true);
            if (empty($next_payment)) {
                continue;
            }

            $renewal_ts = strtotime($next_payment);
            if (!$renewal_ts || $renewal_ts <= $now) {
                continue;
            }

            ActionScheduler::unscheduleAll(
                ActionScheduler::HOOK_GENERATE_RENEWAL_INVOICE,
                [(int) $subscription_id],
                ActionScheduler::GROUP_BILLING
            );

            self::scheduleInvoice((int) $subscription_id, $renewal_ts);
            $count++;
        }

        return $count;
    }

    /**
     * Get subscription billing cycle length in hours.
     *
     * @param int $subscription_id Subscription ID.
     * @return int Billing cycle hours.
     */
    private static function getBillingCycleInHours(int $subscription_id): int
    {
        $interval = (int) get_post_meta($subscription_id, '_billing_interval', true) ?: 1;
        $period   = get_post_meta($subscription_id, '_billing_period', true) ?: 'month';

        $hours = [
            'day'   => 24,
            'week'  => 24 * 7,
            'month' => 24 * 30,
            'year'  => 24 * 365,
        ];

        return $interval * ($hours[$period] ?? (24 * 30));
    }
}
