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

namespace Hafenstudios\Kurato\Rest;

use Hafenstudios\Kurato\Mail\SuppressionService;
use Hafenstudios\Kurato\Mail\WebhookNormalizer;

defined( 'ABSPATH' ) || exit;

/**
 * Inbound-Webhook fuer Zustell-Ereignisse (`kurato/v1/webhooks/brevo`,
 * K4-Konzept Abschnitt 9).
 *
 * Absicherung ohne Login: ein bei der Aktivierung erzeugtes Secret wird als
 * `?secret=...` in der URL mitgefuehrt und mit `hash_equals` gegen das in den
 * Kurato-Settings gespeicherte Secret geprueft. Zusaetzlich ein Rate-Limit pro
 * IP (Muster UnsubscribeController). Der Controller ist gegen den
 * WebhookNormalizer gebaut (SES/Postmark docken in 1.1 daran an) und delegiert
 * die Wirkung an den SuppressionService.
 *
 * Unbekannte/irrelevante Ereignisse werden mit 200 quittiert, damit der ESP den
 * Zustellversuch nicht wiederholt.
 */
final class BrevoWebhookController {

	public const REST_NAMESPACE = 'kurato/v1';

	/**
	 * Rate-Limit-Schwelle pro IP und Zeitfenster (grosszuegig, da ein ESP viele
	 * Ereignisse buendeln kann).
	 */
	private const RATE_LIMIT = 120;

	private WebhookNormalizer $normalizer;

	private SuppressionService $suppression;

	private string $secret;

	/**
	 * @param WebhookNormalizer  $normalizer  Payload-Normalizer (Brevo).
	 * @param SuppressionService $suppression Suppression-/Wirkungsdienst.
	 * @param string             $secret      Erwartetes URL-Secret (aus den Settings).
	 */
	public function __construct( WebhookNormalizer $normalizer, SuppressionService $suppression, string $secret ) {
		$this->normalizer  = $normalizer;
		$this->suppression = $suppression;
		$this->secret      = $secret;
	}

	/**
	 * Registriert die Route.
	 *
	 * @return void
	 */
	public function register_routes(): void {
		register_rest_route(
			self::REST_NAMESPACE,
			'/webhooks/brevo',
			array(
				'methods'             => \WP_REST_Server::CREATABLE,
				'callback'            => array( $this, 'handle' ),
				// Absicherung ist das Secret im Callback, nicht ein WP-Login.
				'permission_callback' => '__return_true',
			)
		);
	}

	/**
	 * Verarbeitet einen Webhook-Aufruf.
	 *
	 * @param \WP_REST_Request $request Request.
	 * @return \WP_REST_Response|\WP_Error
	 */
	public function handle( \WP_REST_Request $request ) {
		if ( $this->rate_limited() ) {
			return new \WP_Error(
				'kurato_rate_limited',
				__( 'Zu viele Anfragen.', 'kurato' ),
				array( 'status' => 429 )
			);
		}

		// Secret-Pruefung VOR jeder Wirkung. Leeres/falsches Secret = 403, keine Seiteneffekte.
		$provided = (string) $request->get_param( 'secret' );
		if ( '' === $this->secret || ! hash_equals( $this->secret, $provided ) ) {
			return new \WP_Error(
				'kurato_forbidden',
				__( 'Zugriff verweigert.', 'kurato' ),
				array( 'status' => 403 )
			);
		}

		$payload = $request->get_json_params();
		$payload = is_array( $payload ) ? $payload : array();

		$event = $this->normalizer->normalize( $payload );
		if ( null === $event ) {
			// Unbekannter/irrelevanter Typ oder fehlende E-Mail: bewusst 200, damit
			// der ESP nicht erneut zustellt.
			return new \WP_REST_Response( array( 'ignored' => true ), 200 );
		}

		$result = $this->suppression->apply( $event['type'], $event['email'], $event['reason'] );

		return new \WP_REST_Response( array( 'ok' => true, 'result' => $result ), 200 );
	}

	/**
	 * Prueft und erhoeht das IP-Rate-Limit (Muster UnsubscribeController).
	 *
	 * @return bool true, wenn blockiert.
	 */
	private function rate_limited(): bool {
		$ip  = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
		$key = 'kurato_webhook_' . md5( $ip );

		$count = (int) get_transient( $key );
		if ( $count >= self::RATE_LIMIT ) {
			return true;
		}
		set_transient( $key, $count + 1, MINUTE_IN_SECONDS );
		return false;
	}
}
