<?php

/**
 * Skip Renewal Hooks
 *
 * Registers WordPress hooks for the skip renewal and pause subscription features.
 * Uses the centralized ActionScheduler for scheduled jobs.
 *
 * Key Features:
 * 1. Skip Next Renewal - Skip 1 or more upcoming renewal cycles
 * 2. Pause Subscription - Temporarily suspend subscription entirely
 *
 * @package ArraySubs\Features\SkipRenewal\Services
 * @since 1.0.0
 */

namespace ArraySubs\Features\SkipRenewal\Services;

defined('ABSPATH') || exit;

use ArraySubs\Supports\ActionScheduler;

/**
 * Skip Renewal hooks integration
 */
class Hooks
{
    /**
     * Enable auto-loading via AbstractLoader
     *
     * @var bool
     */
    public static $loadable = true;

    /**
     * Initialize hooks
     */
    public function __construct()
    {
        // Register action handlers for scheduled jobs
        add_action(ActionScheduler::HOOK_RESUME_SUBSCRIPTION, [$this, 'handleAutoResume'], 10, 1);
        add_action(ActionScheduler::HOOK_PROCESS_SKIPPED_CYCLE, [$this, 'handleSkippedCycle'], 10, 1);

        // Hook into renewal generation to check for skipped cycles
        add_filter('arraysubs_should_generate_renewal_invoice', [$this, 'checkSkipBeforeRenewal'], 10, 2);

        // Add settings defaults
        add_filter('arraysubs_default_settings', [$this, 'addDefaultSettings']);

        // Subscription notes for skip/pause events
        add_action('arraysubs_cycles_skipped', [$this, 'addSkipNote'], 10, 4);
        add_action('arraysubs_skip_undone', [$this, 'addUndoSkipNote'], 10, 3);
        add_action('arraysubs_skip_modified', [$this, 'addModifySkipNote'], 10, 4);
        add_action('arraysubs_subscription_paused', [$this, 'addPauseNote'], 10, 4);
        add_action('arraysubs_subscription_resumed', [$this, 'addResumeNote'], 10, 4);

        // Email notifications for skip/pause events
        add_action('arraysubs_cycles_skipped', [$this, 'sendSkipEmail'], 10, 4);
        add_action('arraysubs_skip_undone', [$this, 'sendSkipUndoneEmail'], 10, 3);
        add_action('arraysubs_subscription_paused', [$this, 'sendPauseEmail'], 10, 4);
        add_action('arraysubs_subscription_resumed', [$this, 'sendResumeEmail'], 10, 4);
    }

    /**
     * Add default settings for skip/pause features
     *
     * @param array $defaults Current default settings
     * @return array Modified defaults
     */
    public function addDefaultSettings(array $defaults): array
    {
        $defaults['skip_renewal'] = [
            'enabled'           => true,
            'max_cycles'        => 3,
            'cutoff_days'       => 2,
            'customer_can_skip' => true,
        ];

        $defaults['pause_subscription'] = [
            'enabled'                     => true,
            'max_duration_days'           => 30,
            'max_pauses_per_subscription' => 2,
            'min_days_between_pauses'     => 30,
            'customer_can_pause'          => true,
            'require_reason'              => false,
        ];

        // Email defaults for skip/pause
        if (!isset($defaults['emails'])) {
            $defaults['emails'] = [];
        }
        $defaults['emails']['subscription_skipped'] = ['enabled' => true];
        $defaults['emails']['skip_undone'] = ['enabled' => true];
        $defaults['emails']['subscription_paused'] = ['enabled' => true];
        $defaults['emails']['subscription_resumed'] = ['enabled' => true];

        return $defaults;
    }

    /**
     * Handle auto-resume for paused subscriptions
     *
     * Called by Action Scheduler when pause duration expires.
     *
     * @param int $subscription_id Subscription post ID
     * @return void
     */
    public function handleAutoResume(int $subscription_id): void
    {
        // Acquire lock to prevent concurrent execution
        if (!ActionScheduler::acquireLock(ActionScheduler::HOOK_RESUME_SUBSCRIPTION, [$subscription_id])) {
            return;
        }

        try {
            $pause_manager = new PauseManager();
            $pause_manager->resume($subscription_id, 0, true); // 0 = system, true = auto
        } finally {
            ActionScheduler::releaseLock(ActionScheduler::HOOK_RESUME_SUBSCRIPTION, [$subscription_id]);
        }
    }

    /**
     * Handle processing a skipped renewal cycle
     *
     * Called when a subscription has skipped cycles and a renewal period passes.
     * Decrements the skip counter and advances the next payment date.
     *
     * @param int $subscription_id Subscription post ID
     * @return void
     */
    public function handleSkippedCycle(int $subscription_id): void
    {
        // Acquire lock to prevent concurrent execution
        if (!ActionScheduler::acquireLock(ActionScheduler::HOOK_PROCESS_SKIPPED_CYCLE, [$subscription_id])) {
            return;
        }

        try {
            $skip_manager = new SkipManager();
            $skip_manager->processSkippedCycle($subscription_id);
        } finally {
            ActionScheduler::releaseLock(ActionScheduler::HOOK_PROCESS_SKIPPED_CYCLE, [$subscription_id]);
        }
    }

    /**
     * Check if renewal invoice should be generated
     *
     * Prevents renewal invoice generation for subscriptions with skipped cycles.
     *
     * @param bool     $should_generate Whether to generate renewal invoice
     * @param \WP_Post $subscription    Subscription post object
     * @return bool Modified decision
     */
    public function checkSkipBeforeRenewal(bool $should_generate, \WP_Post $subscription): bool
    {
        if (!$should_generate) {
            return false;
        }

        $subscription_id = $subscription->ID;

        $skip_manager = new SkipManager();
        $skip_status = $skip_manager->getSkipStatus($subscription_id);

        if ($skip_status['is_skipping'] && $skip_status['cycles_remaining'] > 0) {
            if ($this->shouldCompleteSkipAndGenerateNow($subscription_id, $skip_status, $skip_manager)) {
                $skip_manager->completeSkippedCycles($subscription_id);
                return true;
            }

            // Schedule the skip cycle processing instead
            $this->scheduleSkipCycleProcessing($subscription_id);
            return false;
        }

        return true;
    }

    /**
     * Schedule skip cycle processing
     *
     * @param int $subscription_id Subscription post ID
     * @return void
     */
    private function scheduleSkipCycleProcessing(int $subscription_id): void
    {
        ActionScheduler::scheduleSingle(
            ActionScheduler::HOOK_PROCESS_SKIPPED_CYCLE,
            time() + 60, // Process in 1 minute
            [$subscription_id],
            ActionScheduler::GROUP_BILLING
        );
    }

    /**
     * Determine whether the skipped renewal has reached the shifted billing date.
     *
     * @param int         $subscription_id Subscription post ID.
     * @param array       $skip_status     Current skip status.
     * @param SkipManager $skip_manager    Skip manager instance.
     * @return bool
     */
    private function shouldCompleteSkipAndGenerateNow(
        int $subscription_id,
        array $skip_status,
        SkipManager $skip_manager
    ): bool {
        $current_next_payment = (string) get_post_meta($subscription_id, '_next_payment_date', true);
        $target_date          = $skip_manager->getSkippedRenewalTargetDate($subscription_id);

        if ($this->isSameRenewalDate($current_next_payment, $target_date)) {
            return true;
        }

        $cycles_total     = (int) ($skip_status['cycles_total'] ?? 0);
        $cycles_remaining = (int) ($skip_status['cycles_remaining'] ?? 0);

        return $cycles_remaining === 1 && $cycles_total > $cycles_remaining;
    }

    /**
     * Compare renewal dates by timestamp.
     *
     * @param string $first  First UTC MySQL date.
     * @param string $second Second UTC MySQL date.
     * @return bool
     */
    private function isSameRenewalDate(string $first, string $second): bool
    {
        if ($first === '' || $second === '') {
            return false;
        }

        $first_timestamp  = strtotime($first);
        $second_timestamp = strtotime($second);

        if (!$first_timestamp || !$second_timestamp) {
            return false;
        }

        return abs($first_timestamp - $second_timestamp) <= 5;
    }

    /**
     * Add a note when cycles are skipped
     *
     * Action signature: do_action('arraysubs_cycles_skipped', $subscription_id, $cycles, $user_id, $context)
     *
     * @param int   $subscription_id Subscription post ID
     * @param int   $cycles          Number of cycles skipped
     * @param int   $user_id         User who initiated the skip
     * @param array $context         Additional context (original_date, new_date)
     * @return void
     */
    public function addSkipNote(int $subscription_id, int $cycles, int $user_id, array $context): void
    {
        if (!function_exists('arraysubs_add_subscription_note')) {
            return;
        }

        $user_display = $this->getUserDisplay($user_id);
        $new_date = $context['new_date'] ?? get_post_meta($subscription_id, '_next_payment_date', true);
        $formatted_date = arraysubs_format_date_local($new_date, get_option('date_format'));

        $note = sprintf(
            /* translators: 1: Number of cycles, 2: User display name, 3: New payment date */
            _n(
                '%1$d renewal cycle skipped by %2$s. Next payment date: %3$s.',
                '%1$d renewal cycles skipped by %2$s. Next payment date: %3$s.',
                $cycles,
                'arraysubs'
            ),
            $cycles,
            $user_display,
            $formatted_date
        );

        arraysubs_add_subscription_note($subscription_id, $note, 'private', 0, [], 'subscription');
    }

    /**
     * Add a note when skip is undone
     *
     * Action signature: do_action('arraysubs_skip_undone', $subscription_id, $user_id, $context)
     *
     * @param int   $subscription_id Subscription post ID
     * @param int   $user_id         User who undid the skip
     * @param array $context         Additional context (cycles_undone, restored_date)
     * @return void
     */
    public function addUndoSkipNote(int $subscription_id, int $user_id, array $context): void
    {
        if (!function_exists('arraysubs_add_subscription_note')) {
            return;
        }

        $user_display = $this->getUserDisplay($user_id);
        $restored_date = $context['restored_date'] ?? get_post_meta($subscription_id, '_next_payment_date', true);
        $formatted_date = arraysubs_format_date_local($restored_date, get_option('date_format'));

        $note = sprintf(
            /* translators: 1: User display name, 2: Restored payment date */
            __('Skip cancelled by %1$s. Payment date restored: %2$s.', 'arraysubs'),
            $user_display,
            $formatted_date
        );

        arraysubs_add_subscription_note($subscription_id, $note, 'private', 0, [], 'subscription');
    }

    /**
     * Add a note when skip is modified
     *
     * @param int $subscription_id Subscription post ID
     * @param int $old_cycles      Previous cycle count
     * @param int $new_cycles      New cycle count
     * @param int $user_id         User who modified the skip
     * @return void
     */
    public function addModifySkipNote(int $subscription_id, int $old_cycles, int $new_cycles, int $user_id): void
    {
        if (!function_exists('arraysubs_add_subscription_note')) {
            return;
        }

        $user_display = $this->getUserDisplay($user_id);
        $new_date = get_post_meta($subscription_id, '_next_payment_date', true);
        $formatted_date = arraysubs_format_date_local($new_date, get_option('date_format'));

        $note = sprintf(
            /* translators: 1: User display name, 2: Old cycle count, 3: New cycle count, 4: New payment date */
            __('Skip modified by %1$s from %2$d to %3$d cycles. New payment date: %4$s.', 'arraysubs'),
            $user_display,
            $old_cycles,
            $new_cycles,
            $formatted_date
        );

        arraysubs_add_subscription_note($subscription_id, $note, 'private', 0, [], 'subscription');
    }

    /**
     * Add a note when subscription is paused
     *
     * Action signature: do_action('arraysubs_subscription_paused', $subscription_id, $duration_days, $user_id, $reason)
     *
     * @param int    $subscription_id Subscription post ID
     * @param int    $duration_days   Number of days to pause
     * @param int    $user_id         User who paused
     * @param string $reason          Pause reason (optional)
     * @return void
     */
    public function addPauseNote(int $subscription_id, int $duration_days, int $user_id, string $reason): void
    {
        if (!function_exists('arraysubs_add_subscription_note')) {
            return;
        }

        $user_display = $this->getUserDisplay($user_id);

        if ($duration_days > 0) {
            $resume_date = strtotime("+{$duration_days} days");
            $formatted_date = arraysubs_format_timestamp_local($resume_date, get_option('date_format'));
            $note = sprintf(
                /* translators: 1: User display name, 2: Number of days, 3: Resume date */
                _n(
                    'Subscription paused by %1$s for %2$d day (until %3$s).',
                    'Subscription paused by %1$s for %2$d days (until %3$s).',
                    $duration_days,
                    'arraysubs'
                ),
                $user_display,
                $duration_days,
                $formatted_date
            );
        } else {
            $note = sprintf(
                /* translators: 1: User display name */
                __('Subscription paused indefinitely by %1$s.', 'arraysubs'),
                $user_display
            );
        }

        if (!empty($reason)) {
            $note .= ' ' . sprintf(
                /* translators: %s: reason */
                __('Reason: %s', 'arraysubs'),
                esc_html($reason)
            );
        }

        arraysubs_add_subscription_note($subscription_id, $note, 'private', 0, [], 'subscription');
    }

    /**
     * Add a note when subscription is resumed
     *
     * Action signature: do_action('arraysubs_subscription_resumed', $subscription_id, $user_id, $is_auto, $context)
     *
     * @param int   $subscription_id Subscription post ID
     * @param int   $user_id         User who resumed
     * @param bool  $is_auto         Whether this is an auto-resume
     * @param array $context         Additional context (paused_days, restored_to)
     * @return void
     */
    public function addResumeNote(int $subscription_id, int $user_id, bool $is_auto, array $context): void
    {
        if (!function_exists('arraysubs_add_subscription_note')) {
            return;
        }

        $user_display = $this->getUserDisplay($user_id, $is_auto ? 'auto' : null);
        $paused_days = $context['paused_days'] ?? 0;

        $note = sprintf(
            /* translators: 1: User display name, 2: Number of days paused */
            _n(
                'Subscription resumed by %1$s after %2$d day pause.',
                'Subscription resumed by %1$s after %2$d days pause.',
                $paused_days,
                'arraysubs'
            ),
            $user_display,
            $paused_days
        );

        arraysubs_add_subscription_note($subscription_id, $note, 'private', 0, [], 'subscription');
    }

    /**
     * Get user display name for notes
     *
     * @param int         $user_id User ID (0 for system)
     * @param string|null $type    Optional: 'auto' for automatic actions
     * @return string
     */
    private function getUserDisplay(int $user_id, ?string $type = null): string
    {
        if ($type === 'auto') {
            return __('system (auto)', 'arraysubs');
        }

        if ($user_id === 0) {
            return __('system', 'arraysubs');
        }

        $user = get_userdata($user_id);
        if ($user) {
            return $user->display_name;
        }

        return sprintf(
            /* translators: %d: user ID */
            __('User #%d', 'arraysubs'),
            $user_id
        );
    }

    /**
     * Send email when cycles are skipped
     *
     * @param int   $subscription_id Subscription post ID
     * @param int   $cycles          Number of cycles skipped
     * @param int   $user_id         User who initiated the skip
     * @param array $context         Additional context (original_date, new_date)
     * @return void
     */
    public function sendSkipEmail(int $subscription_id, int $cycles, int $user_id, array $context): void
    {
        // Check if email is enabled
        if (!arraysubs_get_setting('emails.subscription_skipped.enabled', true)) {
            return;
        }

        if (!function_exists('arraysubs_send_subscription_email')) {
            return;
        }

        $subscription = arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof \WP_Post) {
            return;
        }

        // Build template variables
        $variables = [
            'skipped_cycles' => $cycles,
            'next_payment_date' => arraysubs_format_date_local(
                $context['new_date'] ?? get_post_meta($subscription_id, '_next_payment_date', true)
            ),
        ];

        arraysubs_send_subscription_email(
            'subscription_skipped',
            $subscription_id,
            $variables
        );
    }

    /**
     * Send email when skip is undone
     *
     * @param int   $subscription_id Subscription post ID
     * @param int   $user_id         User who undid the skip
     * @param array $context         Additional context (cycles_undone, restored_date)
     * @return void
     */
    public function sendSkipUndoneEmail(int $subscription_id, int $user_id, array $context): void
    {
        // Check if email is enabled
        if (!arraysubs_get_setting('emails.skip_undone.enabled', true)) {
            return;
        }

        if (!function_exists('arraysubs_send_subscription_email')) {
            return;
        }

        $subscription = arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof \WP_Post) {
            return;
        }

        // Build template variables
        $variables = [
            'next_payment_date' => arraysubs_format_date_local(
                $context['restored_date'] ?? get_post_meta($subscription_id, '_next_payment_date', true)
            ),
        ];

        arraysubs_send_subscription_email(
            'skip_undone',
            $subscription_id,
            $variables
        );
    }

    /**
     * Send email when subscription is paused
     *
     * @param int    $subscription_id Subscription post ID
     * @param int    $duration_days   Number of days to pause
     * @param int    $user_id         User who paused
     * @param string $reason          Pause reason (optional)
     * @return void
     */
    public function sendPauseEmail(int $subscription_id, int $duration_days, int $user_id, string $reason): void
    {
        // Check if email is enabled
        if (!arraysubs_get_setting('emails.subscription_paused.enabled', true)) {
            return;
        }

        if (!function_exists('arraysubs_send_subscription_email')) {
            return;
        }

        $subscription = arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof \WP_Post) {
            return;
        }

        // Build template variables
        $pause_date = get_post_meta($subscription_id, '_paused_at', true);
        $resume_date = $duration_days > 0
            ? strtotime("+{$duration_days} days", strtotime($pause_date))
            : null;

        $variables = [
            'pause_date' => arraysubs_format_date_local($pause_date),
            'resume_date' => $resume_date
                ? arraysubs_format_timestamp_local($resume_date)
                : __('Indefinite', 'arraysubs'),
            'pause_duration' => $duration_days > 0
                ? /* translators: %d: number of days subscription is paused */
                sprintf(_n('%d day', '%d days', $duration_days, 'arraysubs'), $duration_days)
                : __('Indefinite', 'arraysubs'),
            'pause_reason' => $reason ?: __('Not specified', 'arraysubs'),
        ];

        arraysubs_send_subscription_email(
            'subscription_paused',
            $subscription_id,
            $variables
        );
    }

    /**
     * Send email when subscription is resumed
     *
     * @param int   $subscription_id Subscription post ID
     * @param int   $user_id         User who resumed
     * @param bool  $is_auto         Whether this is an auto-resume
     * @param array $context         Additional context (paused_days, restored_to)
     * @return void
     */
    public function sendResumeEmail(int $subscription_id, int $user_id, bool $is_auto, array $context): void
    {
        // Check if email is enabled
        if (!arraysubs_get_setting('emails.subscription_resumed.enabled', true)) {
            return;
        }

        if (!function_exists('arraysubs_send_subscription_email')) {
            return;
        }

        $subscription = arraysubs_get_subscription($subscription_id);
        if (!$subscription instanceof \WP_Post) {
            return;
        }

        // Build template variables
        $next_payment_date = get_post_meta($subscription_id, '_next_payment_date', true);

        $variables = [
            'next_payment_date' => arraysubs_format_date_local($next_payment_date),
        ];

        arraysubs_send_subscription_email(
            'subscription_resumed',
            $subscription_id,
            $variables
        );
    }
}
