<?php
/**
 * File Path: includes/class-mbs-review-handler.php
 *
 * Story 9-7: Reader Review Handler — GDPR-aware submission
 *
 * Handles public-facing review submission (both logged-in and guest), stores
 * reviews as WordPress comments of comment_type 'mbs_review' with status
 * pending (0) for moderation, and recalculates the cached average/count
 * postmeta whenever a review comment changes approval state.
 *
 * AJAX actions:
 *   wp_ajax_mbs_submit_review          (logged-in users)
 *   wp_ajax_nopriv_mbs_submit_review   (guests)
 *
 * Postmeta written on the book post:
 *   mbs_review_average  — float, average approved rating
 *   mbs_review_count    — int, count of approved reviews
 *
 * Comment meta written per review:
 *   mbs_review_rating            — int 1–5
 *   mbs_review_title             — string, sanitized
 *   mbs_review_consent_timestamp — ISO-8601 UTC string, e.g. "2026-04-23T14:22:00Z"
 *
 * @package MyBookShowroom
 * @since   1.0.0
 */

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

/**
 * Class MBS_Review_Handler
 */
class MBS_Review_Handler {

	public function __construct() {
		add_action( 'wp_ajax_mbs_submit_review',        array( $this, 'handle_submit' ) );
		add_action( 'wp_ajax_nopriv_mbs_submit_review', array( $this, 'handle_submit' ) );

		// Recalculate cached average/count when a review comment changes approval state.
		add_action( 'transition_comment_status', array( $this, 'on_status_change' ), 10, 3 );
	}

	/**
	 * AJAX handler — validate and insert a pending review comment.
	 *
	 * Expected POST keys (all required unless noted):
	 *   nonce       — wp_nonce value for 'mbs_submit_review'
	 *   post_id     — int, the book post ID
	 *   author_name — string
	 *   author_email— string (email)
	 *   rating      — int 1–5
	 *   title       — string, optional
	 *   body        — string (the review text)
	 *   consent     — must equal '1'
	 *
	 * @return void Sends JSON and exits.
	 */
	public function handle_submit(): void {
		// Security first — nonce must be valid before anything else is read.
		check_ajax_referer( 'mbs_submit_review', 'nonce' );

		// Rate limit: max 3 review submissions per IP per hour (V2-C-014).
		$client_ip  = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
		$rate_key   = 'mbs_review_rl_' . get_current_blog_id() . '_' . md5( $client_ip );
		$rate_count = (int) get_transient( $rate_key );
		if ( $rate_count >= 3 ) {
			wp_send_json_error( array( 'message' => __( 'Too many review submissions. Please wait before submitting again.', 'my-book-showroom' ) ), 429 );
			return;
		}
		set_transient( $rate_key, $rate_count + 1, HOUR_IN_SECONDS );

		// Plugin must be active (license registered) before reviews can be stored.
		if ( ! mbs_is_registered() ) {
			wp_send_json_error( array( 'message' => __( 'Reviews are not available.', 'my-book-showroom' ) ), 403 );
			return;
		}

		// ── Input sanitization ─────────────────────────────────────────────────

		$post_id      = absint( $_POST['post_id'] ?? 0 );
		$author_name  = sanitize_text_field( wp_unslash( $_POST['author_name']  ?? '' ) );
		$author_email = sanitize_email( wp_unslash( $_POST['author_email'] ?? '' ) );
		$rating       = absint( $_POST['rating'] ?? 0 );
		$title        = sanitize_text_field( wp_unslash( $_POST['title'] ?? '' ) );
		$body         = sanitize_textarea_field( wp_unslash( $_POST['body'] ?? '' ) );
		$consent      = sanitize_text_field( wp_unslash( $_POST['consent'] ?? '' ) );

		// ── Validation ─────────────────────────────────────────────────────────

		if ( ! $post_id || get_post_type( $post_id ) !== MBS_POST_TYPE ) {
			wp_send_json_error( array( 'message' => __( 'Invalid book.', 'my-book-showroom' ) ), 400 );
			return; // V8-D-016
		}

		if ( empty( $author_name ) ) {
			wp_send_json_error( array( 'message' => __( 'Please enter your name.', 'my-book-showroom' ) ), 400 );
			return; // V8-D-016
		}

		if ( empty( $author_email ) || ! is_email( $author_email ) ) {
			wp_send_json_error( array( 'message' => __( 'Please enter a valid email address.', 'my-book-showroom' ) ), 400 );
			return; // V8-D-016
		}

		if ( $rating < 1 || $rating > 5 ) {
			wp_send_json_error( array( 'message' => __( 'Please select a star rating.', 'my-book-showroom' ) ), 400 );
			return; // V8-D-016
		}

		if ( empty( $body ) ) {
			wp_send_json_error( array( 'message' => __( 'Please write your review.', 'my-book-showroom' ) ), 400 );
			return; // V8-D-016
		}

		// GDPR consent is mandatory.
		if ( '1' !== $consent ) {
			wp_send_json_error( array( 'message' => __( 'You must consent to data processing to submit a review.', 'my-book-showroom' ) ), 400 );
			return; // V8-D-016
		}

		// ── Duplicate guard — one pending/approved review per email per book ───

		$existing = get_comments( array(
			'post_id'      => $post_id,
			'author_email' => $author_email,
			'type'         => 'mbs_review',
			'status'       => 'all',
			'count'        => true,
			'number'       => 1,
		) );
		if ( (int) $existing > 0 ) {
			wp_send_json_error( array( 'message' => __( 'You have already submitted a review for this book.', 'my-book-showroom' ) ), 409 );
			return; // V8-D-016
		}

		// ── Insert comment ─────────────────────────────────────────────────────

		$comment_data = array(
			'comment_post_ID'      => $post_id,
			'comment_author'       => $author_name,
			'comment_author_email' => $author_email,
			'comment_content'      => $body,
			'comment_type'         => 'mbs_review',
			'comment_approved'     => 0, // pending moderation
		);

		// Attach logged-in user ID when available.
		$current_user_id = get_current_user_id();
		if ( $current_user_id ) {
			$comment_data['user_id'] = $current_user_id;
		}

		$comment_id = wp_insert_comment( $comment_data );

		if ( ! $comment_id || is_wp_error( $comment_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Unable to save your review. Please try again.', 'my-book-showroom' ) ), 500 );
			return; // V8-D-016
		}

		// ── Store comment meta ─────────────────────────────────────────────────

		update_comment_meta( $comment_id, 'mbs_review_rating', $rating );
		update_comment_meta( $comment_id, 'mbs_review_title', $title );
		// ISO-8601 UTC timestamp records exactly when consent was given.
		update_comment_meta( $comment_id, 'mbs_review_consent_timestamp', gmdate( 'Y-m-d\TH:i:s\Z' ) );

		wp_send_json_success( array(
			'message' => __( 'Thank you! Your review has been submitted and is awaiting moderation.', 'my-book-showroom' ),
		) );
	}

	/**
	 * Fires on transition_comment_status.
	 *
	 * Recalculates mbs_review_average and mbs_review_count on the book post
	 * whenever any mbs_review comment is approved or unapproved, so the cached
	 * values stay in sync with moderation actions.
	 *
	 * @param string     $new_status New comment status.
	 * @param string     $old_status Previous comment status.
	 * @param WP_Comment $comment    The comment object.
	 * @return void
	 */
	public function on_status_change( string $new_status, string $old_status, WP_Comment $comment ): void {
		if ( 'mbs_review' !== $comment->comment_type ) {
			return;
		}

		// Only recalculate when moving into or out of 'approved'.
		if ( $new_status === $old_status ) {
			return;
		}
		if ( 'approved' !== $new_status && 'approved' !== $old_status ) {
			return;
		}

		$this->recalculate_rating( (int) $comment->comment_post_ID );
	}

	/**
	 * Recalculate and persist mbs_review_average + mbs_review_count for $post_id.
	 *
	 * Reads only approved mbs_review comments. Stores 0 / 0 when no approved
	 * reviews exist so the front-end knows to suppress the summary block.
	 *
	 * @param int $post_id Book post ID.
	 * @return void
	 */
	public function recalculate_rating( int $post_id ): void {
		if ( $post_id <= 0 ) {
			return;
		}

		// V8-B-017: Prevent concurrent moderation from computing stale averages.
		$_lock_key = 'mbs_rating_lock_' . $post_id;
		if ( get_transient( $_lock_key ) ) {
			return; // Another process is recalculating — skip this call.
		}
		set_transient( $_lock_key, 1, 5 ); // 5-second lock

		$approved_reviews = get_comments( array(
			'post_id' => $post_id,
			'type'    => 'mbs_review',
			'status'  => 'approve',
			'number'  => 0, // fetch all
		) );

		$count = count( $approved_reviews );
		$total = 0;

		foreach ( $approved_reviews as $review ) {
			$rating = (int) get_comment_meta( $review->comment_ID, 'mbs_review_rating', true );
			if ( $rating >= 1 && $rating <= 5 ) {
				$total += $rating;
			}
		}

		$average = $count > 0 ? round( $total / $count, 2 ) : 0.0;

		// Persist to mbs_book_stats (indexed table) -- replaces postmeta writes.
		MBS_Book_Stats_DAO::upsert_stats( $post_id, $average, $count );

		delete_transient( $_lock_key );
	}
}
