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

namespace Hafenstudios\Wellenbrecher\Support;

defined( 'ABSPATH' ) || exit;

/**
 * IP-Behandlung ausschliesslich als HMAC mit rotierendem Wochen-Salt
 * (Bauplan Kap. 9). Der Salt wird pro ISO-Woche zufaellig neu erzeugt und
 * der alte verworfen: Wiedererkennung (Rate-Limit, Wiederholungstaeter)
 * funktioniert nur im Wochenfenster, eine Dauer-Identifizierbarkeit ist
 * ausgeschlossen. Klartext-IPs verlassen diese Klasse nie.
 */
class IpHasher {

	public const SALT_OPTION = 'wellenbrecher_ip_salt';

	/**
	 * HMAC (64 Hex-Zeichen, passend zu hs_spam_log.ip_hmac CHAR(64)).
	 *
	 * @param string   $ip   Klartext-IP.
	 * @param int|null $time Unix-Zeit (Default: jetzt, fuer Tests injizierbar).
	 * @return string Leer bei leerer IP.
	 */
	public function hash( string $ip, ?int $time = null ): string {
		if ( '' === trim( $ip ) ) {
			return '';
		}
		return hash_hmac( 'sha256', trim( $ip ), $this->salt( $time ) );
	}

	/**
	 * Ermittelt die Client-IP aus dem Server-Snapshot. Default ist strikt
	 * REMOTE_ADDR (ASB-2.11.4-Lehre: X-Forwarded-For ist trivial faelschbar);
	 * nur ein explizit konfigurierter Trusted-Proxy-Header wird gelesen
	 * (erster Eintrag der Kette).
	 *
	 * @param array<string, mixed> $server         Server-Snapshot (Kleinbuchstaben-Schluessel).
	 * @param string               $trusted_header Konfigurierter Header (z.B. 'x-forwarded-for'), leer = REMOTE_ADDR.
	 * @return string
	 */
	public function client_ip( array $server, string $trusted_header = '' ): string {
		$trusted_header = strtolower( str_replace( '-', '_', trim( $trusted_header ) ) );
		if ( '' !== $trusted_header && ! empty( $server[ $trusted_header ] ) && is_scalar( $server[ $trusted_header ] ) ) {
			$chain = explode( ',', (string) $server[ $trusted_header ] );
			$first = trim( $chain[0] );
			if ( '' !== $first ) {
				return $first;
			}
		}
		$remote = $server['remote_addr'] ?? '';
		return is_scalar( $remote ) ? trim( (string) $remote ) : '';
	}

	/**
	 * Liefert den Salt der aktuellen ISO-Woche; rotiert bei Wochenwechsel
	 * (alter Salt wird verworfen, nicht archiviert).
	 *
	 * @param int|null $time Unix-Zeit (Default: jetzt).
	 * @return string
	 */
	protected function salt( ?int $time = null ): string {
		$week   = gmdate( 'o-W', $time ?? time() );
		$stored = get_option( self::SALT_OPTION, array() );

		if ( is_array( $stored ) && ( $stored['week'] ?? '' ) === $week && ! empty( $stored['salt'] ) ) {
			return (string) $stored['salt'];
		}

		$salt = bin2hex( random_bytes( 32 ) );
		update_option(
			self::SALT_OPTION,
			array(
				'week' => $week,
				'salt' => $salt,
			)
		);
		return $salt;
	}
}
