<?php
/**
 * ============================================
 * FILE: includes/Services/ReminderService.php
 * Appointment Reminder Service
 * ============================================
 */

namespace RadiusTheme\RadiusBooking\Services;

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

use RadiusTheme\RadiusBooking\Emails\EmailManager;
use RadiusTheme\RadiusBooking\Models\Appointment;
use RadiusTheme\RadiusBooking\Resources\AppointmentResource;

/**
 * Class ReminderService
 *
 * Handles scheduling and cancelling appointment reminder emails
 * via Action Scheduler.
 *
 * Free: Single reminder time per email template.
 * Pro: Multiple reminder times via `rtrb_reminder_times` filter.
 *
 * @package RadiusTheme\RadiusBooking\Services
 */
class ReminderService {

	/**
	 * Action Scheduler hook name for reminder emails.
	 *
	 * @var string
	 */
	const REMINDER_HOOK = 'rtrb_process_appointment_reminder';

	/**
	 * Action Scheduler group for reminders.
	 *
	 * @var string
	 */
	const REMINDER_GROUP = 'booking-emails-reminders';

	/**
	 * Register WordPress hooks for scheduling reminders.
	 */
	public function __construct() {
		$this->register_hooks();
	}

	/**
	 * Register action hooks for appointment lifecycle events.
	 *
	 * Service bookings, meetings and event registrations fire different status
	 * hooks with different argument shapes. Each hook routes through an
	 * adapter that extracts the appointment_id and delegates to
	 * schedule_reminders / cancel_reminders.
	 */
	private function register_hooks() {
		// --- Service appointment hooks. Signature: ($appointment_id). ---
		add_action( 'radius_booking_appointment_status_confirmed', array( $this, 'schedule_reminders' ), 20, 1 );
		add_action( 'radius_booking_appointment_status_pending', array( $this, 'schedule_reminders' ), 20, 1 );
		add_action( 'radius_booking_appointment_status_cancelled', array( $this, 'cancel_reminders' ), 20, 1 );
		add_action( 'radius_booking_appointment_status_completed', array( $this, 'cancel_reminders' ), 20, 1 );

		// --- Meeting booking hooks. Signature: ($appointment, $meeting_type). ---
		add_action( 'radius_booking_meeting_status_confirmed', array( $this, 'handle_meeting_status_schedule' ), 20, 2 );
		add_action( 'radius_booking_meeting_status_pending', array( $this, 'handle_meeting_status_schedule' ), 20, 2 );
		add_action( 'radius_booking_meeting_status_cancelled', array( $this, 'handle_meeting_status_cancel' ), 20, 2 );
		add_action( 'radius_booking_meeting_status_completed', array( $this, 'handle_meeting_status_cancel' ), 20, 2 );

		// --- Event registration hooks. Signature: ($registration, $event). ---
		add_action( 'radius_booking_event_registration_status_confirmed', array( $this, 'handle_event_status_schedule' ), 20, 2 );
		add_action( 'radius_booking_event_registration_status_pending', array( $this, 'handle_event_status_schedule' ), 20, 2 );
		// Cancellation fires its own hook with signature ($registration, $previous_status).
		add_action( 'radius_booking_event_registration_cancelled', array( $this, 'handle_event_status_cancel' ), 20, 2 );

		// Process reminder when Action Scheduler fires.
		add_action( self::REMINDER_HOOK, array( $this, 'process_reminder' ), 10, 2 );
	}

	/**
	 * Adapter: extract appointment_id from a meeting status hook and schedule.
	 *
	 * @param object|int $appointment  The Appointment model (or ID).
	 * @param mixed      $meeting_type Unused; passed by the meeting status hook.
	 */
	public function handle_meeting_status_schedule( $appointment, $meeting_type = null ) {
		// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
//		error_log( '[RTRB Reminder Debug] handle_meeting_status_schedule fired. appointment type: ' . ( is_object( $appointment ) ? get_class( $appointment ) : gettype( $appointment ) ) );
		$appointment_id = $this->extract_appointment_id_from_object( $appointment );
//		error_log( '[RTRB Reminder Debug] extracted meeting appointment_id: ' . var_export( $appointment_id, true ) );
		if ( $appointment_id ) {
			$this->schedule_reminders( $appointment_id );
		}
	}

	/**
	 * Adapter: extract appointment_id from a meeting status hook and cancel.
	 *
	 * @param object|int $appointment  The Appointment model (or ID).
	 * @param mixed      $meeting_type Unused; passed by the meeting status hook.
	 */
	public function handle_meeting_status_cancel( $appointment, $meeting_type = null ) {
		$appointment_id = $this->extract_appointment_id_from_object( $appointment );
		if ( $appointment_id ) {
			$this->cancel_reminders( $appointment_id );
		}
	}

	/**
	 * Adapter: extract appointment_id from an event registration and schedule.
	 *
	 * @param object|int $registration The EventRegistration model (or appointment ID).
	 * @param mixed      $event        Unused; passed by the event status hook.
	 */
	public function handle_event_status_schedule( $registration, $event = null ) {
		// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
//		error_log( '[RTRB Reminder Debug] handle_event_status_schedule fired. registration type: ' . ( is_object( $registration ) ? get_class( $registration ) : gettype( $registration ) ) );
		$appointment_id = $this->extract_appointment_id_from_registration( $registration );
//		error_log( '[RTRB Reminder Debug] extracted event appointment_id: ' . var_export( $appointment_id, true ) );
		if ( $appointment_id ) {
			$this->schedule_reminders( $appointment_id );
		}
	}

	/**
	 * Adapter: extract appointment_id from an event registration and cancel.
	 *
	 * Note: when multiple registrations share one appointment, this cancels
	 * reminders for all of them. The reminder process_reminder() callback
	 * already re-checks appointment status at fire time as a safety net.
	 *
	 * @param object|int $registration    The EventRegistration model (or appointment ID).
	 * @param mixed      $previous_status Unused; passed by the cancellation hook.
	 */
	public function handle_event_status_cancel( $registration, $previous_status = null ) {
		$appointment_id = $this->extract_appointment_id_from_registration( $registration );
		if ( $appointment_id ) {
			$this->cancel_reminders( $appointment_id );
		}
	}

	/**
	 * Pull an appointment_id out of an Appointment model or scalar.
	 *
	 * @param mixed $appointment The Appointment model (or ID).
	 * @return int
	 */
	private function extract_appointment_id_from_object( $appointment ): int {
		if ( is_object( $appointment ) ) {
			return (int) ( $appointment->id ?? 0 );
		}
		return (int) $appointment;
	}

	/**
	 * Pull an appointment_id out of an EventRegistration model or scalar.
	 *
	 * @param mixed $registration The EventRegistration model (or appointment ID).
	 * @return int
	 */
	private function extract_appointment_id_from_registration( $registration ): int {
		if ( is_object( $registration ) ) {
			return (int) ( $registration->appointment_id ?? 0 );
		}
		return (int) $registration;
	}

	/**
	 * Schedule reminder emails for both customer and employee.
	 *
	 * @param int $appointment_id The appointment ID.
	 */
	public function schedule_reminders( $appointment_id ) {
		// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
//		error_log( '[RTRB Reminder Debug] schedule_reminders called with appointment_id=' . var_export( $appointment_id, true ) );

		if ( ! $appointment_id || ! function_exists( 'as_schedule_single_action' ) ) {
//			error_log( '[RTRB Reminder Debug] schedule_reminders bail: missing appointment_id or as_schedule_single_action' );
			return;
		}

		$appointment = Appointment::find( $appointment_id );
		if ( ! $appointment ) {
//			error_log( '[RTRB Reminder Debug] schedule_reminders bail: Appointment::find returned null' );
			return;
		}

		$booking_data = ( new AppointmentResource() )->transform( $appointment );
		if ( ! $booking_data ) {
//			error_log( '[RTRB Reminder Debug] schedule_reminders bail: transform returned empty' );
			return;
		}

		$appointment_date = $booking_data['appointment_date'];
		$start_time       = $booking_data['start_time'];
		$booking_datetime = $appointment_date . ' ' . $start_time;

		// Cancel any existing reminders first to avoid duplicates.
		$this->cancel_existing_reminders( $appointment_id );

		// Schedule the right reminder email IDs for this booking type.
		$booking_type = $booking_data['booking_type'] ?? 'service_booking';
		$email_ids    = $this->get_reminder_email_ids( $booking_type );

		// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
//		error_log( '[RTRB Reminder Debug] booking_type=' . $booking_type . ' booking_datetime=' . $booking_datetime . ' email_ids=' . wp_json_encode( $email_ids ) );

		foreach ( $email_ids as $email_id ) {
			$this->schedule_email_reminders( $email_id, $appointment_id, $booking_datetime );
		}
	}

	/**
	 * Map a booking type to its (customer, employee) reminder email IDs.
	 *
	 * Stored booking_type values: 'service_booking' (DB default for service
	 * rows), 'meeting' (set by MeetingBookingService), 'event' (set by
	 * EventRegistrationService).
	 *
	 * @param string $booking_type The booking type from the appointment row.
	 * @return string[] List of reminder email IDs to schedule.
	 */
	private function get_reminder_email_ids( $booking_type ): array {
		switch ( $booking_type ) {
			case 'meeting':
				return array( 'customer_meeting_reminder', 'employee_meeting_reminder' );
			case 'event':
				return array( 'customer_event_reminder', 'employee_event_reminder' );
			default:
				return array( 'customer_appointment_reminder', 'employee_appointment_reminder' );
		}
	}

	/**
	 * Schedule reminders for a specific email template.
	 *
	 * Free: Schedules a single reminder based on `reminder_minutes`.
	 * Pro: Can schedule multiple reminders by filtering `rtrb_reminder_times`.
	 *
	 * @param string $email_id         The email template ID.
	 * @param int    $appointment_id   The appointment ID.
	 * @param string $booking_datetime The appointment datetime string (Y-m-d H:i:s).
	 */
	private function schedule_email_reminders( $email_id, $appointment_id, $booking_datetime ) {
		$email_manager = new EmailManager();
		$email         = $email_manager->get_email( $email_id );

		// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
		if ( ! $email ) {
//			error_log( '[RTRB Reminder Debug] schedule_email_reminders bail: email class not found for id=' . $email_id );
			return;
		}
		if ( ! $email->is_enabled() ) {
//			error_log( '[RTRB Reminder Debug] schedule_email_reminders bail: email NOT enabled for id=' . $email_id );
			return;
		}

		$settings      = $email->get_settings();
		$send_reminder = $settings['send_reminder'] ?? true;

		// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
//		error_log( '[RTRB Reminder Debug] schedule_email_reminders id=' . $email_id . ' send_reminder=' . var_export( $send_reminder, true ) . ' reminder_minutes=' . var_export( $settings['reminder_minutes'] ?? 'unset', true ) );

		if ( ! $send_reminder ) {
//			error_log( '[RTRB Reminder Debug] schedule_email_reminders bail: send_reminder is false for id=' . $email_id );
			return;
		}

		// Free: single reminder time in minutes.
		$reminder_minutes = $settings['reminder_minutes'] ?? 30; // Default 24 hours.
		$reminder_times   = array( (int) $reminder_minutes );

		/**
		 * Filter the reminder times for an email template.
		 *
		 * Pro plugin can override this to provide multiple reminder times.
		 *
		 * @param array  $reminder_times   Array of minutes before appointment to send reminders.
		 * @param string $email_id         The email template ID.
		 * @param array  $settings         The email settings.
		 * @param int    $appointment_id   The appointment ID.
		 */
		$reminder_times = apply_filters( 'rtrb_reminder_times', $reminder_times, $email_id, $settings, $appointment_id );

		// Convert booking datetime from WordPress local timezone to UTC timestamp.
		// Appointment dates are stored in local timezone, but Action Scheduler expects UTC.
		try {
			$local_dt          = new \DateTime( $booking_datetime, wp_timezone() );
			$booking_timestamp = $local_dt->getTimestamp();
		} catch ( \Exception $e ) {
			return;
		}

		foreach ( $reminder_times as $minutes_before ) {
			$minutes_before     = absint( $minutes_before );
			$reminder_timestamp = $booking_timestamp - ( $minutes_before * 60 );

			// Don't schedule if the reminder time has already passed.
			if ( $reminder_timestamp <= time() ) {
				// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
//				error_log( '[RTRB Reminder Debug] skipping schedule for id=' . $email_id . ': reminder_timestamp ' . $reminder_timestamp . ' <= now ' . time() . ' (minutes_before=' . $minutes_before . ')' );
				continue;
			}

			$action_id = as_schedule_single_action(
				$reminder_timestamp,
				self::REMINDER_HOOK,
				array(
					'email_id'       => $email_id,
					'appointment_id' => $appointment_id,
				),
				self::REMINDER_GROUP
			);
			// TEMP DEBUG — remove after diagnosing meeting/event reminder scheduling.
//			error_log( '[RTRB Reminder Debug] as_schedule_single_action OK id=' . $email_id . ' appointment_id=' . $appointment_id . ' action_id=' . var_export( $action_id, true ) . ' fires_at=' . gmdate( 'Y-m-d H:i:s', $reminder_timestamp ) . ' UTC' );
		}
	}

	/**
	 * Process a reminder when Action Scheduler fires.
	 *
	 * Called by Action Scheduler at the scheduled time. Triggers the reminder
	 * email class which handles data preparation and sending internally.
	 *
	 * @param string $email_id       The email template ID.
	 * @param int    $appointment_id The appointment ID.
	 */
	public function process_reminder( $email_id, $appointment_id ) {
		$email_manager = new EmailManager();
		$email         = $email_manager->get_email( $email_id );

		if ( ! $email || ! $email->is_enabled() ) {
			return;
		}

		// Check that the appointment still exists and is not cancelled.
		$appointment = Appointment::find( $appointment_id );
		if ( ! $appointment ) {
			return;
		}

		$status = $appointment->get_status();
		if ( 'cancelled' === $status || 'completed' === $status ) {
			return;
		}

		// Use trigger() which calls prepare_email_data() and send() internally.
		$email->trigger( $appointment_id );
	}

	/**
	 * Cancel all scheduled reminders for an appointment.
	 *
	 * @param int $appointment_id The appointment ID.
	 */
	public function cancel_reminders( $appointment_id ) {
		if ( ! $appointment_id ) {
			return;
		}

		$this->cancel_existing_reminders( $appointment_id );
	}

	/**
	 * Cancel existing scheduled reminders for an appointment.
	 *
	 * @param int $appointment_id The appointment ID.
	 */
	private function cancel_existing_reminders( $appointment_id ) {
		if ( ! function_exists( 'as_unschedule_all_actions' ) ) {
			return;
		}

		// Cover all reminder email IDs (service / meeting / event). Action
		// Scheduler no-ops if no matching action exists, so passing extra IDs
		// is safe and avoids needing to know the booking type at cancel time.
		$email_ids = array(
			'customer_appointment_reminder',
			'employee_appointment_reminder',
			'customer_meeting_reminder',
			'employee_meeting_reminder',
			'customer_event_reminder',
			'employee_event_reminder',
		);

		foreach ( $email_ids as $email_id ) {
			as_unschedule_all_actions(
				self::REMINDER_HOOK,
				array(
					'email_id'       => $email_id,
					'appointment_id' => $appointment_id,
				),
				self::REMINDER_GROUP
			);
		}
	}
}
