<?php
/**
 * Task scheduler controller for managing scheduled tasks.
 *
 * Dispatches registered tasks grouped by recurrence (daily/hourly)
 * instead of scheduling each task individually.
 *
 * @package Awesomemotive\Rewardswp\Controllers
 * @since 1.0.0
 */

namespace Awesomemotive\Rewardswp\Controllers;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Awesomemotive\Rewardswp\Interfaces\ControllerInterface;
use Awesomemotive\Rewardswp\Interfaces\ScheduledTaskInterface;
use Awesomemotive\Rewardswp\Services\TaskSchedulerService;
use function as_schedule_recurring_action;
use function as_schedule_single_action;
use function as_unschedule_all_actions;
use function as_next_scheduled_action;
use function Awesomemotive\Rewardswp\Helpers\rewardswp_log_error;

/**
 * Task Scheduler Controller.
 *
 * Registers two grouped Action Scheduler hooks (daily + hourly) and dispatches
 * all registered ScheduledTaskInterface implementations within each group.
 * New tasks are added by creating a class and registering it in the
 * 'scheduled_task_classes' DI array.
 *
 * @since 1.0.0
 */
class TaskSchedulerController implements ControllerInterface {
	/**
	 * Task scheduler service instance (for on-demand tasks).
	 *
	 * @since 1.0.0
	 *
	 * @var TaskSchedulerService
	 */
	private TaskSchedulerService $task_scheduler_service;

	/**
	 * Registered scheduled task instances.
	 *
	 * @since 1.4.0
	 *
	 * @var ScheduledTaskInterface[]
	 */
	private array $tasks;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param TaskSchedulerService     $task_scheduler_service Task scheduler service instance.
	 * @param ScheduledTaskInterface[] $tasks                  Registered scheduled tasks.
	 */
	public function __construct( TaskSchedulerService $task_scheduler_service, array $tasks ) {
		$this->task_scheduler_service = $task_scheduler_service;
		$this->tasks                  = $tasks;
	}

	/**
	 * Initiate task scheduler actions.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function init() {
		// Bail early if ActionScheduler is not available.
		if ( ! class_exists( 'ActionScheduler' ) ) {
			return;
		}

		// Register pagination follow-up hooks for all tasks.
		foreach ( $this->tasks as $task ) {
			$task->register_hooks();
		}

		// Register grouped action hooks.
		add_action( 'rewardswp_daily_tasks', [ $this, 'run_daily_tasks' ] );
		add_action( 'rewardswp_hourly_tasks', [ $this, 'run_hourly_tasks' ] );

		// On-demand tasks (not recurring).
		add_action( 'rewardswp_task_send_email', [ $this, 'task_send_email' ], 10, 3 );
		add_action( 'rewardswp_task_auto_enroll_customers', [ $this, 'task_auto_enroll_customers' ], 10, 1 );

		// Schedule recurring actions on init.
		add_action( 'init', [ $this, 'schedule_recurring_actions' ], 20 );
	}

	/**
	 * Schedule recurring actions.
	 *
	 * @since 1.0.6
	 *
	 * @return void
	 */
	public function schedule_recurring_actions(): void {
		if ( ! function_exists( 'as_schedule_recurring_action' ) ) {
			return; // Bail if Action Scheduler is not available.
		}

		// Define constants if not available.
		if ( ! defined( 'HOUR_IN_SECONDS' ) ) {
			define( 'HOUR_IN_SECONDS', 3600 );
		}
		if ( ! defined( 'DAY_IN_SECONDS' ) ) {
			define( 'DAY_IN_SECONDS', 86400 );
		}

		// Schedule daily tasks at 7 AM in WordPress timezone.
		if ( ! as_next_scheduled_action( 'rewardswp_daily_tasks' ) ) {
			$tomorrow_7am = new \DateTime( 'tomorrow 07:00:00', wp_timezone() );
			as_schedule_recurring_action( $tomorrow_7am->getTimestamp(), DAY_IN_SECONDS, 'rewardswp_daily_tasks' );
		}

		// Schedule hourly tasks (only if there are hourly tasks registered).
		$has_hourly = false;
		foreach ( $this->tasks as $task ) {
			if ( 'hourly' === $task->get_recurrence() ) {
				$has_hourly = true;
				break;
			}
		}

		if ( $has_hourly && ! as_next_scheduled_action( 'rewardswp_hourly_tasks' ) ) {
			as_schedule_recurring_action( time() + HOUR_IN_SECONDS, HOUR_IN_SECONDS, 'rewardswp_hourly_tasks' );
		}

		// Schedule auto enrollment if not already complete (only if not already scheduled).
		if ( false === get_option( 'rewardswp_initial_conversion_complete', false ) ) {
			if ( ! as_next_scheduled_action( 'rewardswp_task_auto_enroll_customers' ) ) {
				as_schedule_single_action( time() + 10, 'rewardswp_task_auto_enroll_customers' );
			}
		}
	}

	/**
	 * Run all daily tasks sequentially.
	 *
	 * Each task is wrapped in try/catch so one failure does not block the rest.
	 *
	 * @since 1.4.0
	 *
	 * @return void
	 */
	public function run_daily_tasks(): void {
		foreach ( $this->tasks as $task ) {
			if ( 'daily' !== $task->get_recurrence() ) {
				continue;
			}

			try {
				$task->run();
			} catch ( \Throwable $e ) {
				rewardswp_log_error(
					'Daily task {task} failed: {message}',
					[
						'task'    => get_class( $task ),
						'message' => $e->getMessage(),
					]
				);
			}
		}
	}

	/**
	 * Run all hourly tasks sequentially.
	 *
	 * Each task is wrapped in try/catch so one failure does not block the rest.
	 *
	 * @since 1.4.0
	 *
	 * @return void
	 */
	public function run_hourly_tasks(): void {
		foreach ( $this->tasks as $task ) {
			if ( 'hourly' !== $task->get_recurrence() ) {
				continue;
			}

			try {
				$task->run();
			} catch ( \Throwable $e ) {
				rewardswp_log_error(
					'Hourly task {task} failed: {message}',
					[
						'task'    => get_class( $task ),
						'message' => $e->getMessage(),
					]
				);
			}
		}
	}

	/**
	 * Unschedule all recurring actions.
	 *
	 * @since 1.0.6
	 *
	 * @return void
	 */
	public function unschedule_recurring_actions(): void {
		if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
			return; // Bail if Action Scheduler is not available.
		}

		as_unschedule_all_actions( 'rewardswp_daily_tasks' );
		as_unschedule_all_actions( 'rewardswp_hourly_tasks' );
		as_unschedule_all_actions( 'rewardswp_task_auto_enroll_customers' );
		// Note: rewardswp_task_send_email is not automatically scheduled, so no need to unschedule it.
	}

	/**
	 * Handle the task to send emails asynchronously.
	 *
	 * @since 1.0.6
	 *
	 * @param string       $mailer_name Mailer name to use for sending.
	 * @param string|array $recipients   Email recipient(s).
	 * @param array        $data         Email data to send.
	 *
	 * @return void
	 *
	 * @throws \Exception If sending email fails.
	 */
	public function task_send_email( string $mailer_name, $recipients, array $data ): void {
		$this->task_scheduler_service->task_send_email( $mailer_name, $recipients, $data );
	}

	/**
	 * Handle the task to auto enroll customers.
	 *
	 * @since 1.0.6
	 *
	 * @param int $page Current page number for pagination.
	 *
	 * @return void
	 *
	 * @throws \Exception If auto enrollment fails.
	 */
	public function task_auto_enroll_customers( int $page = 1 ): void {
		$this->task_scheduler_service->task_auto_enroll_customers( $page );
	}
}
