<?php
/**
 * Public AJAX boundary for scheduler availability and booking.
 *
 * @package Niwa_AI
 */

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

class Niwa_AI_Scheduler_Ajax_Controller {

	const NONCE_ACTION = 'niwa_ai_scheduler';
	const BOOK_LIMIT   = 8;
	const BOOK_WINDOW  = HOUR_IN_SECONDS;

	/** @var Niwa_AI_Appointment_Service */
	private $service;

	/**
	 * @param Niwa_AI_Appointment_Service|null $service Appointment service.
	 */
	public function __construct( $service = null ) {
		$this->service = $service ?: new Niwa_AI_Appointment_Service();
	}

	/** Register logged-in and guest AJAX hooks. */
	public function register_hooks() {
		add_action( 'wp_ajax_niwa_ai_scheduler_availability', array( $this, 'ajax_availability' ) );
		add_action( 'wp_ajax_nopriv_niwa_ai_scheduler_availability', array( $this, 'ajax_availability' ) );
		add_action( 'wp_ajax_niwa_ai_scheduler_book', array( $this, 'ajax_book' ) );
		add_action( 'wp_ajax_nopriv_niwa_ai_scheduler_book', array( $this, 'ajax_book' ) );
	}

	/** Return month and/or day availability. */
	public function ajax_availability() {
		$this->require_valid_request();
		$settings = Niwa_AI_Appointment_Service::get_settings();
		if ( empty( $settings['enabled'] ) ) {
			$this->send_error( 'scheduler_disabled', __( 'Online scheduling is currently unavailable.', 'niwa-ai' ), 400 );
		}
		$service_id    = $this->post_text( 'service_id' );
		$team_member_id = $this->post_text( 'team_member_id' );
		$month         = $this->post_text( 'month' );
		$date          = $this->post_text( 'date' );
		if ( '' === $service_id || ( '' === $month && '' === $date ) ) {
			$this->send_error( 'missing_parameters', __( 'Choose a service and a month or date.', 'niwa-ai' ), 400 );
		}

		$response = array();
		if ( '' !== $month ) {
			$result = $this->service->get_month_availability( $service_id, $month, $team_member_id );
			$this->merge_or_error( $response, $result );
		}
		if ( '' !== $date ) {
			$result = $this->service->get_day_availability( $service_id, $date, $team_member_id );
			$this->merge_or_error( $response, $result );
		}
		wp_send_json_success( $response );
	}

	/** Validate contact fields and atomically create a booking. */
	public function ajax_book() {
		$this->require_valid_request();
		if ( '' !== $this->post_text( 'website' ) ) {
			$this->send_error( 'invalid_request', __( 'The booking request is invalid.', 'niwa-ai' ), 400 );
		}
		$proposed_session = $this->post_text( 'session_id' );
		$verified_session = class_exists( 'Niwa_AI_Session_Utils' )
			? Niwa_AI_Session_Utils::resolve_verified_session( true, $proposed_session )
			: '';
		if ( ! $this->consume_booking_rate_limit( $verified_session ) ) {
			$this->send_error( 'rate_limited', __( 'Too many booking attempts. Please try again later.', 'niwa-ai' ), 429 );
		}

		// Deliberately do not accept details or conversation_summary from the client.
		$args = array(
			'service_id'    => $this->post_text( 'service_id' ),
			'team_member_id'=> $this->post_text( 'team_member_id' ),
			'date'          => $this->post_text( 'date' ),
			'time'          => $this->post_text( 'time' ),
			'start_at'      => $this->post_text( 'start_at' ),
			'name'          => $this->post_text( 'name' ),
			'email'         => $this->post_text( 'email' ),
			'phone'         => $this->post_text( 'phone' ),
			// Browser text is not trusted; use the signed HttpOnly session owner.
			'session_id'    => $verified_session,
			'source'        => 'frontend_scheduler',
		);
		$result = $this->service->book_appointment( $args );
		if ( empty( $result['success'] ) ) {
			$error  = isset( $result['error'] ) ? $result['error'] : array();
			$code   = isset( $error['code'] ) ? $error['code'] : 'booking_failed';
			$status = in_array( $code, array( 'slot_unavailable', 'lock_timeout' ), true ) ? 409 : 400;
			$this->send_error( $code, isset( $error['message'] ) ? $error['message'] : __( 'The appointment could not be booked.', 'niwa-ai' ), $status );
		}
		if ( empty( $result['appointment'] ) || ! is_array( $result['appointment'] ) ) {
			$this->send_error( 'booking_response_invalid', __( 'The appointment was saved, but its confirmation could not be loaded. Please contact us before trying again.', 'niwa-ai' ), 500 );
		}

		wp_send_json_success(
			array(
				'appointment' => $this->public_booking( $result['appointment'] ),
			)
		);
	}

	/** Require POST and a valid public scheduler nonce. */
	private function require_valid_request() {
		if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== strtoupper( (string) $_SERVER['REQUEST_METHOD'] ) ) {
			$this->send_error( 'method_not_allowed', __( 'This endpoint accepts POST requests only.', 'niwa-ai' ), 405 );
		}
		if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
			$this->send_error( 'invalid_nonce', __( 'The scheduling session expired. Please refresh the page.', 'niwa-ai' ), 403 );
		}
	}

	/** Merge a domain result or terminate with its error. */
	private function merge_or_error( array &$response, array $result ) {
		if ( isset( $result['error'] ) ) {
			$this->send_error( $result['error']['code'], $result['error']['message'], 400 );
		}
		$response = array_merge( $response, $result );
	}

	/** Lightweight per-session/IP booking abuse guard. */
	private function consume_booking_rate_limit( $session ) {
		$ip      = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ) : '';
		if ( class_exists( 'Niwa_AI_Session_Utils' ) && method_exists( 'Niwa_AI_Session_Utils', 'consume_fixed_window_limit' ) ) {
			return Niwa_AI_Session_Utils::consume_fixed_window_limit( 'scheduler_book', $session . '|' . $ip, self::BOOK_LIMIT, self::BOOK_WINDOW );
		}
		$key     = 'niwa_sched_' . md5( $session . '|' . $ip );
		$count   = (int) get_transient( $key );
		if ( $count >= self::BOOK_LIMIT ) {
			return false;
		}
		set_transient( $key, $count + 1, self::BOOK_WINDOW );
		return true;
	}

	/** Public booking response without staff email, notes, or CRM details. */
	private function public_booking( array $appointment ) {
		$keys = array(
			'id', 'booking_uid', 'service_id', 'service_name',
			'team_member_id', 'team_member_name', 'starts_at', 'ends_at', 'timezone',
			'duration_minutes', 'buffer_minutes', 'status',
		);
		$out = array();
		foreach ( $keys as $key ) {
			$out[ $key ] = isset( $appointment[ $key ] ) ? $appointment[ $key ] : '';
		}
		try {
			$timezone         = new DateTimeZone( '' !== $out['timezone'] ? $out['timezone'] : 'UTC' );
			$out['start_iso'] = ( new DateTimeImmutable( $out['starts_at'], $timezone ) )->format( DateTimeInterface::ATOM );
			$out['end_iso']   = ( new DateTimeImmutable( $out['ends_at'], $timezone ) )->format( DateTimeInterface::ATOM );
		} catch ( Exception $error ) {
			$out['start_iso'] = '';
			$out['end_iso']   = '';
		}
		return $out;
	}

	/** Read one scalar POST value safely. */
	private function post_text( $key ) {
		if ( ! isset( $_POST[ $key ] ) || is_array( $_POST[ $key ] ) ) {
			return '';
		}
		return sanitize_text_field( wp_unslash( (string) $_POST[ $key ] ) );
	}

	/** Send the standard AJAX error envelope. */
	private function send_error( $code, $message, $status ) {
		wp_send_json_error(
			array(
				'code'    => (string) $code,
				'message' => (string) $message,
			),
			(int) $status
		);
	}
}
