<?php
/**
 * @package Hafenstudios\Wellenbrecher
 */

namespace Hafenstudios\Wellenbrecher\Rest;

use Hafenstudios\Core\Settings\Settings;
use Hafenstudios\Wellenbrecher\Db\SpamLogRepository;
use Hafenstudios\Wellenbrecher\Signals\PowChallenge;
use Hafenstudios\Wellenbrecher\Signals\TokenService;
use Hafenstudios\Wellenbrecher\Support\IpHasher;
use Hafenstudios\Wellenbrecher\Support\RequestSnapshot;

defined( 'ABSPATH' ) || exit;

/**
 * Oeffentlicher Token-Endpoint (Bauplan Kap. 4.4/11): liefert nur den
 * Fenster-Token (fuer alle Besucher gleich, kein State) mit
 * nocache_headers(), damit signals.js ihn auch hinter Full-Page-Caches
 * frisch holen kann. Rate-Limit pro IP; Missbrauch bringt einem Bot nur
 * ein Signal von vielen.
 */
class TokenController {

	public const REST_NAMESPACE = 'wellenbrecher/v1';

	/**
	 * Erlaubte Abrufe pro IP und 10-Minuten-Fenster.
	 */
	public const RATE_LIMIT = 60;

	/**
	 * Rueckblick-Fenster der PoW-Eskalation: die IP muss in den letzten 24 h
	 * einen Nicht-pass-Eintrag im Spam-Log haben, sonst keine Challenge.
	 */
	public const POW_LOOKBACK = 86400;

	protected TokenService $tokens;

	protected ?RequestSnapshot $snapshots;

	protected ?Settings $settings;

	protected ?IpHasher $hasher;

	protected ?SpamLogRepository $log;

	protected ?PowChallenge $challenge;

	protected int $pow_difficulty;

	/**
	 * @param TokenService         $tokens         Token-Dienst.
	 * @param RequestSnapshot|null $snapshots      Snapshot-Fabrik fuer die aufgeloeste Client-IP
	 *                                             (W2-Review: hinter einem Trusted Proxy teilen
	 *                                             sich sonst alle Besucher EIN Rate-Limit-Fenster).
	 * @param Settings|null        $settings       Wellenbrecher-Settings (proxy.trusted_header).
	 * @param IpHasher|null        $hasher         IP-HMAC-Helfer (nur fuer die PoW-Eskalation).
	 * @param SpamLogRepository|null $log          Spam-Log fuer den Eskalations-Lookup.
	 * @param PowChallenge|null    $challenge      Proof-of-Work-Dienst (null = keine Challenge).
	 * @param int                  $pow_difficulty Schwierigkeitsgrad (fuehrende Hex-Nullen).
	 */
	public function __construct(
		TokenService $tokens,
		?RequestSnapshot $snapshots = null,
		?Settings $settings = null,
		?IpHasher $hasher = null,
		?SpamLogRepository $log = null,
		?PowChallenge $challenge = null,
		int $pow_difficulty = PowChallenge::DEFAULT_DIFFICULTY
	) {
		$this->tokens         = $tokens;
		$this->snapshots      = $snapshots;
		$this->settings       = $settings;
		$this->hasher         = $hasher;
		$this->log            = $log;
		$this->challenge      = $challenge;
		$this->pow_difficulty = $pow_difficulty;
	}

	/**
	 * Registriert die Route (rest_api_init).
	 *
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route(
			self::REST_NAMESPACE,
			'/token',
			array(
				'methods'             => \WP_REST_Server::READABLE,
				'callback'            => array( $this, 'token' ),
				'permission_callback' => '__return_true',
			)
		);
	}

	/**
	 * Liefert den aktuellen Fenster-Token.
	 *
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function token() {
		$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( (string) $_SERVER['REMOTE_ADDR'] ) ) : '';
		if ( null !== $this->snapshots && null !== $this->settings ) {
			$snapshot = $this->snapshots->build( $_SERVER, (string) $this->settings->get( 'proxy.trusted_header', '' ) );
			$ip       = (string) ( $snapshot['remote_addr'] ?? $ip );
		}
		$key = 'wb_token_rl_' . md5( $ip );

		$count = (int) get_transient( $key );
		if ( $count >= self::RATE_LIMIT ) {
			return new \WP_Error(
				'wellenbrecher_rate_limited',
				__( 'Es wurden zu viele Anfragen gestellt. Bitte versuchen Sie es in einigen Minuten erneut.', 'wellenbrecher' ),
				array( 'status' => 429 )
			);
		}
		set_transient( $key, $count + 1, 10 * MINUTE_IN_SECONDS );

		nocache_headers();

		$data = array( 'token' => $this->tokens->current() );
		$data = $this->maybe_add_challenge( $data, $ip );

		return new \WP_REST_Response( $data, 200 );
	}

	/**
	 * Haengt eine Proof-of-Work-Challenge an, aber NUR wenn PoW verdrahtet ist
	 * (Pro + Setting an) UND die IP in den letzten 24 h einen Nicht-pass-Eintrag
	 * im Spam-Log hat (Eskalation, Technik A3). Unauffaellige Besucher bekommen
	 * nie eine Challenge.
	 *
	 * @param array<string, mixed> $data Antwortdaten.
	 * @param string               $ip   Aufgeloeste Client-IP.
	 * @return array<string, mixed>
	 */
	protected function maybe_add_challenge( array $data, string $ip ): array {
		if ( null === $this->challenge || null === $this->log || null === $this->hasher || '' === $ip ) {
			return $data;
		}

		$hmac = $this->hasher->hash( $ip );
		if ( '' === $hmac || ! $this->log->has_recent_match( $hmac, self::POW_LOOKBACK ) ) {
			return $data;
		}

		$data['pow'] = $this->challenge->issue( $hmac, $this->pow_difficulty );
		return $data;
	}
}
