<?php
/**
 * REST Proxy Endpoint — /wp-json/capiflow/v1/track
 *
 * First-party proxy for browser events (Safari ITP bypass).
 * Fire-and-forget for PageView, ViewContent, Search, AddToCart.
 * Queue-based for InitiateCheckout, AddPaymentInfo.
 * Records events to analytics table via Analytics_Tracker.
 * Cache-safe anti-cache headers + cookie management.
 * Nonce-free (sendBeacon limitation) with origin + IP rate limit protection.
 *
 * @package CapiFlow
 */

namespace CapiFlow\Core;

defined( 'ABSPATH' ) || exit;

class Proxy_Endpoint {

	/**
	 * Register hooks.
	 */
	public function register_hooks(): void {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	/**
	 * Register REST routes.
	 */
	public function register_routes(): void {
		register_rest_route(
			'capiflow/v1',
			'/track',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_track' ),
				'permission_callback' => array( $this, 'allow_public_access' ),
			)
		);

		// Allow Pro to register alias routes at randomized paths.
		// Pro hooks here to add masked endpoints (e.g., /insights-lib/collect)
		// that delegate to the same handle_track() callback above.
		do_action( 'capiflow_register_proxy_routes', array(
			'callback'            => array( $this, 'handle_track' ),
			'permission_callback' => array( $this, 'allow_public_access' ),
		) );
	}

	/**
	 * Permission callback for the public tracking endpoint.
	 *
	 * This endpoint is intentionally public because browser sendBeacon()
	 * cannot attach WordPress nonces. Security is enforced in handle_track()
	 * via Origin/Referer validation (3-tier) and per-IP rate limiting.
	 *
	 * @return true Always returns true — access control is in the handler.
	 */
	public function allow_public_access(): bool {
		return true;
	}

	/**
	 * Handle incoming browser tracking request.
	 *
	 * @param \WP_REST_Request $request REST request.
	 * @return \WP_REST_Response
	 */
	public function handle_track( \WP_REST_Request $request ): \WP_REST_Response {
		// Prevent caching by CDN/proxy layers.
		nocache_headers();

		// Origin validation (3-tier: Origin header → Referer → pass).
		$origin_status = $this->validate_origin();
		
		if ( 'blocked' === $origin_status ) {
			return new \WP_REST_Response( array( 'status' => 'blocked', 'reason' => 'origin' ), 403 );
		}

		// Smart IP-based rate limiting (prevents API exhaustion & handles NAT).
		// sendBeacon can't send nonces, so IP throttling is our primary abuse defense.
		$client_ip = Data_Normalizer::get_real_client_ip();
		$ip_key    = 'capiflow_ip_' . md5( $client_ip );
		$ip_count  = (int) get_transient( $ip_key );

		// Trusted (has Origin/Referer): High limit for CGNAT, Corporate WiFi, Mobile Networks.
		// Suspicious (no Origin/Referer): Low limit to block bots/scripted attacks.
		// Both configurable from Settings → Performance.
		$trusted_limit    = (int) get_option( 'capiflow_ip_trusted_limit', 300 );
		$suspicious_limit = (int) get_option( 'capiflow_ip_suspicious_limit', 30 );
		$max_requests     = ( 'trusted' === $origin_status ) ? $trusted_limit : $suspicious_limit;

		if ( $ip_count >= $max_requests ) {
			return new \WP_REST_Response( array( 'status' => 'blocked', 'reason' => 'rate_limit' ), 429 );
		}
		set_transient( $ip_key, $ip_count + 1, MINUTE_IN_SECONDS );

		// Parse request body.
		$data = $request->get_json_params();
		if ( empty( $data ) ) {
			$data = json_decode( $request->get_body(), true );
		}

		if ( empty( $data ) || ! is_array( $data ) ) {
			return new \WP_REST_Response( array( 'status' => 'error', 'message' => 'Invalid payload' ), 400 );
		}

		// Validate required fields.
		if ( empty( $data['event_name'] ) || empty( $data['event_id'] ) ) {
			return new \WP_REST_Response( array( 'status' => 'error', 'message' => 'Missing event_name or event_id' ), 400 );
		}

		// Validate event_id format (prevents injection).
		if ( ! Event_ID_Generator::validate( $data['event_id'] ) ) {
			return new \WP_REST_Response( array( 'status' => 'error', 'message' => 'Invalid event_id format' ), 400 );
		}

		// ── Consent enforcement gate ─────────────────────────────────────────
		// Option A: toggle OFF = always allow. Toggle ON + no CMP = allow (default true).
		if ( get_option( 'capiflow_consent_enforcement', false ) ) {
			if ( ! apply_filters( 'capiflow_has_tracking_consent', true ) ) {
				return new \WP_REST_Response(
					array( 'status' => 'blocked', 'reason' => 'consent_required' ),
					200
				);
			}
		}

		// Sanitize + truncate to VARCHAR(50).
		$event_name = substr( sanitize_text_field( $data['event_name'] ), 0, 50 );

		// Check if event type is enabled in settings.
		if ( ! $this->is_event_enabled( $event_name ) ) {
			return new \WP_REST_Response( array( 'status' => 'skipped', 'message' => 'Event disabled' ), 200 );
		}

		// Build CAPI payload.
		$builder = new Event_Builder();
		$payload = $builder->build_browser_event( $data );

		if ( empty( $payload ) ) {
			return new \WP_REST_Response( array( 'status' => 'error', 'message' => 'Payload build failed' ), 400 );
		}

		// Determine routing: fire-and-forget vs queue.
		$fire_and_forget_events = array( 'PageView', 'ViewContent', 'Search', 'AddToCart' );
		$pixel_id               = $this->resolve_pixel_id( $data );

		if ( in_array( $event_name, $fire_and_forget_events, true ) ) {
			// Fire-and-forget — no DB storage, direct send.
			$sender   = new CAPI_Sender();
			$result   = $sender->send_fire_and_forget( $payload, $pixel_id );

			// Record event in analytics (source = 'both' — pixel already fired).
			Analytics_Tracker::record( $event_name, 'both', 0.0, 0.0 );

			$response = new \WP_REST_Response(
				array(
					'status'    => $result['success'] ? 'sent' : 'error',
					'event_id'  => $data['event_id'],
					'http_code' => $result['http_code'],
				),
				200
			);
		} else {
			// Queue-based — InitiateCheckout, AddPaymentInfo.
			$loader = capiflow();
			if ( $loader->queue ) {
				$loader->queue->enqueue(
					$event_name,
					$data['event_id'],
					time(),
					$pixel_id,
					0,
					$payload
				);

				Analytics_Tracker::record( $event_name, 'both', 0.0, 0.0 );

				$response = new \WP_REST_Response(
					array(
						'status'   => 'queued',
						'event_id' => $data['event_id'],
					),
					200
				);
			} else {
				$sender = new CAPI_Sender();
				$result = $sender->send_fire_and_forget( $payload, $pixel_id );

				Analytics_Tracker::record( $event_name, 'both', 0.0, 0.0 );

				$response = new \WP_REST_Response(
					array(
						'status'    => $result['success'] ? 'sent' : 'error',
						'event_id'  => $data['event_id'],
						'http_code' => $result['http_code'],
					),
					200
				);
			}
		}

		// Set tracking cookies via response headers (cache-safe — not in PHP session).
		$response = Cookie_Manager::set_tracking_cookies( $response );

		return $response;
	}

	/**
	 * Validate request origin (3-tier security).
	 *
	 * Returns trust level for smart rate limiting:
	 *   'trusted'    — Origin/Referer matches site (real browser).
	 *   'suspicious' — No Origin/Referer (bot/script — stricter limit).
	 *   'blocked'    — Origin/Referer present but mismatched (cross-site abuse).
	 *
	 * @return string 'trusted', 'suspicious', or 'blocked'.
	 */
	private function validate_origin(): string {
		$site_host = wp_parse_url( home_url(), PHP_URL_HOST );

		// Tier 1: Check Origin header.
		if ( ! empty( $_SERVER['HTTP_ORIGIN'] ) ) {
			$origin_host = wp_parse_url(
				sanitize_url( wp_unslash( $_SERVER['HTTP_ORIGIN'] ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
				PHP_URL_HOST
			);
			return ( $origin_host === $site_host ) ? 'trusted' : 'blocked';
		}

		// Tier 2: Check Referer header.
		if ( ! empty( $_SERVER['HTTP_REFERER'] ) ) {
			$referer_host = wp_parse_url(
				sanitize_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ), // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
				PHP_URL_HOST
			);
			return ( $referer_host === $site_host ) ? 'trusted' : 'blocked';
		}

		// Tier 3: No origin — allow with stricter rate limit (IP throttling in handle_track).
		return 'suspicious';
	}



	/**
	 * Check if an event type is enabled in settings.
	 *
	 * @param string $event_name Event name.
	 * @return bool
	 */
	private function is_event_enabled( string $event_name ): bool {
		$map = array(
			'PageView'         => 'capiflow_send_pageview',
			'ViewContent'      => 'capiflow_send_viewcontent',
			'Search'           => 'capiflow_send_search',
			'AddToCart'        => 'capiflow_send_addtocart',
			'InitiateCheckout' => 'capiflow_send_initiatecheckout',
			'AddPaymentInfo'   => 'capiflow_send_addpaymentinfo',
		);

		if ( isset( $map[ $event_name ] ) ) {
			return (bool) get_option( $map[ $event_name ], true );
		}

		return true; // Unknown events pass through (custom events).
	}

	/**
	 * Resolve pixel ID.
	 *
	 * Falls back to global pixel if client doesn't send one.
	 *
	 * @param array $data Client request data.
	 * @return string Pixel ID.
	 */
	private function resolve_pixel_id( array $data ): string {
		if ( ! empty( $data['pixel_id'] ) ) {
			return sanitize_text_field( $data['pixel_id'] );
		}

		return get_option( 'capiflow_pixel_id', '' );
	}
}
