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

namespace Hafenstudios\Wellenbrecher\Channels;

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;

/**
 * Registrierungs-Kanal (Bauplan Kap. 7.2): duenner Adapter um die Score-Engine
 * fuer die drei Registrierungswege.
 *
 * - registration_errors        Single-Site (wp-login.php?action=register)
 * - wpmu_validate_user_signup  Multisite-Netzwerk-Anmeldung
 * - woocommerce_registration_errors  WooCommerce-Konto (Mein Konto/Checkout)
 *
 * Zwei Zonen: pass -> durchlassen; quarantine UND block -> Registrierung mit
 * generischer Meldung ablehnen (es gibt keine native Registrierungs-Quarantaene)
 * plus Log mit dem EHRLICHEN Verdict (quarantined bzw. blocked) und Stats.
 * Beobachtungsmodus: flagged loggen und durchlassen.
 *
 * Registrierungsformulare tragen (noch) KEINE Wellenbrecher-Hidden-Felder
 * (Token-Ausbau fuer wp-login/Registrierung ist bewusst NICHT Teil von W3):
 * meta.skip_token_rules = true neutralisiert die Token-/JS-Regeln, sonst
 * scoret jede legitime Registrierung "Token fehlt" hoch.
 *
 * MemberJet-Hinweis: MemberJet-Anmeldungen laufen ueber die WordPress-
 * Registrierung und sind damit automatisch mitgeschuetzt - es gibt bewusst
 * keine MemberJet-spezifische Integration.
 *
 * Fail-open (Produktversprechen Nr. 6): wirft die Bewertung eine Exception,
 * laeuft die Registrierung unbewertet weiter.
 */
class RegistrationChannel {

	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;
	}

	/**
	 * Registriert die drei Registrierungs-Hooks.
	 *
	 * @return void
	 */
	public function register(): void {
		add_filter( 'registration_errors', array( $this, 'on_registration_errors' ), 20, 3 );
		add_filter( 'wpmu_validate_user_signup', array( $this, 'on_multisite_signup' ), 20 );
		// woocommerce_registration_errors uebergibt ($errors, $username, $email) -
		// KEIN Passwort (wc_create_new_customer). Signatur entsprechend drei Argumente.
		add_filter( 'woocommerce_registration_errors', array( $this, 'on_woo_registration_errors' ), 20, 3 );
	}

	/**
	 * Single-Site-Registrierung (registration_errors).
	 *
	 * @param mixed  $errors    WP_Error-Sammler.
	 * @param string $login     Bereinigter Benutzername.
	 * @param string $email     E-Mail-Adresse.
	 * @return mixed
	 */
	public function on_registration_errors( $errors, $login = '', $email = '' ) {
		if ( $this->should_reject( (string) $email, (string) $login ) && is_object( $errors ) && method_exists( $errors, 'add' ) ) {
			$errors->add( 'wellenbrecher_registration_blocked', $this->reject_message() );
		}
		return $errors;
	}

	/**
	 * Multisite-Netzwerk-Anmeldung (wpmu_validate_user_signup).
	 *
	 * @param mixed $result Ergebnis-Array mit user_name/user_email/errors (WP_Error).
	 * @return mixed
	 */
	public function on_multisite_signup( $result ) {
		if ( ! is_array( $result ) ) {
			return $result;
		}
		$email  = (string) ( $result['user_email'] ?? '' );
		$login  = (string) ( $result['user_name'] ?? ( $result['orig_username'] ?? '' ) );
		$errors = $result['errors'] ?? null;

		if ( $this->should_reject( $email, $login ) && is_object( $errors ) && method_exists( $errors, 'add' ) ) {
			$errors->add( 'wellenbrecher_registration_blocked', $this->reject_message() );
		}
		return $result;
	}

	/**
	 * WooCommerce-Registrierung (woocommerce_registration_errors). WooCommerce
	 * uebergibt ($errors, $username, $email) - ein Passwort ist NICHT dabei
	 * (wc_create_new_customer), es wird ohnehin nie ausgewertet.
	 *
	 * @param mixed  $errors   WP_Error-Sammler.
	 * @param string $username Benutzername.
	 * @param string $email    E-Mail-Adresse.
	 * @return mixed
	 */
	public function on_woo_registration_errors( $errors, $username = '', $email = '' ) {
		if ( $this->should_reject( (string) $email, (string) $username ) && is_object( $errors ) && method_exists( $errors, 'add' ) ) {
			$errors->add( 'wellenbrecher_registration_blocked', $this->reject_message() );
		}
		return $errors;
	}

	/**
	 * Bewertet die Registrierung, verbucht das Verdict und entscheidet, ob
	 * abgelehnt werden soll.
	 *
	 * @param string $email E-Mail-Adresse.
	 * @param string $name  Benutzername/Login (dient als author_name).
	 * @return bool True = Registrierung ablehnen.
	 */
	protected function should_reject( string $email, string $name ): bool {
		try {
			$context = $this->build_context( $email, $name );
			$verdict = ( $this->engine_factory )()->assess( $context );
		} catch ( \Throwable $e ) {
			// Fail-open: eine unbewertete Registrierung laeuft normal weiter.
			error_log( 'Wellenbrecher: Registrierungs-Bewertung uebersprungen (' . $e->getMessage() . ')' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			return false;
		}

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

		$base = array(
			'channel'      => 'registration',
			'ip_hmac'      => $this->hasher->hash( $context->server( 'remote_addr' ) ),
			'email_domain' => Text::email_domain( $email ),
			'excerpt'      => mb_substr( $name, 0, 200 ),
		);

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

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

		// Ehrlicher Verdict (quarantined bzw. blocked) ins Log, dann ablehnen.
		$recorder->record(
			Verdict::ZONE_BLOCK === $verdict->zone ? SpamLogRepository::VERDICT_BLOCKED : SpamLogRepository::VERDICT_QUARANTINED,
			$verdict,
			$base
		);

		return true;
	}

	/**
	 * Baut den Engine-Context fuer eine Registrierung.
	 *
	 * @param string $email E-Mail-Adresse.
	 * @param string $name  Benutzername (author_name).
	 * @return Context
	 */
	protected function build_context( string $email, string $name ): Context {
		$server = $this->snapshots->build(
			$_SERVER, // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Server-Header-Snapshot, kein Nutzereingabewert.
			(string) $this->settings->get( 'proxy.trusted_header', '' )
		);

		$fields = array(
			'content'      => '',
			'author_name'  => $name,
			'author_url'   => '',
			'author_email' => $email,
		);

		// Registrierungsformulare tragen keine Wellenbrecher-Signalfelder:
		// Token-/JS-Regeln neutralisieren (sonst 100 % False Positives).
		$signals = array(
			'token'                 => '',
			'js_timestamp'          => '',
			'events'                => '',
			'honeypot'              => '',
			'original_field_posted' => false,
		);

		$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(
			'skip_token_rules' => true,
		);

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

	/**
	 * Generische Ablehnungs-Meldung (kein Hinweis auf die Spam-Erkennung,
	 * Bauplan Kap. 7.2).
	 *
	 * @return string
	 */
	protected function reject_message(): string {
		return __( 'Die Registrierung konnte nicht abgeschlossen werden. Bitte versuchen Sie es spaeter erneut.', 'wellenbrecher' );
	}
}
