<?php
/**
 * Detection service for double-submit cookie validation.
 *
 * @package Carticy\CheckoutShield\Services
 */

declare(strict_types=1);

namespace Carticy\CheckoutShield\Services;

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

/**
 * Handles the core detection logic using double-submit cookie pattern.
 */
final class DetectionService {

	/**
	 * Cookie name for the proof token.
	 */
	private const COOKIE_NAME = 'ccs_proof';

	/**
	 * POST field name for classic checkout.
	 */
	private const POST_FIELD_NAME = 'ccs_proof';

	/**
	 * Header name for Store API (Block Checkout).
	 */
	private const HEADER_NAME = 'HTTP_X_CCS_PROOF';

	/**
	 * Logging service.
	 *
	 * @var LoggingService
	 */
	private LoggingService $logging_service;

	/**
	 * Constructor.
	 *
	 * @param LoggingService $logging_service Logging service instance.
	 */
	public function __construct( LoggingService $logging_service ) {
		$this->logging_service = $logging_service;
	}

	/**
	 * Validate the double-submit cookie pattern.
	 *
	 * The cookie value must match the submitted field/header value.
	 * This proves the request originated from a page that executed our JavaScript.
	 *
	 * @return bool True if validation passes, false otherwise.
	 */
	public function validate_double_submit(): bool {
		// Get the cookie token.
		$cookie_token = $this->get_cookie_token();

		// Get the submitted token (from POST field or header).
		$submitted_token = $this->get_submitted_token();

		// Both must exist.
		if ( empty( $cookie_token ) || empty( $submitted_token ) ) {
			return false;
		}

		// Must match (timing-safe comparison).
		return hash_equals( $cookie_token, $submitted_token );
	}

	/**
	 * Get the token from the cookie.
	 *
	 * @return string
	 */
	private function get_cookie_token(): string {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized by sanitize_token() below.
		$token = isset( $_COOKIE[ self::COOKIE_NAME ] ) ? wp_unslash( $_COOKIE[ self::COOKIE_NAME ] ) : '';
		return $this->sanitize_token( $token );
	}

	/**
	 * Get the submitted token from POST field or header.
	 *
	 * @return string
	 */
	private function get_submitted_token(): string {
		// Check POST field first (Classic Checkout).
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified by WooCommerce.
		if ( ! empty( $_POST[ self::POST_FIELD_NAME ] ) ) {
			// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$token = wp_unslash( $_POST[ self::POST_FIELD_NAME ] );
			return $this->sanitize_token( $token );
		}

		// Check header (Store API / Block Checkout).
		if ( ! empty( $_SERVER[ self::HEADER_NAME ] ) ) {
			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
			$token = wp_unslash( $_SERVER[ self::HEADER_NAME ] );
			return $this->sanitize_token( $token );
		}

		return '';
	}

	/**
	 * Sanitize a token value.
	 *
	 * Tokens should be 32 hex characters (16 bytes).
	 *
	 * @param mixed $token The token to sanitize.
	 * @return string
	 */
	private function sanitize_token( $token ): string {
		if ( ! is_string( $token ) ) {
			return '';
		}

		// Remove any non-hex characters.
		$sanitized = preg_replace( '/[^a-f0-9]/i', '', $token );

		// Tokens should be exactly 32 hex characters.
		if ( strlen( $sanitized ) !== 32 ) {
			return '';
		}

		return strtolower( $sanitized );
	}

	/**
	 * Get detection context for logging/attribution.
	 *
	 * @param bool $validation_passed Whether validation passed.
	 * @return array
	 */
	public function get_detection_context( bool $validation_passed ): array {
		return array(
			'double_submit' => $validation_passed,
			'has_cookie'    => ! empty( $this->get_cookie_token() ),
			'has_submitted' => ! empty( $this->get_submitted_token() ),
			'has_session'   => $this->has_wc_session(),
			'user_agent'    => $this->get_user_agent_summary(),
			'referrer'      => $this->has_referrer(),
		);
	}

	/**
	 * Check if WooCommerce session exists and has cart.
	 *
	 * @return bool
	 */
	private function has_wc_session(): bool {
		if ( ! function_exists( 'WC' ) || ! WC()->session ) {
			return false;
		}

		// Check if session has any data.
		$cart = WC()->session->get( 'cart' );
		return ! empty( $cart );
	}

	/**
	 * Get a summary of the user agent for logging.
	 *
	 * @return string
	 */
	private function get_user_agent_summary(): string {
		$ua = sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ) );

		if ( empty( $ua ) ) {
			return 'empty';
		}

		// Check for common bot patterns.
		$suspicious_patterns = array(
			'curl',
			'python',
			'bot',
			'spider',
			'headless',
			'phantom',
			'selenium',
		);

		foreach ( $suspicious_patterns as $pattern ) {
			if ( stripos( $ua, $pattern ) !== false ) {
				return 'suspicious:' . $pattern;
			}
		}

		return 'normal';
	}

	/**
	 * Check if HTTP referer is present and valid.
	 *
	 * @return bool
	 */
	private function has_referrer(): bool {
		$referrer = sanitize_text_field( wp_unslash( $_SERVER['HTTP_REFERER'] ?? '' ) );

		if ( empty( $referrer ) ) {
			return false;
		}

		// Check if referrer is from the same site.
		$site_host     = wp_parse_url( get_site_url(), PHP_URL_HOST );
		$referrer_host = wp_parse_url( $referrer, PHP_URL_HOST );

		return $site_host === $referrer_host;
	}
}
