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

namespace Hafenstudios\Wellenbrecher\Channels;

use Hafenstudios\Core\Events\EventBus;
use Hafenstudios\Core\Settings\Settings;
use Hafenstudios\Wellenbrecher\Db\CommentLookup;
use Hafenstudios\Wellenbrecher\Db\SpamLogRepository;
use Hafenstudios\Wellenbrecher\Db\StatsRepository;
use Hafenstudios\Wellenbrecher\Engine\Context;
use Hafenstudios\Wellenbrecher\Engine\ScoreEngine;
use Hafenstudios\Wellenbrecher\Engine\Verdict;
use Hafenstudios\Wellenbrecher\Support\Allowlist;
use Hafenstudios\Wellenbrecher\Support\IpHasher;
use Hafenstudios\Wellenbrecher\Support\ObservationMode;
use Hafenstudios\Wellenbrecher\Support\RequestSnapshot;
use Hafenstudios\Wellenbrecher\Support\SpamRecorder;
use Hafenstudios\Wellenbrecher\Support\Text;

defined( 'ABSPATH' ) || exit;

/**
 * Leadlotse-Kanal (Bauplan Kap. 7.3, das Flotten-Argument): lauscht auf den
 * designierten Leadlotse-Filter `hafenstudios/leadlotse_spam_check`, der seit
 * W-L1 zusaetzlich einen Kontext-Array uebergibt (Server-Snapshot-Rohdaten,
 * die injizierten wb_*-Signalfelder und die von Leadlotse aufbereiteten
 * Textfeld-/E-Mail-Werte des Submits).
 *
 * Zonen (Bauplan Kap. 7.3):
 * - pass       -> Filter unveraendert durchreichen (Submit laeuft normal).
 * - block      -> WP_Error mit hoeflicher, generischer Meldung (kein Hinweis
 *                 auf die Spam-Erkennung); Log-Verdict blocked.
 * - quarantine -> Submit wird NICHT gespeichert; der komplette Payload wandert
 *                 als JSON in hs_spam_log (Verdict quarantined, form_id), der
 *                 Besucher erhaelt die normale Erfolgsantwort. Signalwert dafuer
 *                 ist die Konstante SILENT_DROP, die Leadlotses SubmitHandler in
 *                 success_payload(0) uebersetzt (stilles Abfangen).
 * - flagged    -> Beobachtungsmodus: nur loggen und durchlassen.
 *
 * Kurato ist damit automatisch geschuetzt: ein als Spam eingestufter
 * Newsletter-Submit erzeugt weder Entry noch Kontakt noch Abo, weil der
 * SubmitHandler vor Insert und Event abbricht.
 *
 * Fail-open (Produktversprechen Nr. 6): wirft die Bewertung eine Exception,
 * wird der Filter unveraendert durchgereicht und der Submit laeuft normal.
 */
class LeadlotseChannel {

	/**
	 * Signalwert fuer den SubmitHandler: "still verwerfen, Erfolg vortaeuschen"
	 * (Quarantaene). Muss mit Leadlotses SubmitHandler::SPAM_SILENT_DROP
	 * uebereinstimmen - bewusst als Literal gehalten, damit kein harter
	 * Klassenbezug auf Leadlotse entsteht (Filter-Vertrag).
	 */
	public const SILENT_DROP = 'leadlotse_spam_silent_drop';

	protected \Closure $engine_factory;

	protected Settings $settings;

	protected SpamLogRepository $log;

	protected StatsRepository $stats;

	protected IpHasher $hasher;

	protected Allowlist $allowlist;

	protected ObservationMode $observation;

	protected CommentLookup $lookup;

	protected RequestSnapshot $snapshots;

	/**
	 * @param \Closure          $engine_factory Liefert die ScoreEngine (lazy).
	 * @param Settings          $settings       Wellenbrecher-Settings.
	 * @param SpamLogRepository $log            Spam-Log.
	 * @param StatsRepository   $stats          Tageszaehler.
	 * @param IpHasher          $hasher         IP-HMAC-Helfer.
	 * @param Allowlist         $allowlist      Rehabilitations-Allowlist.
	 * @param ObservationMode   $observation    Beobachtungsmodus.
	 * @param CommentLookup     $lookup         Kommentar-Abfragen (Trust).
	 * @param RequestSnapshot   $snapshots      Server-Snapshot-Fabrik.
	 */
	public function __construct(
		\Closure $engine_factory,
		Settings $settings,
		SpamLogRepository $log,
		StatsRepository $stats,
		IpHasher $hasher,
		Allowlist $allowlist,
		ObservationMode $observation,
		CommentLookup $lookup,
		RequestSnapshot $snapshots
	) {
		$this->engine_factory = $engine_factory;
		$this->settings       = $settings;
		$this->log            = $log;
		$this->stats          = $stats;
		$this->hasher         = $hasher;
		$this->allowlist      = $allowlist;
		$this->observation    = $observation;
		$this->lookup         = $lookup;
		$this->snapshots      = $snapshots;
	}

	/**
	 * Haengt den Kanal an den Leadlotse-Filter.
	 *
	 * @return void
	 */
	public function register(): void {
		EventBus::add_filter( 'leadlotse_spam_check', array( $this, 'on_spam_check' ), 10, 4 );
	}

	/**
	 * Filter-Callback (leadlotse_spam_check).
	 *
	 * @param mixed                $result  Bisheriges Ergebnis (true|WP_Error|SILENT_DROP).
	 * @param mixed                $input   Roh-Submit (Feld-ID => Wert).
	 * @param mixed                $form_id Formular-ID.
	 * @param mixed                $context Kontext-Array (server, signals, fields, submit).
	 * @return mixed
	 */
	public function on_spam_check( $result, $input = array(), $form_id = 0, $context = array() ) {
		// Ein vorheriger Konsument hat bereits abgelehnt: nicht ueberstimmen.
		if ( is_wp_error( $result ) ) {
			return $result;
		}

		$context = is_array( $context ) ? $context : array();

		try {
			$ctx     = $this->build_context( is_array( $input ) ? $input : array(), (int) $form_id, $context );
			$verdict = ( $this->engine_factory )()->assess( $ctx );
		} catch ( \Throwable $e ) {
			// Fail-open: der Submit laeuft unbewertet weiter.
			error_log( 'Wellenbrecher: Leadlotse-Bewertung uebersprungen (' . $e->getMessage() . ')' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			return $result;
		}

		if ( $verdict->passes() ) {
			return $result;
		}

		$email = $ctx->field( 'author_email' );
		$base  = array(
			'channel'      => 'leadlotse',
			'ip_hmac'      => $this->hasher->hash( $ctx->server( 'remote_addr' ) ),
			'email_domain' => Text::email_domain( $email ),
			'excerpt'      => mb_substr( $ctx->field( 'content' ), 0, 200 ),
			'form_id'      => (int) $form_id,
		);

		$recorder = new SpamRecorder( $this->log, $this->stats );

		// Beobachtungsmodus: markieren und durchlassen.
		if ( $this->observation->is_active() ) {
			$recorder->record( SpamLogRepository::VERDICT_FLAGGED, $verdict, $base );
			return $result;
		}

		if ( Verdict::ZONE_BLOCK === $verdict->zone ) {
			$recorder->record( SpamLogRepository::VERDICT_BLOCKED, $verdict, $base );
			return new \WP_Error(
				'leadlotse_rejected',
				__( 'Ihre Anfrage konnte nicht gesendet werden. Bitte versuchen Sie es spaeter erneut oder wenden Sie sich direkt an den Betreiber der Website.', 'wellenbrecher' ),
				array( 'status' => 400 )
			);
		}

		// Quarantaene: kompletten Payload sichern, dann still Erfolg vortaeuschen.
		$base['payload'] = (string) wp_json_encode(
			array(
				'input'   => is_array( $input ) ? $input : array(),
				'context' => is_array( $context['submit'] ?? null ) ? $context['submit'] : array(),
			)
		);
		$recorder->record( SpamLogRepository::VERDICT_QUARANTINED, $verdict, $base );

		return self::SILENT_DROP;
	}

	/**
	 * Baut den Engine-Context aus dem Leadlotse-Submit-Kontext.
	 *
	 * @param array<string, mixed> $input   Roh-Submit.
	 * @param int                  $form_id Formular-ID.
	 * @param array<string, mixed> $context Filter-Kontext (server, signals, fields, submit).
	 * @return Context
	 */
	protected function build_context( array $input, int $form_id, array $context ): Context {
		$server = $this->snapshots->build(
			is_array( $context['server'] ?? null ) ? $context['server'] : $_SERVER,
			(string) $this->settings->get( 'proxy.trusted_header', '' )
		);

		$parsed = is_array( $context['fields'] ?? null ) ? $context['fields'] : array();
		$email  = (string) ( $parsed['email'] ?? '' );

		$fields = array(
			'content'      => (string) ( $parsed['content'] ?? '' ),
			'author_name'  => (string) ( $parsed['author_name'] ?? '' ),
			'author_url'   => '',
			'author_email' => $email,
		);

		// Injizierte wb_*-Signalfelder aus dem Kontext (W-L1).
		$signal = is_array( $context['signals'] ?? null ) ? $context['signals'] : array();
		$signals = array(
			'token'                 => (string) ( $signal['token'] ?? '' ),
			'js_timestamp'          => (string) ( $signal['js_timestamp'] ?? '' ),
			'events'                => (string) ( $signal['events'] ?? '' ),
			// Der Leadlotse-Honeypot faengt vorher ab; Wellenbrecher injiziert hier keinen.
			'honeypot'              => '',
			'original_field_posted' => false,
			// PoW-Loesung: Leadlotse fuehrt sie nicht in context.signals, sie liegt
			// aber als injiziertes fields[wb_pow] im Roh-Submit (Pro; sonst ungenutzt).
			'pow_solution'          => (string) ( $input['wb_pow'] ?? '' ),
		);

		$user = array(
			'can_moderate'         => current_user_can( 'moderate_comments' ) || current_user_can( 'hs_manage_spam' ),
			'has_approved_comment' => '' !== $email && $this->lookup->has_approved_comment( $email ),
			'has_history'          => false,
			'is_verified_buyer'    => false,
			'is_allowlisted'       => '' !== $email && $this->allowlist->contains( $email ),
		);

		$meta = array(
			'form_id' => $form_id,
			// Leadlotse-Formulare tragen die injizierten wb_*-Felder: Token-/JS-Regeln aktiv.
			'skip_token_rules' => false,
		);

		return new Context( 'leadlotse', $fields, $server, $signals, $user, $meta );
	}
}
