<?php
namespace Pay4All\Pro\Frontend;

use Pay4All\Pro\Domain\Ticket;

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

/**
 * Public URL for the QR-code ticket validation page:
 *
 *   /p4all-ticket/{token}                 — short URL (18–40 hex chars)
 *   /p4all-ticket/{product-slug}/{token}  — legacy URL kept for old QR codes
 *
 * The gatekeeper at the venue door scans a printed ticket with their phone
 * camera; the QR resolves to one of the URLs above and lands on a standalone
 * HTML page (no theme wrapper) that shows:
 *
 *   - « Billet valide » with a « Valider la place » CTA if never scanned
 *   - « Billet validé » + OK panel right after the CTA is clicked
 *   - « Billet déjà utilisé » + previous-scan timestamp on subsequent visits
 *   - « Billet introuvable » (404) if the token doesn't resolve
 *
 * The response is deliberately built with echo + a small custom stylesheet
 * so we can guarantee sub-second first paint on a spotty 4G connection at
 * the door and stay usable even when the theme's front-end assets fail to
 * load. Assets are versioned via P4ALL_PRO_VERSION.
 */
final class TicketEndpoint {

	/** Bumped whenever `add_rewrite_rules()` gains or changes a rule. */
	public const REWRITE_VERSION = 1;

	public static function register() : void {
		add_action( 'init',              [ self::class, 'add_rewrite_rules' ] );
		add_action( 'init',              [ self::class, 'maybe_flush_rewrite_rules' ], 20 );
		add_filter( 'query_vars',        [ self::class, 'add_query_vars' ] );
		add_action( 'template_redirect', [ self::class, 'maybe_handle_ticket' ] );
		add_action( 'admin_post_nopriv_p4all_validate_ticket', [ self::class, 'handle_validation_post' ] );
		add_action( 'admin_post_p4all_validate_ticket',        [ self::class, 'handle_validation_post' ] );
	}

	/**
	 * Re-flushes rewrite rules when a Pro update introduces or changes a
	 * ticket rule — covers installations where the Pro activation hook
	 * already ran on an older version and never runs again. Cheap
	 * option_get + int compare on every subsequent request.
	 */
	public static function maybe_flush_rewrite_rules() : void {
		$current = (int) get_option( 'p4all_pro_rewrite_v', 0 );
		if ( $current >= self::REWRITE_VERSION ) { return; }
		flush_rewrite_rules( false );
		update_option( 'p4all_pro_rewrite_v', self::REWRITE_VERSION );
	}

	public static function add_rewrite_rules() : void {
		add_rewrite_rule(
			'^p4all-ticket/([a-f0-9]{18,40})/?$',
			'index.php?p4all_ticket_token=$matches[1]',
			'top'
		);
		add_rewrite_rule(
			'^p4all-ticket/([^/]+)/([a-f0-9]{40})/?$',
			'index.php?p4all_ticket_slug=$matches[1]&p4all_ticket_token=$matches[2]',
			'top'
		);
	}

	public static function add_query_vars( array $vars ) : array {
		$vars[] = 'p4all_ticket_slug';
		$vars[] = 'p4all_ticket_token';
		return $vars;
	}

	public static function maybe_handle_ticket() : void {
		$token = get_query_var( 'p4all_ticket_token' );
		if ( ! is_string( $token ) || $token === '' ) { return; }
		self::render_ticket_page( $token );
		exit;
	}

	private static function render_ticket_page( string $token ) : void {
		$row = Ticket::find_by_token( $token );
		status_header( $row ? 200 : 404 );
		nocache_headers();
		header( 'Content-Type: text/html; charset=utf-8' );

		$title    = __( 'Validation du billet', 'pay4all-pro' );
		$lang     = get_bloginfo( 'language' );
		$css_href = P4ALL_PRO_URL . 'assets/public/css/ticket.css?v=' . P4ALL_PRO_VERSION;
		$js_href  = P4ALL_PRO_URL . 'assets/public/js/ticket.js?v=' . P4ALL_PRO_VERSION;
		echo '<!doctype html><html lang="' . esc_attr( $lang ) . '"><head><meta charset="utf-8"><title>' . esc_html( $title ) . '</title>';
		echo '<meta name="viewport" content="width=device-width,initial-scale=1">';
		// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- standalone HTML response outside the WP render pipeline; wp_enqueue_style would never reach wp_head() here.
		echo '<link rel="stylesheet" href="' . esc_url( $css_href ) . '">';
		// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript -- same reason as the stylesheet above.
		echo '<script src="' . esc_url( $js_href ) . '" defer></script>';
		echo '</head><body><div class="card">';

		if ( ! $row ) {
			echo '<h1>' . esc_html__( 'Billet introuvable', 'pay4all-pro' ) . '</h1>';
			echo '<p class="muted">' . esc_html__( 'Ce billet n\'existe pas ou son lien est invalide.', 'pay4all-pro' ) . '</p>';
		} elseif ( ! empty( $row['validated_at'] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only UI flag set by our own post-validate redirect.
			$just_validated = isset( $_GET['validated'] ) && sanitize_text_field( wp_unslash( (string) $_GET['validated'] ) ) === '1';
			if ( $just_validated ) {
				echo '<h1>' . esc_html__( 'Billet validé', 'pay4all-pro' ) . '</h1>';
				$prod = get_post( (int) $row['product_id'] );
				if ( $prod ) {
					echo '<p class="muted"><strong>' . esc_html( get_the_title( $prod ) ) . '</strong></p>';
				}
				echo '<p class="ok ok-big">' . esc_html__( 'OK', 'pay4all-pro' ) . '</p>';
			} else {
				echo '<h1>' . esc_html__( 'Billet déjà utilisé', 'pay4all-pro' ) . '</h1>';
				echo '<p class="bad">' . sprintf(
					/* translators: %s = date and time of the previous scan */
					esc_html__( 'Ce billet a été validé le %s.', 'pay4all-pro' ),
					esc_html( mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $row['validated_at'] ) )
				) . '</p>';
			}
			self::render_scan_next_button();
		} else {
			echo '<h1>' . esc_html__( 'Billet valide', 'pay4all-pro' ) . '</h1>';
			$prod = get_post( (int) $row['product_id'] );
			if ( $prod ) {
				echo '<p class="muted"><strong>' . esc_html( get_the_title( $prod ) ) . '</strong></p>';
				// The short-description helper (with its ALLOWED_SHORT_DESC_HTML
				// allow-list) still ships in the free plugin — reuse it rather
				// than duplicate the sanitisation table.
				if ( class_exists( '\\Pay4All\\Form\\Domain\\Product' ) ) {
					$desc = \Pay4All\Form\Domain\Product::short_desc( (int) $prod->ID );
					if ( $desc !== '' ) {
						echo '<div class="ticket-desc">' . wp_kses( $desc, \Pay4All\Form\Domain\Product::ALLOWED_SHORT_DESC_HTML ) . '</div>';
					}
				}
			}
			echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
			echo '<input type="hidden" name="action" value="p4all_validate_ticket">';
			echo '<input type="hidden" name="token" value="' . esc_attr( (string) $row['token'] ) . '">';
			wp_nonce_field( 'p4all_validate_' . $row['token'], '_p4all_nonce' );
			echo '<p style="margin-top:18px;"><button type="submit" class="btn">' . esc_html__( 'Valider la place', 'pay4all-pro' ) . '</button></p>';
			echo '</form>';
		}
		echo '</div></body></html>';
	}

	/**
	 * « Scan next » button shown after a successful validation so the
	 * gatekeeper at the venue door can chain validations without leaving
	 * Safari. Wraps a `<input type="file" capture="environment">` that
	 * triggers the smartphone camera; ticket.js then uses the native
	 * BarcodeDetector API to decode the captured photo and redirect to
	 * the next ticket URL. Falls back to an explanatory alert on browsers
	 * without BarcodeDetector support.
	 */
	private static function render_scan_next_button() : void {
		echo '<button type="button" id="p4all-next-scan" class="btn-secondary"';
		echo ' data-msg-unsupported="' . esc_attr__( "Votre navigateur ne sait pas scanner les QR codes en direct. Utilisez l'appareil photo de votre téléphone pour scanner le prochain billet.", 'pay4all-pro' ) . '"';
		echo ' data-msg-permission="' . esc_attr__( "L'accès à la caméra a été refusé. Autorisez l'accès dans les réglages de votre navigateur.", 'pay4all-pro' ) . '"';
		echo ' data-msg-error="' . esc_attr__( 'Impossible de démarrer la caméra.', 'pay4all-pro' ) . '"';
		echo ' data-msg-hint="' . esc_attr__( 'Centrez le QR code du billet suivant dans le cadre.', 'pay4all-pro' ) . '"';
		echo ' data-msg-close="' . esc_attr__( 'Fermer le scanner', 'pay4all-pro' ) . '"';
		echo ' data-msg-loading="' . esc_attr__( 'Chargement du billet…', 'pay4all-pro' ) . '">';
		echo esc_html__( 'Valider une autre place', 'pay4all-pro' );
		echo '</button>';
	}

	public static function handle_validation_post() : void {
		$token = isset( $_POST['token'] ) ? sanitize_key( wp_unslash( (string) $_POST['token'] ) ) : '';
		$nonce = isset( $_POST['_p4all_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['_p4all_nonce'] ) ) : '';
		if ( $token === '' || ! wp_verify_nonce( $nonce, 'p4all_validate_' . $token ) ) {
			wp_die( esc_html__( 'Lien invalide.', 'pay4all-pro' ), '', [ 'response' => 403 ] );
		}
		$row = Ticket::find_by_token( $token );
		if ( ! $row ) {
			wp_die( esc_html__( 'Billet introuvable.', 'pay4all-pro' ), '', [ 'response' => 404 ] );
		}
		if ( empty( $row['validated_at'] ) ) {
			Ticket::mark_validated( (int) $row['id'], self::client_ip() );
		}
		// `?validated=1` lets render_ticket_page() distinguish « just validated
		// by me in the previous click » from « already validated some time ago »
		// — the first deserves a friendly OK panel, the second the alarming
		// « Billet déjà utilisé » warning.
		wp_safe_redirect( add_query_arg( 'validated', '1', Ticket::validation_url( $row ) ) );
		exit;
	}

	private static function client_ip() : string {
		foreach ( [ 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' ] as $k ) {
			if ( ! empty( $_SERVER[ $k ] ) ) {
				$v = sanitize_text_field( wp_unslash( (string) $_SERVER[ $k ] ) );
				if ( strpos( $v, ',' ) !== false ) { $v = trim( explode( ',', $v )[0] ); }
				if ( filter_var( $v, FILTER_VALIDATE_IP ) ) { return $v; }
			}
		}
		return '';
	}
}
