<?php

declare( strict_types=1 );

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

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching

/**
 * Public REST routes for booking operations.
 * POST /bookings/create-pending  — availability check + create pending booking
 * POST /bookings/confirm-payment — confirm after Stripe payment
 * POST /bookings/cash            — create cash / pay-on-arrival booking
 * POST /bookings/{ref}/abandon   — delete pending booking (customer clicked Back)
 */
class Playard_API_Bookings {

	private Playard_Booking  $booking;
	private Playard_Slot     $slot;
	private Playard_Customer $customer;
	private Playard_Payment  $payment;

	public function __construct( Playard_Booking $booking, Playard_Slot $slot, Playard_Customer $customer, Playard_Payment $payment ) {
		$this->booking  = $booking;
		$this->slot     = $slot;
		$this->customer = $customer;
		$this->payment  = $payment;
	}

	public function register_routes(): void {
		$namespace = 'playard/v1';

		register_rest_route( $namespace, '/bookings/create-pending', [
			'methods'             => 'POST',
			'callback'            => [ $this, 'create_pending' ],
			// Public: guest checkout — customer provides details in the request body.
			'permission_callback' => '__return_true',
		] );

		register_rest_route( $namespace, '/bookings/confirm-payment', [
			'methods'             => 'POST',
			'callback'            => [ $this, 'confirm_payment' ],
			'permission_callback' => '__return_true',
			'args'                => [
				'booking_ref'       => [ 'required' => true, 'type' => 'string' ],
				'payment_intent_id' => [ 'required' => true, 'type' => 'string' ],
			],
		] );

		register_rest_route( $namespace, '/bookings/cash', [
			'methods'             => 'POST',
			'callback'            => [ $this, 'cash' ],
			'permission_callback' => '__return_true',
		] );

		register_rest_route( $namespace, '/bookings/my', [
			'methods'             => 'GET',
			'callback'            => [ $this, 'my_bookings' ],
			'permission_callback' => '__return_true',
			'args'                => [
				'status'   => [ 'required' => false, 'type' => 'string', 'default' => 'all',
				               'enum'     => [ 'upcoming', 'past', 'all' ] ],
				'page'     => [ 'required' => false, 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
				'per_page' => [ 'required' => false, 'type' => 'integer', 'default' => 10, 'minimum' => 1, 'maximum' => 50 ],
			],
		] );

		register_rest_route( $namespace, '/bookings/(?P<ref>[A-Z0-9\-]+)', [
			'methods'             => 'GET',
			'callback'            => [ $this, 'get_booking' ],
			// Auth required: returns private customer booking details. The callback
			// additionally scopes the query to the JWT's own customer_id (ownership check).
			'permission_callback' => function ( WP_REST_Request $request ) {
				$auth = (string) $request->get_header( 'authorization' );
				if ( '' === $auth || ! str_starts_with( $auth, 'Bearer ' ) ) {
					return new WP_Error( 'rest_forbidden', __( 'Authentication required.', 'playard' ), [ 'status' => 401 ] );
				}
				return true;
			},
			'args'                => [
				'ref' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
			],
		] );

		register_rest_route( $namespace, '/bookings/(?P<ref>[A-Z0-9\-]+)/pay-online', [
			[
				'methods'             => 'POST',
				'callback'            => [ $this, 'create_online_payment_intent' ],
				'permission_callback' => '__return_true',
			],
			[
				'methods'             => 'PUT',
				'callback'            => [ $this, 'confirm_online_payment' ],
				'permission_callback' => '__return_true',
			],
		] );

		register_rest_route( $namespace, '/bookings/(?P<ref>[A-Z0-9\-]+)/abandon', [
			'methods'             => 'POST',
			'callback'            => [ $this, 'abandon_booking' ],
			// Auth required: this deletes a pending booking (mutates data). Accept
			// either the customer's JWT (Bearer) or the signed abandon_token that was
			// handed back by /bookings/create-pending. The callback verifies both.
			'permission_callback' => function ( WP_REST_Request $request ) {
				$auth          = (string) $request->get_header( 'authorization' );
				$abandon_token = sanitize_text_field( (string) ( $request->get_param( 'abandon_token' ) ?? '' ) );

				if ( '' !== $auth && str_starts_with( $auth, 'Bearer ' ) ) {
					return true;
				}
				if ( '' !== $abandon_token ) {
					return true;
				}
				return new WP_Error( 'rest_forbidden', __( 'Authentication required.', 'playard' ), [ 'status' => 401 ] );
			},
			'args'                => [
				'ref'           => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				'abandon_token' => [ 'required' => false, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
			],
		] );

		register_rest_route( $namespace, '/bookings/(?P<ref>[A-Z0-9\-]+)/cancel', [
			[
				'methods'             => 'GET',
				'callback'            => [ $this, 'cancel_preview' ],
				'permission_callback' => '__return_true',
				'args'                => [
					'ref'   => [ 'required' => true,  'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
					'token' => [ 'required' => true,  'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				],
			],
			[
				'methods'             => 'POST',
				'callback'            => [ $this, 'cancel' ],
				'permission_callback' => '__return_true',
				'args'                => [
					'ref' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field' ],
				],
			],
		] );
	}

	// ── Single booking (GET /bookings/{ref}) ─────────────────────────────────────

	public function get_booking( WP_REST_Request $request ): WP_REST_Response {
		$customer_id = $this->authenticate( $request );

		if ( ! $customer_id ) {
			return new WP_REST_Response(
				playard_rest_error( 'unauthorized', __( 'Authentication required.', 'playard' ) ),
				401
			);
		}

		$ref = sanitize_text_field( (string) $request->get_param( 'ref' ) );

		global $wpdb;
		$booking = $wpdb->get_row( $wpdb->prepare(
			"SELECT b.*, f.name AS field_name
			   FROM {$wpdb->prefix}playard_bookings b
			   LEFT JOIN {$wpdb->prefix}playard_fields f ON f.id = b.field_id
			  WHERE b.booking_ref = %s AND b.customer_id = %d LIMIT 1",
			$ref,
			$customer_id
		) );

		if ( ! $booking ) {
			return new WP_REST_Response(
				playard_rest_error( 'not_found', __( 'Booking not found.', 'playard' ) ),
				404
			);
		}

		return new WP_REST_Response( playard_rest_success( [
			'booking_ref'    => $booking->booking_ref,
			'field_name'     => (string) ( $booking->field_name ?? '' ),
			'booking_date'   => $booking->booking_date,
			'start_time'     => $booking->start_time,
			'end_time'       => $booking->end_time,
			'slots_count'    => (int) $booking->slots_count,
			'slot_duration'  => (int) ( $booking->slot_duration ?? 60 ),
			'booked_slots'   => ! empty( $booking->booked_slots ) ? json_decode( (string) $booking->booked_slots, true ) : null,
			'sport'          => $booking->sport,
			'amount_total'   => (float) $booking->amount_total,
			'amount_paid'    => (float) $booking->amount_paid,
			'amount_due'     => (float) $booking->amount_due,
			'booking_status' => $booking->booking_status,
			'payment_status' => $booking->payment_status,
			'payment_gateway' => $booking->payment_gateway ?? '',
			'payment_mode'   => $booking->payment_mode ?? '',
			'created_at'     => $booking->created_at,
		] ), 200 );
	}

	// ── My bookings ──────────────────────────────────────────────────────────────

	public function my_bookings( WP_REST_Request $request ): WP_REST_Response {
		$customer_id = $this->authenticate( $request );

		if ( ! $customer_id ) {
			return new WP_REST_Response(
				playard_rest_error( 'unauthorized', __( 'Authentication required.', 'playard' ) ),
				401
			);
		}

		$result = $this->booking->get_customer_bookings(
			$customer_id,
			(string) $request->get_param( 'status' ),
			(int) $request->get_param( 'page' ),
			(int) $request->get_param( 'per_page' )
		);

		return new WP_REST_Response( playard_rest_success( $result ), 200 );
	}

	// ── Cancel ────────────────────────────────────────────────────────────────────

	public function cancel( WP_REST_Request $request ): WP_REST_Response {
		$ref    = sanitize_text_field( (string) $request->get_param( 'ref' ) );
		$token  = sanitize_text_field( (string) $request->get_param( 'token' ) );
		$reason = sanitize_textarea_field( (string) $request->get_param( 'reason' ) );

		// Token-based cancel (from email link) — no auth required.
		if ( ! empty( $token ) ) {
			$result = $this->booking->customer_cancel_booking( $ref, $token, $reason );

			if ( is_wp_error( $result ) ) {
				$http = $result->get_error_code() === 'not_found' ? 404 : 422;
				return new WP_REST_Response(
					playard_rest_error( $result->get_error_code(), $result->get_error_message() ),
					$http
				);
			}

			return new WP_REST_Response(
				playard_rest_success( $result, __( 'Booking cancelled.', 'playard' ) ),
				200
			);
		}

		// JWT-based cancel (from My Bookings page).
		$customer_id = $this->authenticate( $request );

		if ( ! $customer_id ) {
			return new WP_REST_Response(
				playard_rest_error( 'unauthorized', __( 'Authentication required.', 'playard' ) ),
				401
			);
		}

		$result = $this->booking->cancel_booking( $ref, $customer_id );

		if ( is_wp_error( $result ) ) {
			return new WP_REST_Response(
				playard_rest_error( $result->get_error_code(), $result->get_error_message() ),
				422
			);
		}

		return new WP_REST_Response(
			playard_rest_success( is_array( $result ) ? $result : [], __( 'Booking cancelled.', 'playard' ) ),
			200
		);
	}

	// ── Cancel preview (GET) ─────────────────────────────────────────────────────

	public function cancel_preview( WP_REST_Request $request ): WP_REST_Response {
		global $wpdb;

		$ref   = sanitize_text_field( (string) $request->get_param( 'ref' ) );
		$token = sanitize_text_field( (string) $request->get_param( 'token' ) );

		$booking = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT b.*, f.name AS field_name
				   FROM {$wpdb->prefix}playard_bookings b
				   LEFT JOIN {$wpdb->prefix}playard_fields f ON f.id = b.field_id
				  WHERE b.booking_ref = %s LIMIT 1",
				$ref
			)
		);

		if ( ! $booking ) {
			return new WP_REST_Response(
				playard_rest_error( 'not_found', __( 'Booking not found.', 'playard' ) ),
				404
			);
		}

		$expected = wp_hash( $booking->booking_ref . $booking->customer_id );
		if ( ! hash_equals( $expected, $token ) ) {
			return new WP_REST_Response(
				playard_rest_error( 'invalid_token', __( 'Invalid cancellation link.', 'playard' ) ),
				403
			);
		}

		$settings = get_option( 'playard_settings', [] );
		$policy   = $settings['cancellation'] ?? [];
		$refund   = ( new Playard_Booking() )->is_refundable( $booking );

		return new WP_REST_Response(
			playard_rest_success( [
				'booking_ref'    => $booking->booking_ref,
				'booking_status' => $booking->booking_status,
				'field_name'     => $booking->field_name ?? '',
				'booking_date'   => $booking->booking_date,
				'start_time'     => $booking->start_time,
				'end_time'       => $booking->end_time,
				'sport'          => $booking->sport,
				'amount_paid'    => (float) $booking->amount_paid,
				'payment_mode'   => $booking->payment_mode,
				'cancellation_allowed' => ! empty( $policy['allowed'] ),
				'refund'         => $refund,
			] ),
			200
		);
	}

	// ── JWT auth helper ───────────────────────────────────────────────────────────

	// ── Pay Online — create intent ────────────────────────────────────────────────

	public function create_online_payment_intent( WP_REST_Request $request ): WP_REST_Response {
		$customer_id = $this->authenticate( $request );

		if ( ! $customer_id ) {
			return new WP_REST_Response(
				playard_rest_error( 'unauthorized', __( 'Authentication required.', 'playard' ) ),
				401
			);
		}

		$ref = sanitize_text_field( (string) $request->get_param( 'ref' ) );

		global $wpdb;
		$booking = $wpdb->get_row( $wpdb->prepare(
			"SELECT * FROM {$wpdb->prefix}playard_bookings WHERE booking_ref = %s AND customer_id = %d LIMIT 1",
			$ref, $customer_id
		) );

		if ( ! $booking ) {
			return new WP_REST_Response(
				playard_rest_error( 'not_found', __( 'Booking not found.', 'playard' ) ),
				404
			);
		}

		if ( (string) $booking->payment_status === 'paid' ) {
			return new WP_REST_Response(
				playard_rest_error( 'already_paid', __( 'This booking is already paid.', 'playard' ) ),
				400
			);
		}

		$result = $this->payment->create_booking_payment_intent( $booking );

		if ( is_wp_error( $result ) ) {
			return new WP_REST_Response(
				playard_rest_error( $result->get_error_code(), $result->get_error_message() ),
				422
			);
		}

		return new WP_REST_Response( playard_rest_success( $result ), 200 );
	}

	// ── Pay Online — confirm ──────────────────────────────────────────────────────

	public function confirm_online_payment( WP_REST_Request $request ): WP_REST_Response {
		$customer_id = $this->authenticate( $request );

		if ( ! $customer_id ) {
			return new WP_REST_Response(
				playard_rest_error( 'unauthorized', __( 'Authentication required.', 'playard' ) ),
				401
			);
		}

		$ref       = sanitize_text_field( (string) $request->get_param( 'ref' ) );
		$body      = (array) $request->get_json_params();
		$intent_id = sanitize_text_field( (string) ( $body['payment_intent_id'] ?? '' ) );

		if ( ! $intent_id ) {
			return new WP_REST_Response(
				playard_rest_error( 'validation_error', __( 'Payment intent ID is required.', 'playard' ) ),
				400
			);
		}

		global $wpdb;
		$booking = $wpdb->get_row( $wpdb->prepare(
			"SELECT * FROM {$wpdb->prefix}playard_bookings WHERE booking_ref = %s AND customer_id = %d LIMIT 1",
			$ref, $customer_id
		) );

		if ( ! $booking ) {
			return new WP_REST_Response(
				playard_rest_error( 'not_found', __( 'Booking not found.', 'playard' ) ),
				404
			);
		}

		if ( (string) $booking->payment_status === 'paid' ) {
			return new WP_REST_Response(
				playard_rest_success( [], __( 'Booking already confirmed.', 'playard' ) ),
				200
			);
		}

		if ( ! $this->payment->verify_stripe_intent( $intent_id ) ) {
			return new WP_REST_Response(
				playard_rest_error( 'payment_failed', __( 'Payment not verified. Please try again or contact support.', 'playard' ) ),
				400
			);
		}

		$amount_due  = round( (float) $booking->amount_due, 2 );
		$amount_paid = round( (float) $booking->amount_paid + $amount_due, 2 );

		$wpdb->update(
			$wpdb->prefix . 'playard_bookings',
			[
				'payment_status'    => 'paid',
				'payment_gateway'   => 'stripe',
				'payment_intent_id' => $intent_id,
				'payment_mode'      => 'stripe',
				'amount_paid'       => $amount_paid,
				'amount_due'        => 0.00,
				'booking_status'    => 'confirmed',
			],
			[ 'id' => (int) $booking->id ],
			[ '%s', '%s', '%s', '%s', '%f', '%f', '%s' ],
			[ '%d' ]
		);

		do_action( 'playard_payment_received', (int) $booking->id, $amount_due );

		return new WP_REST_Response(
			playard_rest_success( [], __( 'Payment confirmed. Your booking is now confirmed.', 'playard' ) ),
			200
		);
	}

	private function authenticate( WP_REST_Request $request ): int {
		$header = (string) $request->get_header( 'authorization' );

		if ( ! $header || ! str_starts_with( $header, 'Bearer ' ) ) {
			return 0;
		}

		return (int) $this->customer->validate_jwt( trim( substr( $header, 7 ) ) );
	}

	// ── Create pending booking ───────────────────────────────────────────────────

	public function create_pending( WP_REST_Request $request ): WP_REST_Response {
		$raw         = (array) $request->get_json_params();
		$customer_id = $this->authenticate( $request );

		if ( $customer_id > 0 ) {
			$raw['customer_id'] = $customer_id;
		}

		$result = $this->booking->create_pending_booking( $raw );

		if ( is_wp_error( $result ) ) {
			$data = $result->get_error_data();
			$body = playard_rest_error( $result->get_error_code(), $result->get_error_message() );
			if ( is_array( $data ) && ! empty( $data['available'] ) ) {
				$body['available'] = $data['available'];
			}
			return new WP_REST_Response( $body, 409 );
		}

		// Attach JWT so the confirmation screen can offer password setup.
		$cid = (int) ( $result['customer_id'] ?? 0 );
		unset( $result['customer_id'] );
		if ( $cid > 0 && $customer_id === 0 ) {
			$result['customer_token'] = $this->customer->generate_jwt( $cid );
		}

		// Signed token so a guest (no JWT) can later abandon this pending booking.
		$result['abandon_token'] = wp_hash( (string) ( $result['booking_ref'] ?? '' ) . (int) ( $result['booking_id'] ?? 0 ) );

		return new WP_REST_Response( playard_rest_success( $result ), 200 );
	}

	// ── Confirm after Stripe payment ─────────────────────────────────────────────

	public function confirm_payment( WP_REST_Request $request ): WP_REST_Response {
		$booking_ref = sanitize_text_field( (string) $request->get_param( 'booking_ref' ) );
		$intent_id   = sanitize_text_field( (string) $request->get_param( 'payment_intent_id' ) );
		$customer_id = $this->authenticate( $request );

		$result = $this->booking->confirm_payment( $booking_ref, $intent_id, $customer_id );

		if ( is_wp_error( $result ) ) {
			return new WP_REST_Response(
				playard_rest_error( $result->get_error_code(), $result->get_error_message() ),
				422
			);
		}

		$cid = (int) ( $result['customer_id'] ?? 0 );
		unset( $result['customer_id'] );
		if ( $cid > 0 ) {
			$result['customer_token'] = $this->customer->generate_jwt( $cid );
		}

		return new WP_REST_Response(
			playard_rest_success( $result, __( 'Booking confirmed.', 'playard' ) ),
			200
		);
	}

	// ── Abandon pending booking (customer clicked Back) ──────────────────────────

	public function abandon_booking( WP_REST_Request $request ): WP_REST_Response {
		$ref           = sanitize_text_field( (string) $request->get_param( 'ref' ) );
		$raw           = (array) $request->get_json_params();
		$phone         = sanitize_text_field( (string) ( $raw['phone'] ?? '' ) );
		$abandon_token = sanitize_text_field( (string) ( $request->get_param( 'abandon_token' ) ?? '' ) );

		global $wpdb;
		$booking = $wpdb->get_row( $wpdb->prepare(
			"SELECT id, customer_id, booking_ref FROM {$wpdb->prefix}playard_bookings WHERE booking_ref = %s LIMIT 1",
			$ref
		) );

		if ( ! $booking ) {
			return new WP_REST_Response(
				playard_rest_error( 'not_found', __( 'Booking not found.', 'playard' ) ),
				404
			);
		}

		// Verify ownership: JWT (Bearer) proves the account, or the signed abandon_token
		// returned by create-pending. Either path skips the legacy phone check.
		$customer_id = $this->authenticate( $request );
		if ( $customer_id > 0 ) {
			if ( (int) $booking->customer_id !== $customer_id ) {
				return new WP_REST_Response(
					playard_rest_error( 'forbidden', __( 'Not your booking.', 'playard' ) ),
					403
				);
			}
			$phone = '';
		} elseif ( '' !== $abandon_token ) {
			$expected = wp_hash( $booking->booking_ref . $booking->id );
			if ( ! hash_equals( $expected, $abandon_token ) ) {
				return new WP_REST_Response(
					playard_rest_error( 'forbidden', __( 'Invalid token.', 'playard' ) ),
					403
				);
			}
			$phone = '';
		} else {
			return new WP_REST_Response(
				playard_rest_error( 'unauthorized', __( 'Authentication required.', 'playard' ) ),
				401
			);
		}

		$result = $this->booking->abandon_booking( $ref, $phone );

		if ( is_wp_error( $result ) ) {
			$code = $result->get_error_code() === 'not_found' ? 404 : 422;
			return new WP_REST_Response(
				playard_rest_error( $result->get_error_code(), $result->get_error_message() ),
				$code
			);
		}

		return new WP_REST_Response( playard_rest_success( [], __( 'Booking abandoned.', 'playard' ) ), 200 );
	}

	// ── Cash / pay-on-arrival booking ────────────────────────────────────────────

	public function cash( WP_REST_Request $request ): WP_REST_Response {
		$data        = (array) $request->get_json_params();
		$customer_id = $this->authenticate( $request );

		// If the customer is logged in, pin the booking to their account directly.
		if ( $customer_id > 0 ) {
			$data['customer_id'] = $customer_id;
		}

		$result = $this->booking->create_cash_booking( $data );

		if ( is_wp_error( $result ) ) {
			return new WP_REST_Response(
				playard_rest_error( $result->get_error_code(), $result->get_error_message() ),
				422
			);
		}

		// Attach a short-lived JWT so the confirmation page can offer password setup.
		$customer_id = (int) ( $result['customer_id'] ?? 0 );
		unset( $result['customer_id'] );

		if ( $customer_id > 0 ) {
			$result['customer_token'] = $this->customer->generate_jwt( $customer_id );
		}

		return new WP_REST_Response(
			playard_rest_success( $result, __( 'Booking reserved. Pay on arrival.', 'playard' ) ),
			200
		);
	}
}
