<?php
/**
 * Store API interceptor for Block Checkout protection.
 *
 * @package Carticy\CheckoutShield\Interceptors
 */

declare(strict_types=1);

namespace Carticy\CheckoutShield\Interceptors;

use Carticy\CheckoutShield\Services\DetectionService;
use Carticy\CheckoutShield\Services\LoggingService;
use Carticy\CheckoutShield\Services\OrderAttributionService;
use Carticy\CheckoutShield\Services\WhitelistService;
use WP_Error;
use WP_REST_Request;
use WP_REST_Server;

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

/**
 * Intercepts Store API checkout requests for bot detection.
 */
final class StoreApiInterceptor {

	/**
	 * Detection service.
	 *
	 * @var DetectionService
	 */
	private DetectionService $detection_service;

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

	/**
	 * Whitelist service.
	 *
	 * @var WhitelistService
	 */
	private WhitelistService $whitelist_service;

	/**
	 * Order attribution service.
	 *
	 * @var OrderAttributionService
	 */
	private OrderAttributionService $attribution_service;

	/**
	 * Constructor.
	 *
	 * @param DetectionService        $detection_service   Detection service instance.
	 * @param LoggingService          $logging_service     Logging service instance.
	 * @param WhitelistService        $whitelist_service   Whitelist service instance.
	 * @param OrderAttributionService $attribution_service Order attribution service instance.
	 */
	public function __construct(
		DetectionService $detection_service,
		LoggingService $logging_service,
		WhitelistService $whitelist_service,
		OrderAttributionService $attribution_service
	) {
		$this->detection_service   = $detection_service;
		$this->logging_service     = $logging_service;
		$this->whitelist_service   = $whitelist_service;
		$this->attribution_service = $attribution_service;
	}

	/**
	 * Register hooks.
	 */
	public function register(): void {
		add_filter( 'rest_pre_dispatch', array( $this, 'intercept_store_api' ), 10, 3 );
	}

	/**
	 * Intercept Store API checkout requests.
	 *
	 * @param mixed           $result  Response to replace the requested version with.
	 * @param WP_REST_Server  $server  Server instance.
	 * @param WP_REST_Request $request Request used to generate the response.
	 * @return mixed|WP_Error
	 */
	public function intercept_store_api( $result, WP_REST_Server $server, WP_REST_Request $request ) {
		// Only intercept Store API checkout POST requests.
		$route = $request->get_route();
		if ( ! $this->is_checkout_route( $route ) ) {
			return $result;
		}

		if ( $request->get_method() !== 'POST' ) {
			return $result;
		}

		// Check if protection is enabled.
		if ( ! $this->is_enabled() ) {
			return $result;
		}

		// Check whitelist first (API key or IP).
		if ( $this->whitelist_service->is_whitelisted( $request ) ) {
			$this->logging_service->info(
				'Store API request whitelisted',
				array(
					'route' => $route,
					'ip'    => $this->whitelist_service->get_client_ip(),
				)
			);
			$this->attribution_service->set_attribution( 'store_api', 'bypassed', array( 'reason' => 'whitelisted' ) );
			return $result;
		}

		// Validate double-submit.
		$validation_passed = $this->detection_service->validate_double_submit();
		$detection_context = $this->detection_service->get_detection_context( $validation_passed );

		// Get current mode.
		$mode = $this->get_mode();

		// Learning mode: log but don't block.
		if ( 'learning' === $mode ) {
			$this->logging_service->log_detection( 'store_api', $validation_passed, $detection_context );
			$this->attribution_service->set_attribution( 'store_api', 'learning', $detection_context );
			return $result;
		}

		// Handle based on mode.
		if ( ! $validation_passed ) {
			// Permissive mode: only block if no session exists.
			if ( 'permissive' === $mode && $detection_context['has_session'] ) {
				$this->logging_service->log_detection(
					'store_api',
					true,
					array_merge(
						$detection_context,
						array( 'mode_override' => 'permissive_session_bypass' )
					)
				);
				$this->attribution_service->set_attribution(
					'store_api',
					'passed',
					array_merge(
						$detection_context,
						array( 'mode_override' => 'permissive_session_bypass' )
					)
				);
				return $result;
			}

			// Block the request.
			$this->logging_service->log_detection( 'store_api', false, $detection_context );
			$this->attribution_service->set_attribution( 'store_api', 'blocked', $detection_context );

			return new WP_Error(
				'ccs_blocked',
				__( 'Unable to process your request. Please refresh the page and try again.', 'carticy-checkout-shield-for-woocommerce' ),
				array( 'status' => 403 )
			);
		}

		// Strict mode: also require session.
		if ( 'strict' === $mode && ! $detection_context['has_session'] ) {
			$this->logging_service->log_detection(
				'store_api',
				false,
				array_merge(
					$detection_context,
					array( 'strict_session_required' => true )
				)
			);
			$this->attribution_service->set_attribution(
				'store_api',
				'blocked',
				array_merge(
					$detection_context,
					array( 'strict_session_required' => true )
				)
			);

			return new WP_Error(
				'ccs_blocked',
				__( 'Unable to process your request. Please refresh the page and try again.', 'carticy-checkout-shield-for-woocommerce' ),
				array( 'status' => 403 )
			);
		}

		// Validation passed.
		$this->logging_service->log_detection( 'store_api', true, $detection_context );
		$this->attribution_service->set_attribution( 'store_api', 'passed', $detection_context );

		return $result;
	}

	/**
	 * Check if route is a checkout route.
	 *
	 * @param string $route The REST route.
	 * @return bool
	 */
	private function is_checkout_route( string $route ): bool {
		// Match /wc/store/checkout and /wc/store/v1/checkout.
		return (bool) preg_match( '#^/wc/store(/v\d+)?/checkout#', $route );
	}

	/**
	 * Check if protection is enabled.
	 *
	 * @return bool
	 */
	private function is_enabled(): bool {
		return get_option( 'carticy_checkout_shield_enabled', 'yes' ) === 'yes';
	}

	/**
	 * Get the current operating mode.
	 *
	 * @return string
	 */
	private function get_mode(): string {
		$mode = get_option( 'carticy_checkout_shield_mode', 'balanced' );
		return in_array( $mode, array( 'learning', 'permissive', 'balanced', 'strict' ), true ) ? $mode : 'balanced';
	}
}
