<?php
/**
 * REST ingestion endpoint for lightweight traffic batches.
 *
 * @package Niwa_AI
 */

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

class Niwa_AI_Traffic_REST_Controller {

	const REST_NAMESPACE = 'niwa-ai/v1';
	const REST_ROUTE     = '/traffic/events';
	const RATE_LIMIT_MAX = 120;
	const RATE_LIMIT_WINDOW = MINUTE_IN_SECONDS;
	const MAX_PAYLOAD_BYTES = 65536;
	const GLOBAL_RATE_LIMIT_MAX = 2000;

	public function __construct(
		private readonly Niwa_AI_Traffic_Repository $repository
	) {}

	public function register_hooks(): void {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
	}

	public function register_routes(): void {
		if ( ! function_exists( 'register_rest_route' ) ) {
			return;
		}

		register_rest_route(
			self::REST_NAMESPACE,
			self::REST_ROUTE,
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_events' ),
				'permission_callback' => '__return_true',
			)
		);
	}

	public function handle_events( $request ) {
		if ( ! apply_filters( 'niwa_ai_traffic_collection_enabled', true ) ) {
			return rest_ensure_response( array( 'tracked' => 0, 'disabled' => true ) );
		}
		if ( $this->payload_is_too_large( $request ) ) {
			return new WP_Error( 'niwa_ai_traffic_payload_too_large', 'Traffic ingestion payload is too large.', array( 'status' => 413 ) );
		}

		$verified_session = Niwa_AI_Session_Utils::resolve_verified_session( true );
		if ( '' === $verified_session || ! $this->consume_rate_limit( $verified_session ) ) {
			return new WP_Error( 'niwa_ai_traffic_rate_limited', 'Too many traffic ingestion requests.', array( 'status' => 429 ) );
		}

		$payload = $this->get_payload( $request );
		$events  = array();
		if ( isset( $payload['events'] ) && is_array( $payload['events'] ) ) {
			$events = $payload['events'];
		} elseif ( ! empty( $payload['event_type'] ) ) {
			$events = array( $payload );
		}

		if ( empty( $events ) ) {
			return rest_ensure_response( array( 'tracked' => 0 ) );
		}

		$events = array_slice( $events, 0, 20 );
		foreach ( $events as &$event ) {
			if ( ! is_array( $event ) ) {
				continue;
			}
			$public_session     = isset( $event['session_id'] ) ? (string) $event['session_id'] : 'session';
			$public_visitor     = isset( $event['visitor_id'] ) ? (string) $event['visitor_id'] : $public_session;
			$event['session_id'] = Niwa_AI_Session_Utils::bind_public_id( 'traffic', $public_session, $verified_session );
			$event['visitor_id'] = Niwa_AI_Session_Utils::bind_public_id( 'visitor', $public_visitor, $verified_session );
		}
		unset( $event );

		$count = $this->repository->record_events( $events, $_SERVER );

		return rest_ensure_response(
			array(
				'tracked' => $count,
			)
		);
	}

	private function get_payload( $request ): array {
		$json = is_object( $request ) && method_exists( $request, 'get_json_params' ) ? $request->get_json_params() : null;
		if ( is_array( $json ) && ! empty( $json ) ) {
			return $json;
		}

		$params = is_object( $request ) && method_exists( $request, 'get_body_params' ) ? $request->get_body_params() : null;
		return is_array( $params ) ? $params : array();
	}

	private function payload_is_too_large( $request ): bool {
		$content_length = isset( $_SERVER['CONTENT_LENGTH'] ) ? (int) $_SERVER['CONTENT_LENGTH'] : 0;
		$body           = is_object( $request ) && method_exists( $request, 'get_body' ) ? (string) $request->get_body() : '';
		return $content_length > self::MAX_PAYLOAD_BYTES || strlen( $body ) > self::MAX_PAYLOAD_BYTES;
	}

	private function consume_rate_limit( $session ): bool {
		$ip      = Niwa_AI_Session_Utils::get_client_ip();
		$limits  = array(
			array( 'traffic_global_minute', 'global', (int) apply_filters( 'niwa_ai_traffic_global_per_minute', self::GLOBAL_RATE_LIMIT_MAX ) ),
			array( 'traffic_ip_minute', $ip, (int) apply_filters( 'niwa_ai_traffic_ip_per_minute', self::RATE_LIMIT_MAX ) ),
			array( 'traffic_session_minute', $session, (int) apply_filters( 'niwa_ai_traffic_session_per_minute', self::RATE_LIMIT_MAX ) ),
		);
		foreach ( $limits as $limit ) {
			if ( ! Niwa_AI_Session_Utils::consume_fixed_window_limit( $limit[0], $limit[1], max( 1, $limit[2] ), self::RATE_LIMIT_WINDOW ) ) {
				return false;
			}
		}
		return true;
	}
}
