<?php
/**
 * Appointment Validator
 *
 * @package Replyan_AI
 */

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

class Replyan_AI_Appointment_Validator {

	private $form_id;

	public function __construct( int $form_id ) {
		$this->form_id = $form_id;
	}

	/**
	 * Check if the submission values are valid for booking.
	 */
	public function validate_submission( int $customer_id, string $session_id, array $values ) {
		$keys = $this->get_field_keys();
		if ( empty( $keys['date'] ) || empty( $keys['time'] ) ) {
			return true; // Missing date/time fields, cannot validate as appointment
		}
		
		$date = $values[ $keys['date'] ] ?? '';
		$time = $values[ $keys['time'] ] ?? '';
		
		return $this->can_book( $customer_id, $session_id, $date, $time );
	}

	/**
	 * Check if the customer can book an appointment.
	 */
	public function can_book( int $customer_id, string $session_id, string $date, string $time, int $duration = 60 ) {
		$settings = Replyan_AI_Form_Settings::get_settings( $this->form_id );
		$datetime = DateTime::createFromFormat( 'Y-m-d H:i', "{$date} {$time}" );

		if ( ! $datetime ) {
			return new WP_Error( 'invalid_date', 'Invalid date or time format provided.' );
		}

		// 1. Advance notice check
		$advance_notice   = ! empty( $settings['advance_notice'] ) ? intval( $settings['advance_notice'] ) : 0;
		$min_booking_time = ( new DateTime() )->modify( "+{$advance_notice} minutes" );
		if ( $datetime < $min_booking_time ) {
			return new WP_Error( 'too_late', 'Booking must be made at least ' . $advance_notice . ' minutes in advance.' );
		}

		// 2. Within daily range?
		if ( ! empty( $settings['format_time'] ) && $settings['format_time'] === 'range' && ! empty( $settings['time_range'] ) && is_array( $settings['time_range'] ) ) {
			$start_time  = $settings['time_range'][0];
			$end_time    = $settings['time_range'][1];
			$start_limit = DateTime::createFromFormat( 'Y-m-d H:i', "{$date} {$start_time}" );
			$end_limit   = DateTime::createFromFormat( 'Y-m-d H:i', "{$date} {$end_time}" );

			if ( $start_limit && $end_limit && ( $datetime < $start_limit || $datetime >= $end_limit ) ) {
				return new WP_Error( 'out_of_range', 'Selected time is outside business hours.' );
			}
		}

		// 3. Max daily appointments reached?
		$max_daily = ! empty( $settings['max_appointments_per_day'] ) ? intval( $settings['max_appointments_per_day'] ) : 0;
		if ( $max_daily > 0 ) {
			$daily_count = $this->count_bookings_for_date( $date );
			if ( $daily_count >= $max_daily ) {
				return new WP_Error( 'daily_limit', 'Daily appointment limit reached.' );
			}
		}

		// 4. Max per customer per day?
		$max_per_customer = ! empty( $settings['max_appointments_per_customer'] ) ? intval( $settings['max_appointments_per_customer'] ) : 0;
		if ( $max_per_customer > 0 ) {
			$customer_daily = $this->count_customer_bookings_for_date( $customer_id, $session_id, $date );
			if ( $customer_daily >= $max_per_customer ) {
				return new WP_Error( 'customer_limit', 'You have reached your daily booking limit.' );
			}
		}

		// 5. Slot capacity check
		$max_per_slot = ! empty( $settings['max_appointments_per_slot'] ) ? intval( $settings['max_appointments_per_slot'] ) : 0;
		if ( $max_per_slot > 0 ) {
			$slot_count = $this->count_bookings_for_slot( $date, $time );
			if ( $slot_count >= $max_per_slot ) {
				return new WP_Error( 'slot_full', 'This time slot is fully booked.' );
			}
		}

		// 6. Overlap check (with buffer)
		$overlap_buffer = ! empty( $settings['overlap_time_period'] ) ? intval( $settings['overlap_time_period'] ) : 0;
		$overlap_unit   = ! empty( $settings['overlap_time_unit'] ) ? $settings['overlap_time_unit'] : 'minutes';
		
		if ( $overlap_unit === 'hours' ) {
			$overlap_buffer = $overlap_buffer * 60;
		}

		if ( $overlap_buffer > 0 ) {
			if ( $this->has_overlap( $date, $time, $duration, $overlap_buffer ) ) {
				return new WP_Error( 'overlap', 'This time conflicts with an existing appointment.' );
			}
		}

		return true;
	}

	/**
	 * Get an array of available time slots for a given date.
	 */
	public function get_available_slots( int $customer_id, string $session_id, string $date, int $duration = 60 ): array {
		$settings = Replyan_AI_Form_Settings::get_settings( $this->form_id );
		
		$start_time = '00:00';
		$end_time   = '23:59';
		$interval   = 60;

		if ( ! empty( $settings['time_range'] ) && is_array( $settings['time_range'] ) ) {
			$start_time = $settings['time_range'][0];
			$end_time   = $settings['time_range'][1];
		}
		
		$interval = ! empty( $settings['time_interval'] ) ? intval( $settings['time_interval'] ) : 60;

		$start_timestamp = strtotime( "{$date} {$start_time}" );
		$end_timestamp   = strtotime( "{$date} {$end_time}" );
		
		$available_slots = array();
		$current_timestamp = $start_timestamp;

		while ( $current_timestamp <= $end_timestamp ) {
			$time_str = wp_date( 'H:i', $current_timestamp );
			
			// Check if this specific slot can be booked
			$result = $this->can_book( $customer_id, $session_id, $date, $time_str, $duration );
			
			if ( ! is_wp_error( $result ) ) {
				$available_slots[] = $time_str;
			}
			
			$current_timestamp += ( $interval * 60 );
		}

		return $available_slots;
	}

	private function get_field_keys(): array {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$fields = $wpdb->get_results( $wpdb->prepare( "SELECT field_key, type FROM {$wpdb->prefix}replyan_form_fields WHERE form_id = %d", $this->form_id ) );
		$keys   = array( 'date' => '', 'time' => '' );
		
		if ( $fields ) {
			foreach ( $fields as $field ) {
				if ( $field->type === 'appointment' ) {
					$keys['date'] = $field->field_key . '_date';
					$keys['time'] = $field->field_key . '_time';
					break; // Found the primary appointment field, no need to keep searching
				}
				if ( $field->type === 'date' && empty( $keys['date'] ) ) {
					$keys['date'] = $field->field_key;
				}
				if ( $field->type === 'time' && empty( $keys['time'] ) ) {
					$keys['time'] = $field->field_key;
				}
			}
		}
		
		return $keys;
	}

	private function get_bookings_for_date( string $date ): array {
		global $wpdb;
		$table = $wpdb->prefix . 'replyan_form_submissions';
		
		// Pre-filter submissions that contain the date string to optimize
		$like_date = '%' . $wpdb->esc_like( $date ) . '%';
		
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$results = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, session_id, `values` FROM {$table} WHERE form_id = %d AND `values` LIKE %s", $this->form_id, $like_date ) );
		
		$keys = $this->get_field_keys();
		if ( empty( $keys['date'] ) || empty( $keys['time'] ) ) {
			return array(); // Cannot validate without date/time fields mapped
		}
		
		$bookings = array();
		
		if ( $results ) {
			foreach ( $results as $row ) {
				$values = json_decode( $row->values, true );
				if ( is_array( $values ) && isset( $values[ $keys['date'] ] ) && $values[ $keys['date'] ] === $date ) {
					$bookings[] = array(
						'user_id'          => intval( $row->user_id ),
						'session_id'       => $row->session_id,
						'appointment_time' => $values[ $keys['time'] ] ?? '',
						'duration'         => 60 // Assuming default duration, this could be dynamic if needed
					);
				}
			}
		}
		
		return $bookings;
	}

	private function has_overlap( string $date, string $time, int $duration, int $buffer ): bool {
		$requested_start = strtotime( "{$date} {$time}" );
		$requested_end   = strtotime( "{$date} {$time}" ) + ( $duration * 60 );

		$bookings = $this->get_bookings_for_date( $date );

		foreach ( $bookings as $b ) {
			if ( empty( $b['appointment_time'] ) ) {
				continue;
			}
			$existing_start = strtotime( "{$date} {$b['appointment_time']}" ) - ( $buffer * 60 );
			$existing_end   = strtotime( "{$date} {$b['appointment_time']}" ) + ( $b['duration'] * 60 ) + ( $buffer * 60 );

			if ( $requested_start < $existing_end && $requested_end > $existing_start ) {
				return true; // Overlap detected
			}
		}
		
		return false;
	}

	private function count_bookings_for_date( string $date ): int {
		return count( $this->get_bookings_for_date( $date ) );
	}

	private function count_customer_bookings_for_date( int $customer_id, string $session_id, string $date ): int {
		$bookings = $this->get_bookings_for_date( $date );
		$count    = 0;
		foreach ( $bookings as $b ) {
			if ( $customer_id > 0 && $b['user_id'] === $customer_id ) {
				$count++;
			} elseif ( $customer_id === 0 && ! empty( $b['session_id'] ) && $b['session_id'] === $session_id ) {
				$count++;
			}
		}
		return $count;
	}

	private function count_bookings_for_slot( string $date, string $time ): int {
		$bookings = $this->get_bookings_for_date( $date );
		$count    = 0;
		foreach ( $bookings as $b ) {
			if ( $b['appointment_time'] === $time ) {
				$count++;
			}
		}
		return $count;
	}
}
