<?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\Rest\TokenController;
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;

/**
 * WooCommerce-Checkout, klassisch (Pro, Bauplan Kap. 7.4). Honeypot + Signal-
 * Felder via `woocommerce_review_order_before_submit`, Bewertung in
 * `woocommerce_after_checkout_validation` ($data, $errors).
 *
 * Geld-Pfad, darum NIEMALS still verwerfen (anders als der Leadlotse-Kanal):
 * block UND quarantine fuehren zu $errors->add() mit einer generischen Meldung,
 * damit der Kunde die Bestellung wiederholen kann und nichts unbemerkt
 * verloren geht (WB-Entscheidung). Der Beobachtungsmodus flaggt nur und laesst
 * die Bestellung durch.
 *
 * Nur mit class_exists-Guards verdrahtet (WooCommerce ist nicht gebundelt).
 * Store-API/Block-Checkout laeuft NICHT durch diese klassischen Hooks und
 * bleibt ein Folge-Release (readme-FAQ nennt das ehrlich).
 */
class WooCheckoutChannel {

	public const HONEYPOT_FIELD = 'wb_hp';

	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;

	protected bool $pow_enabled;

	/**
	 * @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.
	 * @param bool              $pow_enabled    Proof-of-Work-Feld rendern (Pro + Setting an).
	 */
	public function __construct(
		\Closure $engine_factory,
		Settings $settings,
		SpamLogRepository $log,
		StatsRepository $stats,
		IpHasher $hasher,
		Allowlist $allowlist,
		ObservationMode $observation,
		CommentLookup $lookup,
		RequestSnapshot $snapshots,
		bool $pow_enabled = false
	) {
		$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;
		$this->pow_enabled    = $pow_enabled;
	}

	/**
	 * Registriert die Woo-Hooks.
	 *
	 * @return void
	 */
	public function register(): void {
		add_action( 'woocommerce_review_order_before_submit', array( $this, 'render_fields' ) );
		add_action( 'woocommerce_after_checkout_validation', array( $this, 'validate' ), 10, 2 );
	}

	/**
	 * Rendert Honeypot- und Signal-Felder und reiht signals.js ein.
	 *
	 * @return void
	 */
	public function render_fields(): void {
		// Honeypot: fuer Menschen unsichtbar, barrierefrei ausgeblendet.
		echo '<div style="position:absolute!important;left:-9999px;top:-9999px;" aria-hidden="true">';
		echo '<label>' . esc_html__( 'Dieses Feld bitte leer lassen', 'wellenbrecher' )
			. '<input type="text" name="' . esc_attr( self::HONEYPOT_FIELD ) . '" value="" tabindex="-1" autocomplete="new-password" /></label>';
		echo '</div>';

		echo '<input type="hidden" name="' . esc_attr( CommentFormFields::FIELD_TIMESTAMP ) . '" value="' . esc_attr( (string) wp_rand( 0, 250 ) ) . '" />';
		echo '<input type="hidden" name="' . esc_attr( CommentFormFields::FIELD_EVENTS ) . '" value="" />';
		echo '<input type="hidden" name="' . esc_attr( CommentFormFields::FIELD_TOKEN ) . '" value="" />';
		if ( $this->pow_enabled ) {
			echo '<input type="hidden" name="wb_pow" value="" />';
		}

		$this->enqueue_signals();
	}

	/**
	 * Bewertet die Bestellung. block UND quarantine -> Fehler (nie stiller Drop),
	 * Beobachtungsmodus -> nur flaggen.
	 *
	 * @param mixed $data   Gepostete Checkout-Daten.
	 * @param mixed $errors WP_Error-Sammler von WooCommerce.
	 * @return void
	 */
	public function validate( $data, $errors = null ): void {
		$data = is_array( $data ) ? $data : array();

		try {
			$context = $this->build_context( $data );
			$verdict = ( $this->engine_factory )()->assess( $context );
		} catch ( \Throwable $e ) {
			// Fail-open: bei einem internen Fehler laeuft die Bestellung durch.
			error_log( 'Wellenbrecher: Woo-Checkout-Bewertung uebersprungen (' . $e->getMessage() . ')' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			return;
		}

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

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

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

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

		// Geld-Pfad: block UND quarantine ergeben eine ehrliche Log-Zeile und eine
		// generische Ablehnung - niemals ein stilles Verwerfen einer Bestellung.
		$type = Verdict::ZONE_BLOCK === $verdict->zone
			? SpamLogRepository::VERDICT_BLOCKED
			: SpamLogRepository::VERDICT_QUARANTINED;
		$recorder->record( $type, $verdict, $base );

		if ( is_object( $errors ) && method_exists( $errors, 'add' ) ) {
			$errors->add(
				'wb_checkout',
				__( 'Ihre Bestellung konnte nicht geprueft werden. Bitte laden Sie die Seite neu und versuchen Sie es erneut. Bei wiederholten Problemen wenden Sie sich bitte direkt an den Shop.', 'wellenbrecher' )
			);
		}
	}

	/**
	 * Baut den Engine-Context aus den Checkout-Daten und $_POST-Signalen.
	 *
	 * @param array<string, mixed> $data Gepostete Checkout-Daten.
	 * @return Context
	 */
	protected function build_context( array $data ): Context {
		$server = $this->snapshots->build(
			$_SERVER,
			(string) $this->settings->get( 'proxy.trusted_header', '' )
		);

		$email = (string) ( $data['billing_email'] ?? '' );
		$name  = trim( (string) ( $data['billing_first_name'] ?? '' ) . ' ' . (string) ( $data['billing_last_name'] ?? '' ) );

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

		$signals = array(
			'token'                 => $this->post_value( CommentFormFields::FIELD_TOKEN ),
			'js_timestamp'          => $this->post_value( CommentFormFields::FIELD_TIMESTAMP ),
			'events'                => $this->post_value( CommentFormFields::FIELD_EVENTS ),
			'honeypot'              => $this->post_value( self::HONEYPOT_FIELD ),
			'original_field_posted' => false,
			'pow_solution'          => $this->post_value( '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(
			// Der klassische Checkout traegt die wb_*-Felder: Token-/JS-Regeln aktiv.
			'skip_token_rules' => false,
		);

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

	/**
	 * Signal-Wert aus $_POST (nur Skalare, sanitisiert).
	 *
	 * @param string $key POST-Schluessel.
	 * @return string
	 */
	protected function post_value( string $key ): string {
		$value = $_POST[ $key ] ?? ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Spam-Signale des Checkout-Submits.
		if ( ! is_scalar( $value ) ) {
			return '';
		}
		return sanitize_text_field( wp_unslash( (string) $value ) );
	}

	/**
	 * signals.js einreihen (Vanilla, No-Build).
	 *
	 * @return void
	 */
	protected function enqueue_signals(): void {
		if ( ! function_exists( 'wp_enqueue_script' ) ) {
			return;
		}
		wp_enqueue_script(
			'wellenbrecher-signals',
			WELLENBRECHER_URL . 'assets/js/signals.js',
			array(),
			WELLENBRECHER_VERSION,
			true
		);
		wp_localize_script(
			'wellenbrecher-signals',
			'wellenbrecherSignals',
			array(
				'tokenUrl' => esc_url_raw( rest_url( TokenController::REST_NAMESPACE . '/token' ) ),
			)
		);
	}
}
