<?php
/**
 * EventRegistrationService
 *
 * Orchestrates the event registration flow:
 * validates event, checks capacity, creates appointment,
 * delegates to BookingService for payment pipeline, creates registration,
 * and fires hooks for emails/integrations.
 *
 * @package RadiusTheme\RadiusBooking\Services
 * @since   1.2.0
 */

namespace RadiusTheme\RadiusBooking\Services;

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

use RadiusTheme\RadiusBooking\Models\Appointment;
use RadiusTheme\RadiusBooking\Models\EventRegistration;
use RadiusTheme\RadiusBooking\Models\EventTicketType;
use RadiusTheme\RadiusBooking\Repositories\EventRepository;
use RadiusTheme\RadiusBooking\Repositories\EventRegistrationRepository;
use RadiusTheme\RadiusBooking\Repositories\AppointmentRepository;

/**
 * Class EventRegistrationService
 */
class EventRegistrationService {

	/**
	 * @var EventRegistrationRepository
	 */
	private EventRegistrationRepository $registration_repo;

	/**
	 * @var EventCapacityService
	 */
	private EventCapacityService $capacity_service;

	/**
	 * @var BookingService
	 */
	private BookingService $booking_service;

	/**
	 * @var CustomerService
	 */
	private CustomerService $customer_service;

	/**
	 * @var AppointmentRepository
	 */
	private AppointmentRepository $appointment_repo;

	/**
	 * @var EventRepository
	 */
	private EventRepository $event_repo;

	/**
	 * EventRegistrationService constructor.
	 *
	 * @param EventRegistrationRepository $registration_repo Registration repository.
	 * @param EventCapacityService        $capacity_service  Capacity management service.
	 * @param BookingService              $booking_service   Existing booking pipeline.
	 * @param CustomerService             $customer_service  Customer resolution service.
	 * @param AppointmentRepository       $appointment_repo  Appointment repository.
	 * @param EventRepository             $event_repo        Event repository.
	 */
	public function __construct(
		EventRegistrationRepository $registration_repo,
		EventCapacityService $capacity_service,
		BookingService $booking_service,
		CustomerService $customer_service,
		AppointmentRepository $appointment_repo,
		EventRepository $event_repo
	) {
		$this->registration_repo = $registration_repo;
		$this->capacity_service  = $capacity_service;
		$this->booking_service   = $booking_service;
		$this->customer_service  = $customer_service;
		$this->appointment_repo  = $appointment_repo;
		$this->event_repo        = $event_repo;
	}

	/**
	 * Register for an event by slug.
	 *
	 * @param string $slug The event slug.
	 * @param array  $data Registration data with keys:
	 *                     - ticket_type_id (int)
	 *                     - quantity (int)
	 *                     - attendee_info (array)
	 *                     - bookings (array — customer info passed to BookingService)
	 *                     - payments (array — payment gateway info)
	 *
	 * @return array Registration result with registration_id, appointment_id, status.
	 *
	 * @throws \Exception If module inactive, event not found, deadline passed, or no capacity.
	 */
	public function registerForEvent( string $slug, array $data ): array {
		// 1. Check module is active.
		if ( ! rtrb_is_module_active( 'event' ) ) {
			throw new \Exception( esc_html__( 'Event module is not active.', 'radius-booking' ) );
		}

		// 2. Load event by slug and validate.
		$event = $this->event_repo->findBySlug( $slug );

		if ( ! $event ) {
			throw new \Exception( esc_html__( 'Event not found.', 'radius-booking' ) );
		}

		if ( 'published' !== $event->status ) {
			throw new \Exception( esc_html__( 'This event is not available for registration.', 'radius-booking' ) );
		}

		// Check registration deadline (deadline date is inclusive — expires at end of day).
		if ( ! empty( $event->registration_deadline ) ) {
			$now      = current_time( 'timestamp' ); // phpcs:ignore WordPress.DateTime.CurrentTimeTimestamp.Requested
			$deadline = strtotime( $event->registration_deadline . ' 23:59:59' );
			if ( $deadline && $now > $deadline ) {
				throw new \Exception( esc_html__( 'Registration deadline has passed for this event.', 'radius-booking' ) );
			}
		}

		$attendee_info = $data['attendee_info'] ?? array();

		// Store selected session IDs in attendee_info.
		if ( ! empty( $data['sessions'] ) && is_array( $data['sessions'] ) ) {
			$attendee_info['sessions'] = array_map( 'intval', $data['sessions'] );
		}

		// Normalize tickets: support both `tickets` array (multiple) and legacy `ticket_type_id` (single).
		$tickets = array();
		if ( ! empty( $data['tickets'] ) && is_array( $data['tickets'] ) ) {
			foreach ( $data['tickets'] as $ticket ) {
				$tid = (int) ( $ticket['ticket_type_id'] ?? 0 );
				$qty = (int) ( $ticket['quantity'] ?? 1 );
				if ( $tid > 0 && $qty > 0 ) {
					$tickets[] = array(
						'ticket_type_id' => $tid,
						'quantity'        => $qty,
					);
				}
			}
		}

		// Fallback to legacy single ticket fields.
		if ( empty( $tickets ) ) {
			$tid = (int) ( $data['ticket_type_id'] ?? 0 );
			$qty = (int) ( $data['quantity'] ?? 1 );
			if ( $tid > 0 ) {
				$tickets[] = array(
					'ticket_type_id' => $tid,
					'quantity'        => $qty,
				);
			}
		}

		if ( empty( $tickets ) ) {
			throw new \Exception( esc_html__( 'A ticket type is required.', 'radius-booking' ) );
		}

		// 3. Check availability for all ticket types before creating anything.
		$overall_status = 'confirmed';
		foreach ( $tickets as $ticket ) {
			$availability = $this->capacity_service->checkAvailability( $event->id, $ticket['ticket_type_id'], $ticket['quantity'] );
			if ( ! $availability['available'] ) {
				if ( $availability['waitlist_available'] ) {
					$overall_status = 'waitlisted';
				} else {
					throw new \Exception( esc_html__( 'This event is sold out and the waitlist is full.', 'radius-booking' ) );
				}
			}
		}

		// 4. Create Appointment record.
		$total_quantity = array_sum( array_column( $tickets, 'quantity' ) );
		$appointment    = Appointment::create(
			array(
				'employee_id'      => $event->organizer_id,
				'service_id'       => 0,
				'appointment_date' => $event->start_date,
				'start_time'       => $event->start_time ?? '00:00:00',
				'end_time'         => $event->end_time ?? '23:59:59',
				'status'           => 'waitlisted' === $overall_status ? 'pending' : 'confirmed',
				'booking_type'     => 'event',
				'event_id'         => $event->id,
				'notify_customer'  => true,
			)
		);

		if ( ! $appointment || ! $appointment->get_id() ) {
			throw new \Exception( esc_html__( 'Failed to create appointment for event registration.', 'radius-booking' ) );
		}

		$appointment_id = $appointment->get_id();

		// 5. Calculate total price from ticket types.
		$total_price = 0.0;
		foreach ( $tickets as $ticket_entry ) {
			$ticket_type = EventTicketType::find( $ticket_entry['ticket_type_id'] );
			if ( $ticket_type ) {
				$total_price += (float) $ticket_type->price * $ticket_entry['quantity'];
			}
		}

		// 6. Delegate to BookingService for customer resolution and payment pipeline.
		$booking_data                     = array();
		$booking_data['appointment_date'] = $event->start_date;
		$booking_data['start_time']       = $event->start_time ?? '00:00:00';
		$booking_data['end_time']         = $event->end_time ?? '23:59:59';
		$booking_data['employee_id']      = $event->organizer_id;
		$booking_data['service_id']       = 0;
		$booking_data['booking_type']     = 'event';
		$booking_data['notes']            = sanitize_text_field( $data['notes'] ?? '' );

		// Read custom-field answers from either `bookings[0].custom_info`
		// (the shape the public event form sends today) or top-level
		// `custom_info` (forward-compat for flat-payload clients).
		$raw_custom_info = ( $data['bookings'][0]['custom_info'] ?? null )
			?? ( $data['custom_info'] ?? array() );
		$custom_info     = $this->sanitizeCustomInfo( $raw_custom_info );

		// Wrap customer info into the bookings array format expected by BookingService.
		$booking_data['bookings'] = array(
			array(
				'customer_info' => array(
					'first_name' => sanitize_text_field( $data['first_name'] ?? '' ),
					'last_name'  => sanitize_text_field( $data['last_name'] ?? '' ),
					'email'      => sanitize_email( $data['email'] ?? '' ),
					'phone'      => sanitize_text_field( $data['phone'] ?? '' ),
					'name'       => trim(
						sanitize_text_field( $data['first_name'] ?? '' ) . ' ' .
						sanitize_text_field( $data['last_name'] ?? '' )
					),
				),
				'custom_info' => $custom_info,
			),
		);

		// Set event ticket total so payment records and gateway processing get the correct amount.
		$booking_data['grand_total'] = $total_price;
		$booking_data['total']       = $total_price;
		$booking_data['payments']    = $data['payments'] ?? array();

		// Map PayPal data to the format BookingService expects (under 'paypal' key with camelCase).
		$payments = $data['payments'] ?? array();
		if ( 'paypal' === ( $payments['getway'] ?? '' ) ) {
			$booking_data['paypal'] = array(
				'orderId'   => $payments['paypal_order_id'] ?? '',
				'payerId'   => $payments['paypal_payer_id'] ?? '',
				'captureId' => $payments['paypal_capture_id'] ?? '',
				'status'    => $payments['status'] ?? '',
				'amount'    => $payments['amount'] ?? $total_price,
			);
		}

		$this->booking_service->createBookingsForAppointment( $booking_data, $appointment_id );

		// 6. Determine waitlist position if applicable.
		$waitlist_position = null;
		if ( 'waitlisted' === $overall_status ) {
			$existing_waitlisted = $this->registration_repo->getWaitlisted( $event->id );
			$waitlist_position   = count( $existing_waitlisted ) + 1;
		}

		// 7. Resolve customer ID.
		$customer_id = get_current_user_id();
		if ( ! empty( $data['bookings'] ) && is_array( $data['bookings'] ) ) {
			$first_booking = reset( $data['bookings'] );
			if ( ! empty( $first_booking['customer_id'] ) ) {
				$customer_id = (int) $first_booking['customer_id'];
			}
		}

		// 8. Create EventRegistration records — one per ticket type.
		$registrations = array();
		foreach ( $tickets as $ticket ) {
			$registration = EventRegistration::create(
				array(
					'event_id'          => $event->id,
					'ticket_type_id'    => $ticket['ticket_type_id'],
					'appointment_id'    => $appointment_id,
					'customer_id'       => $customer_id,
					'quantity'          => $ticket['quantity'],
					'attendee_info'     => $attendee_info,
					'status'            => $overall_status,
					'waitlist_position' => $waitlist_position,
				)
			);

			$registrations[] = $registration;

			// 9. Reserve capacity for confirmed registrations.
			if ( 'confirmed' === $overall_status ) {
				$this->capacity_service->reserveCapacity( $event->id, $ticket['ticket_type_id'], $ticket['quantity'] );
			}
		}

		$first_registration = $registrations[0];

		// 10. Fire action hooks.

		/**
		 * Fires after a user registers for an event.
		 *
		 * @since 1.2.0
		 *
		 * @param object $registration The first created registration.
		 * @param object $event        The event.
		 * @param object $appointment  The created appointment.
		 * @param array  $data         The original registration data.
		 */
		do_action( 'radius_booking_event_registered', $first_registration, $event, $appointment, $data );

		/**
		 * Fires the event status hook to trigger emails.
		 *
		 * @since 1.2.0
		 */
		do_action( 'radius_booking_event_registration_status_' . $overall_status, $first_registration, $event );

		// 11. Build ticket details for all registrations.
		$payment_method  = $payments['getway'] ?? 'offline';
		$ticket_details  = array();
		foreach ( $registrations as $reg ) {
			$tt = EventTicketType::find( (int) $reg->ticket_type_id );
			$ticket_details[] = array(
				'ticket_name'         => $tt ? $tt->name : 'Ticket',
				'quantity'            => (int) $reg->quantity,
				'price'               => $tt ? (float) $tt->price : 0,
				'registration_number' => $reg->registration_number,
			);
		}

		return array(
			'registration_id'     => $first_registration->get_id(),
			'registration_number' => $first_registration->registration_number,
			'appointment_id'      => $appointment_id,
			'status'              => $overall_status,
			'waitlist_position'   => $waitlist_position,
			'event'               => array(
				'id'         => $event->id,
				'name'       => $event->name,
				'start_date' => $event->start_date,
				'start_time' => $event->start_time,
			),
			'quantity'            => $total_quantity,
			'total_amount'        => $total_price,
			'payment_method'      => $total_price > 0 ? $payment_method : null,
			'payment_status'      => $total_price > 0 ? ( 'offline' === $payment_method ? 'pending' : 'paid' ) : null,
			'tickets'             => $ticket_details,
			'sessions'            => $attendee_info['sessions'] ?? array(),
		);
	}

	/**
	 * Normalize and sanitize a custom-field answers payload.
	 *
	 * Accepts a list of {field_id, label, type, value} entries from the
	 * public event registration form, drops malformed rows, and sanitizes
	 * each piece so the array is safe to persist into the JSON column on
	 * customer_bookings.custom_info.
	 *
	 * @param mixed $raw Raw payload value (expected to be an array).
	 *
	 * @return array Sanitized list of answer entries.
	 */
	private function sanitizeCustomInfo( $raw ): array {
		if ( ! is_array( $raw ) ) {
			return array();
		}

		$out = array();
		foreach ( $raw as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}

			$field_id = isset( $row['field_id'] ) ? (int) $row['field_id'] : 0;
			if ( $field_id <= 0 ) {
				continue;
			}

			$value = $row['value'] ?? '';
			if ( is_array( $value ) ) {
				$value = array_map( 'sanitize_text_field', array_map( 'strval', $value ) );
			} else {
				$value = sanitize_textarea_field( (string) $value );
			}

			$out[] = array(
				'field_id' => $field_id,
				'label'    => sanitize_text_field( (string) ( $row['label'] ?? '' ) ),
				'type'     => sanitize_key( (string) ( $row['type'] ?? 'text' ) ),
				'value'    => $value,
			);
		}

		return $out;
	}

	/**
	 * Cancel an event registration.
	 *
	 * @param int $registration_id The registration ID.
	 *
	 * @return bool Whether the cancellation succeeded.
	 *
	 * @throws \Exception If the registration is not found or already cancelled.
	 */
	public function cancelRegistration( int $registration_id ): bool {
		$registration = $this->registration_repo->findOrFail( $registration_id );

		if ( 'cancelled' === $registration->status ) {
			throw new \Exception( esc_html__( 'This registration is already cancelled.', 'radius-booking' ) );
		}

		$previous_status = $registration->status;

		// 1. Update status to cancelled.
		$registration->update( array( 'status' => 'cancelled' ) );

		// 2. Release capacity if the registration was confirmed.
		if ( 'confirmed' === $previous_status ) {
			$this->capacity_service->releaseCapacity(
				(int) $registration->event_id,
				(int) $registration->ticket_type_id,
				(int) $registration->quantity
			);

			// 3. Promote from waitlist.
			$this->capacity_service->promoteFromWaitlist( (int) $registration->event_id );
		}

		/**
		 * Fires after an event registration is cancelled.
		 *
		 * @since 1.2.0
		 *
		 * @param object $registration   The cancelled registration.
		 * @param string $previous_status The status before cancellation.
		 */
		do_action( 'radius_booking_event_registration_cancelled', $registration, $previous_status );

		return true;
	}
}
