<?php

namespace WPProofShield\Integration;

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


use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
use WPProofShield\Security\ModeAdvisor;
use WPProofShield\Security\RateLimiter;
use WPProofShield\Security\RequestVerifier;
use WPProofShield\Security\StatsStore;
use WPProofShield\Security\TokenService;
use WPProofShield\Support\FeatureAccess\FeatureAccess;
use WPProofShield\Support\Settings;

final class ApiController {

	public function __construct(
		private Settings $settings,
		private ModeAdvisor $mode_advisor,
		private RateLimiter $rate_limiter,
		private RequestVerifier $verifier,
		private TokenService $token_service,
		private StatsStore $stats,
		private FeatureAccess $feature_access
	) {
	}

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

	public function registerRoutes(): void {
		register_rest_route(
			'wpshield/v1',
			'/challenge',
			array(
				'methods'             => 'POST',
				'permission_callback' => array( $this, 'publicPowEndpointPermission' ),
				'callback'            => array( $this, 'challenge' ),
			)
		);

		register_rest_route(
			'wpshield/v1',
			'/verify',
			array(
				'methods'             => 'POST',
				'permission_callback' => array( $this, 'publicPowEndpointPermission' ),
				'callback'            => array( $this, 'verify' ),
			)
		);
	}

	/**
	 * ProofShield challenge/verify endpoints are intentionally public.
	 * They only issue/validate PoW tokens and do not expose private data.
	 */
	public function publicPowEndpointPermission( WP_REST_Request $request ): bool {
		return true;
	}

	public function challenge( WP_REST_Request $request ): WP_REST_Response|WP_Error {

		$ctx = sanitize_text_field( (string) $request->get_param( 'ctx' ) );
		if ( $ctx === '' ) {
			return new WP_Error( 'wpsh_bad_request', __( 'Context is required.', 'proofshield' ), array( 'status' => 400 ) );
		}
		$honeypot_field = $this->sanitizeHoneypotField( (string) $request->get_param( 'hpn' ) );
		$refresh_retry  = (int) $request->get_param( 'refresh_retry' ) === 1;
		if (
			str_starts_with( $ctx, 'form:' )
			&& ! $this->isFreeFormContext( $ctx )
			&& ! $this->license_gate->canUse( 'generic_forms' )
		) {
			return new WP_Error( 'wpsh_bad_request', __( 'Unsupported context.', 'proofshield' ), array( 'status' => 400 ) );
		}

		if ( $ctx === 'woo_login' && ! false ) {
			return new WP_Error( 'wpsh_bad_request', __( 'Unsupported context.', 'proofshield' ), array( 'status' => 400 ) );
		}

		$ip       = $this->remoteIp();
		$attempts = $this->rate_limiter->recordChallengeAttempt( $ctx, $ip );
		$failures = $this->rate_limiter->failureCount( $ctx, $ip );

		$base_difficulty = $this->settings->difficultyForMode( $this->mode_advisor->effectiveMode() );
		$difficulty      = $this->stats->suggestedDifficulty( $ctx, $base_difficulty );
		$difficulty      = $this->verifier->adjustDifficultyForRisk( $ctx, $difficulty );
		$difficulty      = $this->calibrateDifficultyByAttempts( $difficulty, $attempts, $failures, $ctx );
		$grace_applied   = false;
		if ( $refresh_retry && $this->rate_limiter->consumeRetryRelief( $ctx, $ip ) ) {
			if ( str_starts_with( $ctx, 'form:' ) ) {
				// For protected forms, refresh should provide a deterministic baseline reset.
				$difficulty = $this->baseDifficultyForContext( $ctx );
			} else {
				$difficulty = $this->applyRefreshRelief( $difficulty, $ctx );
			}
			$this->rate_limiter->activateRetryGrace( $ctx, $ip, 5 * MINUTE_IN_SECONDS );
			$grace_applied = true;
		} elseif ( $this->rate_limiter->hasRetryGrace( $ctx, $ip ) ) {
			if ( str_starts_with( $ctx, 'form:' ) ) {
				$difficulty = $this->baseDifficultyForContext( $ctx );
			} else {
				$difficulty = $this->applyRefreshRelief( $difficulty, $ctx );
			}
			$grace_applied = true;
		}
		$token = $this->token_service->issue( $ctx, $difficulty, $honeypot_field );
		return new WP_REST_Response(
			array(
				'token'          => $token,
				'difficulty'     => $difficulty,
				'refresh_relief' => $grace_applied,
				'algo'           => 'sha256',
				'max_age'        => $this->settings->maxAgeSeconds(),
			)
		);
	}

	private function calibrateDifficultyByAttempts( int $difficulty, int $attempts, int $failures, string $context ): int {
		$difficulty = max( 2, min( 6, $difficulty ) );

		if ( $context === 'login' && $failures >= 5 ) {
			return 6;
		}

		if ( $attempts <= 2 ) {
			return min( 4, $difficulty );
		}

		if ( $attempts <= 5 ) {
			return min( 5, $difficulty );
		}

		if ( $attempts >= 12 ) {
			return 6;
		}

		if ( $attempts >= 6 ) {
			return max( 5, $difficulty );
		}

		return $difficulty;
	}

	private function applyRefreshRelief( int $difficulty, string $context ): int {

		$difficulty = max( 2, min( 6, $difficulty ) );

		if ( str_starts_with( $context, 'form:' ) ) {
			return min( 3, max( 2, $difficulty - 2 ) );
		}

		return max( 2, $difficulty - 1 );
	}

	private function baseDifficultyForContext( string $context ): int {

		$modeBase = $this->settings->difficultyForMode( $this->mode_advisor->effectiveMode() );
		$modeBase = max( 2, min( 6, $modeBase ) );
		if ( str_starts_with( $context, 'form:' ) ) {
			return min( 4, $modeBase );
		}

		return $modeBase;
	}
	private function remoteIp(): string {
		$candidate     = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ) : '';
		$host          = (string) wp_parse_url( home_url( '/' ), PHP_URL_HOST );
		$is_local_host = in_array( $host, array( 'localhost', '127.0.0.1', '::1' ), true );
		if ( $is_local_host ) {
			$sim = isset( $_SERVER['HTTP_X_WPSH_SIM_IP'] ) ? trim( sanitize_text_field( wp_unslash( (string) $_SERVER['HTTP_X_WPSH_SIM_IP'] ) ) ) : '';
			if ( filter_var( $sim, FILTER_VALIDATE_IP ) ) {
				$candidate = $sim;
			}
		}
		return filter_var( $candidate, FILTER_VALIDATE_IP ) ? $candidate : '0.0.0.0';
	}

	private function isFreeFormContext( string $ctx ): bool {

		if ( ! false ) {
			return false;
		}

		if ( ! $this->settings->isEnabled( 'enabled_contact_forms' ) ) {
			return false;
		}

		return in_array(
			$ctx,
			array(
				'form:elementor',
				'form:cf7',
				'form:wpforms',
				'form:enfold',
				'form:gravityforms',
				'form:ninjaforms',
				'form:formidable',
				'form:fluentforms',
				'form:jetformbuilder',
				'form:everestforms',
				'form:bricks',
				'form:divi',
			),
			true
		);
	}

	private function sanitizeHoneypotField( string $name ): string {

		$name = sanitize_key( $name );
		if ( $name === '' || strlen( $name ) < 3 || strlen( $name ) > 32 ) {
			return '';
		}
		if ( ! preg_match( '/^[a-z][a-z0-9_]+$/', $name ) ) {
			return '';
		}
		return $name;
	}
	public function verify( WP_REST_Request $request ): WP_REST_Response {
		$this->verifier->seedRequestValues(
			array(
				'wpsh_pow'  => (string) $request->get_param( 'token' ),
				'wpsh_ctr'  => (string) $request->get_param( 'counter' ),
				'wpsh_hash' => (string) $request->get_param( 'hash' ),
			)
		);

		$ctx = sanitize_text_field( (string) $request->get_param( 'ctx' ) );
		if ( $ctx === '' ) {
			$this->verifier->clearSeedRequestValues();
			return new WP_REST_Response(
				array(
					'ok'     => false,
					'reason' => 'missing_context',
				),
				400
			);
		}

		try {
			$result = $this->verifier->verify( $ctx, true );
		} finally {
			$this->verifier->clearSeedRequestValues();
		}
		if ( is_wp_error( $result ) ) {
			return new WP_REST_Response(
				array(
					'ok'             => false,
					'reason'         => $result->get_error_code(),
					'cooldown_until' => null,
				),
				403
			);
		}

		return new WP_REST_Response(
			array(
				'ok'             => true,
				'reason'         => 'ok',
				'cooldown_until' => null,
			)
		);
	}
}
