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

namespace Hafenstudios\Wellenbrecher\Engine;

use Hafenstudios\Core\Events\EventBus;

defined( 'ABSPATH' ) || exit;

/**
 * Der zentrale Bewertungskern (Bauplan Kap. 4). NUR die Engine bewertet;
 * Kanaele sammeln Kontext und setzen das Urteil um.
 *
 * Stufen-Pipeline mit Early-Exit in beide Richtungen:
 * - Stufe 0 (Gates/Trust) bis Stufe 2 (Inhalt) laufen immer.
 * - Stufe 3 (teuer) laeuft NUR, wenn der Zwischenscore im Graubereich
 *   25-79 liegt (darunter lohnt sie nicht, darueber ist der Block sicher).
 * - Sobald der Score die Block-Schwelle erreicht, bricht die Pipeline ab:
 *   Punkte oberhalb der Schwelle aendern die Zone nicht mehr, und
 *   entlastende Regeln gibt es nur in Stufe 0 (Trust, laeuft zuerst) und
 *   Stufe 3 (laeuft ab Block-Schwelle ohnehin nicht mehr).
 *
 * Fail-open (Produktversprechen Nr. 6): wirft eine Regel eine Exception,
 * wird sie uebersprungen und per error_log gemeldet; ein interner Fehler
 * darf nie Inhalte verschlucken.
 */
class ScoreEngine {

	public const STAGE3_MIN = 25;
	public const STAGE3_MAX = 79;

	/**
	 * @var array<int, RuleInterface>
	 */
	private array $rules;

	/**
	 * @var array<string, mixed>
	 */
	private array $settings;

	/**
	 * @param array<int, RuleInterface> $rules    Regelsatz (Default aus RuleRegistry).
	 * @param array<string, mixed>      $settings Settings-Baum (zones.*).
	 */
	public function __construct( array $rules, array $settings = array() ) {
		$this->rules    = $rules;
		$this->settings = $settings;
	}

	/**
	 * Bewertet einen Kontext und liefert das Urteil.
	 *
	 * @param Context $context Bewertungs-Kontext.
	 * @return Verdict
	 */
	public function assess( Context $context ): Verdict {
		$start = microtime( true );

		$quarantine = $this->threshold( 'quarantine', 40 );
		$block      = $this->threshold( 'block', 80 );

		// Gate (Stufe 0, vor allen Regeln): eingeloggt mit Moderations-Cap
		// -> Engine komplett uebersprungen (Bauplan Kap. 6, Stufe 0).
		if ( $context->user_flag( 'can_moderate' ) ) {
			return new Verdict( 0, Verdict::ZONE_PASS, array( 'gate_trusted_user' => 0 ), $this->elapsed( $start ) );
		}

		/**
		 * Erlaubt Dritt-Regeln (oeffentliches API-Versprechen, Bauplan Kap. 4.2).
		 *
		 * @param array<int, RuleInterface> $rules   Regelsatz.
		 * @param Context                   $context Bewertungs-Kontext.
		 */
		$rules = EventBus::filter( 'wellenbrecher_rules', $this->rules, $context );
		$rules = array_values(
			array_filter(
				is_array( $rules ) ? $rules : $this->rules,
				static function ( $rule ) {
					return $rule instanceof RuleInterface;
				}
			)
		);

		usort(
			$rules,
			static function ( RuleInterface $a, RuleInterface $b ) {
				return $a->stage() <=> $b->stage();
			}
		);

		$score   = 0;
		$reasons = array();

		foreach ( $rules as $rule ) {
			// Early-Exit nach oben: Block-Schwelle erreicht, Zone steht fest.
			if ( $score >= $block ) {
				break;
			}

			// Early-Exit nach unten: die teure Stufe 3 nur im Graubereich.
			if ( $rule->stage() >= 3 && ( $score < self::STAGE3_MIN || $score > self::STAGE3_MAX ) ) {
				continue;
			}

			try {
				if ( ! $rule->applies( $context ) ) {
					continue;
				}
				$result = $rule->evaluate( $context );
			} catch ( \Throwable $e ) {
				// Fail-open: Regel ueberspringen, Hinweis ins Fehler-Log.
				error_log( 'Wellenbrecher: Regel "' . $rule->id() . '" uebersprungen (' . $e->getMessage() . ')' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
				continue;
			}

			if ( ! $result->hit() ) {
				continue;
			}

			$key             = '' !== $result->reason ? $result->reason : $rule->id();
			$score          += $result->points;
			$reasons[ $key ] = ( $reasons[ $key ] ?? 0 ) + $result->points;
		}

		$zone = Verdict::ZONE_PASS;
		if ( $score >= $block ) {
			$zone = Verdict::ZONE_BLOCK;
		} elseif ( $score >= $quarantine ) {
			$zone = Verdict::ZONE_QUARANTINE;
		}

		return new Verdict( $score, $zone, $reasons, $this->elapsed( $start ) );
	}

	/**
	 * Zonen-Schwelle aus den Settings (mit sicherem Default).
	 *
	 * @param string $key     zones-Schluessel (quarantine|block).
	 * @param int    $default Standardwert.
	 * @return int
	 */
	private function threshold( string $key, int $default ): int {
		$value = $this->settings['zones'][ $key ] ?? $default;
		return is_numeric( $value ) ? (int) $value : $default;
	}

	/**
	 * Vergangene Zeit seit $start in Millisekunden.
	 *
	 * @param float $start microtime(true)-Startwert.
	 * @return float
	 */
	private function elapsed( float $start ): float {
		return round( ( microtime( true ) - $start ) * 1000, 3 );
	}
}
