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

namespace Hafenstudios\Wellenbrecher\Rules;

use Hafenstudios\Wellenbrecher\Engine\Context;
use Hafenstudios\Wellenbrecher\Engine\RuleInterface;
use Hafenstudios\Wellenbrecher\Engine\RuleResult;
use Hafenstudios\Wellenbrecher\Pro\AbuseIpdb\AbuseIpdbClient;
use Hafenstudios\Wellenbrecher\Support\IpHasher;

defined( 'ABSPATH' ) || exit;

/**
 * AbuseIPDB-Reputationsregel (Pro, Stufe 3, W5b Technik A9). Als Stufe-3-Regel
 * laeuft sie ohnehin NUR im Graubereich (Zwischenscore 25-79, ScoreEngine) -
 * unauffaellige und sicher schlechte Vorgaenge kosten nie einen API-Call.
 *
 * Punkte = confidence (0-100) x 0,3, gedeckelt bei 30. Das Ergebnis wird 12 h
 * als Transient gecacht; der Cache-Schluessel ist der IP-HMAC, NIE die
 * Klartext-IP. Jeder Fehler ist fail-open (kein Aufschlag), Fehler werden nicht
 * gecacht (schnelle Erholung).
 */
class AbuseIpdbRule implements RuleInterface {

	public const ID = 'abuseipdb';

	public const CACHE_PREFIX = 'wb_abuseipdb_';

	public const CACHE_TTL = 12 * HOUR_IN_SECONDS;

	public const MULTIPLIER = 0.3;

	public const CAP = 30;

	protected AbuseIpdbClient $client;

	protected IpHasher $hasher;

	/**
	 * @param AbuseIpdbClient $client AbuseIPDB-Client (mit BYO-Key).
	 * @param IpHasher        $hasher IP-HMAC (nur fuer den Cache-Schluessel).
	 */
	public function __construct( AbuseIpdbClient $client, IpHasher $hasher ) {
		$this->client = $client;
		$this->hasher = $hasher;
	}

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

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

	/**
	 * Nur aktiv mit hinterlegtem Key und aufgeloester IP.
	 *
	 * @param Context $context Bewertungs-Kontext.
	 * @return bool
	 */
	public function applies( Context $context ): bool {
		return $this->client->is_configured() && '' !== $context->server( 'remote_addr' );
	}

	public function evaluate( Context $context ): RuleResult {
		$ip = $context->server( 'remote_addr' );

		$confidence = $this->cached_confidence( $ip );
		if ( $confidence < 0 ) {
			// Netz-/Parsefehler - fail-open, nichts aufschlagen.
			return RuleResult::none( self::ID );
		}

		$points = (int) min( self::CAP, round( $confidence * self::MULTIPLIER ) );
		if ( $points <= 0 ) {
			return RuleResult::none( self::ID );
		}
		return new RuleResult( $points, self::ID );
	}

	/**
	 * Confidence mit 12-h-Transient-Cache. Cache-Schluessel = IP-HMAC (nie
	 * Klartext). Fehler (-1) werden bewusst nicht gecacht.
	 *
	 * @param string $ip Klartext-IP.
	 * @return int Confidence 0-100 oder -1 bei Fehler.
	 */
	protected function cached_confidence( string $ip ): int {
		$key    = self::CACHE_PREFIX . $this->hasher->hash( $ip );
		$cached = get_transient( $key );
		if ( false !== $cached ) {
			return (int) $cached;
		}

		$confidence = $this->client->confidence( $ip );
		if ( $confidence < 0 ) {
			return -1;
		}

		set_transient( $key, $confidence, self::CACHE_TTL );
		return $confidence;
	}
}
