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

namespace Hafenstudios\Wellenbrecher\Rules;

use Hafenstudios\Core\Ai\AiClient;
use Hafenstudios\Core\Ai\AiRequest;
use Hafenstudios\Wellenbrecher\Engine\Context;
use Hafenstudios\Wellenbrecher\Engine\RuleInterface;
use Hafenstudios\Wellenbrecher\Engine\RuleResult;

defined( 'ABSPATH' ) || exit;

/**
 * KI-Zweitmeinung (Pro, Stufe 3, Bauplan 4.5 Nr. 2, W5b). Als Stufe-3-Regel
 * fragt sie NUR im Graubereich (Zwischenscore 25-79) - genau die Grenzfaelle,
 * bei denen sich ein LLM-Aufruf lohnt.
 *
 * Nutzt die Core-KI-Fassade (Core::instance()->ai(), Etappe H1) - KEIN eigener
 * HTTP-Client. Zusaetzliches Wellenbrecher-Opt-in ($enabled) NEBEN dem
 * Core-Hauptschalter; beide muessen an sein.
 *
 * Feste Antwortstruktur (System-Prompt): erste Zeile "SPAM|HAM <Konfidenz 0-100>",
 * zweite Zeile eine Ein-Satz-Begruendung. Mapping: SPAM >= 90 -> +30, >= 70 ->
 * +20, >= 50 -> +10; HAM >= 80 -> -15; unparsebar -> 0 (fail-open). Der Rohtext
 * der KI landet NIE in den reasons (nur der stabile Key ai_second_opinion). Die
 * Antwort wird 7 Tage als Transient auf den SHA-256-Hash des Inhalts gecacht.
 */
class AiSecondOpinionRule implements RuleInterface {

	public const ID = 'ai_second_opinion';

	public const PURPOSE = 'spam_check';

	public const CACHE_PREFIX = 'wb_ai_';

	public const CACHE_TTL = 7 * DAY_IN_SECONDS;

	public const MAX_TOKENS = 40;

	/**
	 * Obergrenze des gesendeten Inhalts (Kosten-/Token-Deckel).
	 */
	public const MAX_INPUT_CHARS = 2000;

	/**
	 * Fester System-Prompt (Deutsch) mit erzwungener Antwortstruktur.
	 */
	public const SYSTEM = 'Du bist ein Spam-Filter fuer eine deutschsprachige Website. Bewerte den folgenden Beitrag daraufhin, ob er Spam ist. Antworte in genau zwei Zeilen. Erste Zeile: das Wort SPAM oder das Wort HAM, dann ein Leerzeichen und eine Konfidenz als ganze Zahl von 0 bis 100. Zweite Zeile: eine kurze Begruendung in einem Satz. Gib nichts anderes aus.';

	protected AiClient $ai;

	/**
	 * Eigenes Wellenbrecher-Opt-in (zusaetzlich zum Core-Hauptschalter).
	 */
	protected bool $enabled;

	/**
	 * @param AiClient $ai      Core-KI-Fassade.
	 * @param bool     $enabled Wellenbrecher-Opt-in (Default aus).
	 */
	public function __construct( AiClient $ai, bool $enabled ) {
		$this->ai      = $ai;
		$this->enabled = $enabled;
	}

	public function id(): string {
		return self::ID;
	}

	public function stage(): int {
		return 3;
	}

	/**
	 * Nur aktiv, wenn das Wellenbrecher-Opt-in UND der Core-Hauptschalter an sind
	 * und ueberhaupt Inhalt vorliegt.
	 *
	 * @param Context $context Bewertungs-Kontext.
	 * @return bool
	 */
	public function applies( Context $context ): bool {
		return $this->enabled && $this->ai->is_enabled() && '' !== $this->content( $context );
	}

	public function evaluate( Context $context ): RuleResult {
		$content = $this->content( $context );
		if ( '' === $content ) {
			return RuleResult::none( self::ID );
		}

		$points = $this->cached_points( $content );
		if ( 0 === $points ) {
			return RuleResult::none( self::ID );
		}
		return new RuleResult( $points, self::ID );
	}

	/**
	 * Punkte mit 7-Tage-Transient-Cache auf den SHA-256-Hash des Inhalts. Ein
	 * gueltiges KI-Ergebnis (auch 0) wird gecacht; ein Anbieter-Fehler (ok=false)
	 * NICHT (fail-open, naechster Versuch spaeter).
	 *
	 * @param string $content Gesendeter Inhalt.
	 * @return int Punkte (0 = kein Treffer/unparsebar/Fehler).
	 */
	protected function cached_points( string $content ): int {
		$key    = self::CACHE_PREFIX . hash( 'sha256', $content );
		$cached = get_transient( $key );
		if ( false !== $cached ) {
			return (int) $cached;
		}

		$result = $this->ai->complete(
			new AiRequest( self::PURPOSE, $content, self::SYSTEM, self::MAX_TOKENS )
		);
		if ( ! $result->ok ) {
			// Hauptschalter aus, Netz-/Anbieterfehler - fail-open, nicht cachen.
			return 0;
		}

		$points = self::classify_points( $result->text );
		set_transient( $key, $points, self::CACHE_TTL );
		return $points;
	}

	/**
	 * Baut den zu bewertenden Inhalt (Text plus Autor-Name/-URL, ohne die
	 * E-Mail-Adresse = Datensparsamkeit). Auf MAX_INPUT_CHARS gedeckelt.
	 *
	 * @param Context $context Bewertungs-Kontext.
	 * @return string
	 */
	protected function content( Context $context ): string {
		$text = trim(
			$context->field( 'content' ) . "\n"
			. $context->field( 'author_name' ) . ' '
			. $context->field( 'author_url' )
		);
		if ( mb_strlen( $text ) > self::MAX_INPUT_CHARS ) {
			$text = mb_substr( $text, 0, self::MAX_INPUT_CHARS );
		}
		return trim( $text );
	}

	/**
	 * Parst die KI-Antwort und bildet sie auf Punkte ab (reine, testbare Logik).
	 * Erste Zeile: SPAM|HAM plus Konfidenz. Unparsebar -> 0.
	 *
	 * @param string $text KI-Rohtext.
	 * @return int
	 */
	public static function classify_points( string $text ): int {
		$lines = preg_split( '/\r\n|\r|\n/', trim( $text ) );
		$first = strtoupper( trim( (string) ( $lines[0] ?? '' ) ) );
		if ( '' === $first ) {
			return 0;
		}

		$is_spam = (bool) preg_match( '/\bSPAM\b/', $first );
		$is_ham  = (bool) preg_match( '/\bHAM\b/', $first );
		if ( $is_spam === $is_ham ) {
			// Weder eindeutig SPAM noch HAM (oder beides) - unparsebar.
			return 0;
		}

		$confidence = preg_match( '/(\d{1,3})/', $first, $m ) ? min( 100, (int) $m[1] ) : 0;

		if ( $is_spam ) {
			if ( $confidence >= 90 ) {
				return 30;
			}
			if ( $confidence >= 70 ) {
				return 20;
			}
			if ( $confidence >= 50 ) {
				return 10;
			}
			return 0;
		}

		// HAM: nur bei hoher Konfidenz ein Vertrauens-Abzug.
		return $confidence >= 80 ? -15 : 0;
	}
}
