<?php
/**
 * RadiusBooking API Route Registrar
 *
 * This class is responsible for registering API routes dynamically with the WordPress REST API.
 * It supports CRUD operations and allows for middleware to be applied globally or per route.
 *
 * @package RadiusTheme\RadiusBooking\Core\Api\Routes
 */

namespace RadiusTheme\RadiusBooking\Repositories;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}
use Exception;
use RadiusTheme\RadiusBooking\Abstracts\BaseRepository;
use RadiusTheme\RadiusBooking\Core\Database\Connection;
use RadiusTheme\RadiusBooking\Models\Appointment;
use RadiusTheme\RadiusBooking\Models\Payment;
use RadiusTheme\RadiusBooking\Resources\AppointmentResource;

/**
 * Repositories/AppointmentRepository.php
 * Enhanced appointment repository with business logic
 */
class AppointmentRepository extends BaseRepository {

	/**
	 * The model class that this repository interacts with.
	 *
	 * @var string
	 */

	protected string $model = Appointment::class;

	/**
	 * Retrieve all appointments for a given user, ordered by appointment date descending.
	 *
	 * @param int $userId The ID of the user whose appointments to retrieve.
	 * @return array An array of Appointment models matching the user.
	 */
	public function findByUser( int $userId ): array {
		return Appointment::where( 'user_id', $userId )
							->orderBy( 'appointment_date', 'DESC' )
							->get();
	}
	/**
	 * Retrieve all appointments within a specified date range, ordered by appointment date ascending.
	 *
	 * @param string $startDate The start date (inclusive) in 'Y-m-d' or datetime format.
	 * @param string $endDate   The end date (inclusive) in 'Y-m-d' or datetime format.
	 * @return array An array of Appointment models within the date range.
	 */
	public function findByDateRange( string $startDate, string $endDate ): array {
		return Appointment::where( 'appointment_date', '>=', $startDate )
							->where( 'appointment_date', '<=', $endDate )
							->orderBy( 'appointment_date', 'ASC' )
							->get();
	}
	/**
	 * Retrieve upcoming appointments that are not cancelled, ordered by appointment date ascending.
	 *
	 * @param int $limit The maximum number of appointments to retrieve. Default is 10.
	 *
	 * @return array An array of upcoming Appointment models.
	 */
	public function findUpcoming( int $limit = 10 ): array {
		return Appointment::where( 'appointment_date', '>=', current_time( 'mysql' ) )
							->where( 'status', '!=', 'cancelled' )
							->orderBy( 'appointment_date', 'ASC' )
							->limit( $limit )
							->get();
	}
	/**
	 * Retrieve all appointments filtered by status, ordered by appointment date descending.
	 *
	 * @param string $status The status to filter appointments by (e.g., 'pending', 'confirmed').
	 * @return array An array of Appointment models with the specified status.
	 */
	public function findByStatus( string $status ): array {
		return Appointment::where( 'status', $status )
							->orderBy( 'appointment_date', 'DESC' )
							->get();
	}

	/**
	 * Count the number of appointments that match the specified status.
	 *
	 * @param mixed $status The status to filter appointments by.
	 *
	 * @return int The total number of appointments matching the given status.
	 */
	public function countByStatus( $status, $booking_type = null ): int {
		$query = Appointment::where( 'status', $status );
		if ( $booking_type ) {
			$query = $query->where( 'booking_type', $booking_type );
		}
		return $query->count();
	}

	/**
	 * Count appointments, optionally filtered by booking type.
	 *
	 * @param string|null $booking_type Optional booking type filter.
	 *
	 * @return int
	 */
	public function count( $booking_type = null ): int {
		if ( $booking_type ) {
			return Appointment::where( 'booking_type', $booking_type )->count();
		}
		return parent::count();
	}
	/**
	 * Retrieve all appointments with a 'pending' status.
	 *
	 * @return array An array of Appointment models that are pending.
	 */
	public function getPendingAppointments(): array {
		return Appointment::pending()->get();
	}
	/**
	 * Retrieve all appointments with a 'confirmed' status.
	 *
	 * @return array An array of Appointment models that are confirmed.
	 */
	public function getConfirmedAppointments(): array {
		return Appointment::confirmed()->get();
	}

	/**
	 * Create a new appointment after performing additional business logic validation.
	 *
	 * @param array $data The appointment data to create.
	 *
	 * @throws \Exception If the appointment slot validation fails.
	 *
	 * @return Appointment The newly created Appointment model instance.
	 */
	public function createWithValidation( array $data ): Appointment {
		// Additional business logic validation
		$this->validateAppointmentSlot( $data );

		return $this->create( $data );
	}

	/**
	 * Update the status of an appointment by its ID.
	 *
	 * @param int    $id     The ID of the appointment to update.
	 * @param string $status The new status value.
	 *
	 * @return bool True if the update was successful, false otherwise.
	 */
	public function updateStatus( int $id, string $status ): bool {
		return $this->update( $id, array( 'status' => $status ) );
	}

	/**
	 * Cancel an appointment by setting its status to 'cancelled' and optionally adding a cancellation reason.
	 *
	 * @param int         $id     The ID of the appointment to cancel.
	 * @param string|null $reason Optional reason for cancellation.
	 *
	 * @return bool True if the appointment was successfully updated, false otherwise.
	 */
	public function cancelAppointment( int $id, string $reason = null ): bool {
		$data = array( 'status' => 'cancelled' );
		if ( $reason ) {
			$data['cancellation_reason'] = $reason;
		}

		return $this->update( $id, $data );
	}
	/**
	 * Retrieve appointments along with related user and service details using a raw SQL query.
	 *
	 * This method returns appointment records joined with user name/email and service name/duration.
	 *
	 * @return array The raw query result as an array of appointments with related user and service data.
	 */
	public function getAppointmentsWithRelations(): array {
		return DB::raw(
			"
            SELECT a.*, u.name as user_name, u.email as user_email, 
                   s.name as service_name, s.duration as service_duration
            FROM {$this->getTableName('appointments')} a
            LEFT JOIN {$this->getTableName('users')} u ON a.user_id = u.id
            LEFT JOIN {$this->getTableName('services')} s ON a.service_id = s.id
            WHERE a.deleted_at IS NULL
            ORDER BY a.appointment_date DESC
        "
		);
	}
	/**
	 * Validate that the appointment time slot does not overlap with existing non-cancelled appointments.
	 *
	 * @param array $data The appointment data containing 'appointment_date', 'start_time', and 'end_time'.
	 *
	 * @throws \Exception If the appointment time slot overlaps with an existing booking.
	 */
	public function validateAppointmentSlot( array $data ) {
		// Check for overlapping appointments
		$overlapping = Appointment::where( 'appointment_date', $data['appointment_date'] )
									->where( 'start_time', '<', $data['end_time'] )
									->where( 'end_time', '>', $data['start_time'] )
									->where( 'status', '!=', 'cancelled' )
									->exists();
		if ( $overlapping ) {
			return 'The selected time slot is already booked.';
		}
	}
	/**
	 * Get the full table name with the database prefix.
	 *
	 * @param string $table The base table name.
	 * @return string The full table name including the database prefix.
	 */
	private function getTableName( string $table ): string {
		return Connection::getInstance()->prefix() . $table;
	}

	/**
	 * Retrieve appointments within a specific time range for a given service and employee.
	 *
	 * @param int|string $service_id The ID of the service for which to retrieve appointments.
	 * @param int|string $employee_id The ID of the employee associated with the appointments.
	 * @param string $date The date of the appointments in 'Y-m-d' format or a format parsable by strtotime.
	 * @param string $startTime The starting time of the appointment range.
	 * @param string $endTime The ending time of the appointment range.
	 *
	 * @return array An array of Appointment models matching the provided criteria.
	 */
	public function getAppointmentsInTimeRange( $service_id, $employee_id, $date, $startTime, $endTime ): array {
		return Appointment::where( 'start_time', '=', $startTime )
							->where( 'end_time', '=', $endTime )
							->where( 'appointment_date', '=', gmdate( 'Y-m-d', strtotime( $date ) ) )
							->where( 'status', '!=', 'cancelled' )
							->where( 'service_id', '=', $service_id )
							->where( 'employee_id', '=', $employee_id )
							->orderBy( 'start_time', 'ASC' )
							->get();
	}

	/**
	 * Retrieve appointments scheduled on a specific date.
	 *
	 * @param string $dayDate The date to filter appointments by, formatted as 'YYYY-MM-DD'.
	 */
	public function getAppointmentsByDate( string $dayDate ) {
		return Appointment::whereDate( 'appointment_date', '=', $dayDate )
							->get();
	}

	/**
	 * Count the number of daily appointments that are either confirmed or pending for a specific date.
	 *
	 * @param string $dayDate The date for which to count appointments in the format 'Y-m-d'.
	 *
	 * @return int The total count of appointments for the given date.
	 */
	public function getDailyAppointmentsCount( string $dayDate ): int {
		return Appointment::whereDate( 'appointment_date', '=', $dayDate )
							->whereIn( 'status', array( 'confirmed', 'pending' ) )
							->count();
	}

	/**
	 * Get the count of confirmed appointments for a specific day.
	 *
	 * @param string $dayDate The date for which to count confirmed appointments, in 'YYYY-MM-DD' format.
	 *
	 * @return int The total number of confirmed appointments on the given date.
	 */
	public function getDailyConfirmedAppointmentsCount( string $dayDate ): int {
		return Appointment::whereDate( 'appointment_date', '=', $dayDate )
							->where( 'status', 'confirmed' )
							->count();
	}

	/**
	 * Transform a collection of appointments into a resource collection.
	 *
	 * @param mixed $appointments A collection or array of Appointment instances to be wrapped in a resource.
	 */
	public function getAppointmentsWithResource( $appointments ) {
		return AppointmentResource::collection( $appointments );
	}

	/**
	 * Retrieve child appointments associated with a specific parent, ordered by appointment date in ascending order.
	 *
	 * @param mixed $parent_id The identifier of the parent whose child appointments are to be retrieved.
	 *
	 * @return mixed A collection of Appointment models associated with the specified parent.
	 */
	public function getChildAppointments( $parent_id ) {
		$appointments = Appointment::where( 'parent_id', $parent_id )
									->orderBy( 'appointment_date', 'asc' )
									->get();

		return $appointments;
	}

	/**
	 * Retrieve the parent appointment's ID for a given appointment if it exists.
	 *
	 * @param mixed $appointment_id The ID of the appointment to retrieve the parent for.
	 *
	 * @return mixed The ID of the parent appointment if found, or null if no parent exists or the appointment is not found.
	 */
	public function getParentAppointment( $appointment_id ) {
		$appointment = Appointment::find( $appointment_id );
		if ( $appointment && $appointment->parent_id ) {
			$parentAppointment = Appointment::find( $appointment->parent_id );
			if( $parentAppointment){
				return $parentAppointment->get_id();
			}
			return null;
		}
		return null;
	}
}
