<?php
/**
 * AjaxTracker
 *
 * Registers wp_ajax handlers for click and impression tracking.
 * Accepts requests from both authenticated and guest users (visitors).
 * Nonce-gated on every request.
 *
 * @package Relatedly\Tracking
 * @since   2.0.0
 */

namespace Relatedly\Tracking;

use Relatedly\Core\Options;

defined( 'ABSPATH' ) || exit;

/**
 * Handles AJAX tracking requests for clicks and impressions.
 *
 * @since 2.0.0
 */
class AjaxTracker {

	/**
	 * Plugin settings repository.
	 *
	 * @since 2.0.0
	 * @var Options
	 */
	private Options $options;

	/**
	 * Event persistence service.
	 *
	 * @since 2.0.0
	 * @var Tracker
	 */
	private Tracker $tracker;

	/**
	 * Create the AJAX tracker.
	 *
	 * @since 2.0.0
	 * @param Options $options Plugin settings repository.
	 */
	public function __construct( Options $options ) {
		$this->options = $options;
		$this->tracker = new Tracker();
	}

	/**
	 * Register AJAX hooks for authenticated and guest users.
	 *
	 * @since 2.0.0
	 */
	public function init_hooks(): void {
		add_action( 'wp_ajax_relatedly_track_event', [ $this, 'handle' ] );
		add_action( 'wp_ajax_nopriv_relatedly_track_event', [ $this, 'handle' ] );
	}

	/**
	 * Handle incoming tracking request.
	 *
	 * @since 2.0.0
	 */
	public function handle(): void {
		// Nonce check — required even for unauthenticated requests.
		if ( ! wp_verify_nonce(
			sanitize_text_field( wp_unslash( $_POST['nonce'] ?? '' ) ),
			'relatedly_track_event'
		) ) {
			wp_send_json_error( [ 'message' => __( 'Security check failed.', 'relatedly' ) ] );
		}

		if ( ! $this->options->get( 'enable_click_tracking', true ) ) {
			wp_send_json_error( [ 'message' => __( 'Tracking is disabled.', 'relatedly' ) ] );
		}

		$source_id    = isset( $_POST['source_post_id'] ) ? absint( $_POST['source_post_id'] ) : 0;
		$display_type = isset( $_POST['display_type'] ) ? sanitize_text_field( wp_unslash( $_POST['display_type'] ) ) : '';
		$event_type   = isset( $_POST['event_type'] ) ? sanitize_text_field( wp_unslash( $_POST['event_type'] ) ) : 'click';
		$raw_ids      = isset( $_POST['clicked_post_ids'] ) ? sanitize_text_field( wp_unslash( $_POST['clicked_post_ids'] ) ) : '';

		if ( ! $source_id ) {
			wp_send_json_error( [ 'message' => __( 'Invalid source post.', 'relatedly' ) ] );
		}

		if ( ! in_array( $display_type, [ 'inline', 'end_content', 'floating' ], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid display type.', 'relatedly' ) ] );
		}

		if ( ! in_array( $event_type, [ 'click', 'impression' ], true ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid event type.', 'relatedly' ) ] );
		}

		if ( ! get_post( $source_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Source post not found.', 'relatedly' ) ] );
		}

		$ip         = $this->get_client_ip();
		$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] )
			? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) )
			: '';

		// Batched impressions: clicked_post_ids is a comma-separated list.
		if ( 'impression' === $event_type ) {
			$ids   = array_filter( array_map( 'absint', explode( ',', $raw_ids ) ) );
			$count = $this->tracker->record_batch( $source_id, $ids, $display_type, $ip, $user_agent );
			wp_send_json_success( [ 'recorded' => $count ] );
		}

		// Single click event.
		$clicked_id = absint( $raw_ids );
		if ( ! $clicked_id || ! get_post( $clicked_id ) ) {
			wp_send_json_error( [ 'message' => __( 'Clicked post not found.', 'relatedly' ) ] );
		}

		$ok = $this->tracker->record( $source_id, $clicked_id, $display_type, 'click', $ip, $user_agent );
		if ( ! $ok ) {
			wp_send_json_error( [ 'message' => __( 'Failed to record event.', 'relatedly' ) ] );
		}

		wp_send_json_success( [ 'recorded' => 1 ] );
	}

	// -------------------------------------------------------------------------
	// Internal
	// -------------------------------------------------------------------------

	/**
	 * Resolve the real visitor IP, respecting common proxy headers.
	 *
	 * @since 2.0.0
	 */
	private function get_client_ip(): string {
		$keys = [
			'HTTP_CF_CONNECTING_IP', // Cloudflare.
			'HTTP_X_REAL_IP',
			'HTTP_X_FORWARDED_FOR',
			'REMOTE_ADDR',
		];

		foreach ( $keys as $key ) {
			if ( empty( $_SERVER[ $key ] ) ) {
				continue;
			}
			$ip = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
			// X-Forwarded-For can be a comma-separated list; take the first entry.
			if ( strpos( $ip, ',' ) !== false ) {
				$ip = trim( explode( ',', $ip )[0] );
			}
			if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) {
				return $ip;
			}
		}

		return '';
	}
}
