<?php
declare(strict_types=1);
namespace ConfManager\Security;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}


use ConfManager\Traits\Singleton;

/**
 * ボット防御（ハニーポット + 署名付きタイムスタンプ）
 *
 * 外部サービス（reCAPTCHA等）に依存しない軽量なボット対策。
 * フロントエンドから送信される _hp（ハニーポット）、_ts（タイムスタンプ）、
 * _ts_sig（HMAC署名）を検証する。
 *
 * @since 1.2.0
 */
class BotGuard {

	use Singleton;

	/**
	 * 最小経過時間（秒）: フォーム表示からこの秒数未満の送信はボットと判定
	 */
	private const MIN_ELAPSED = 2;

	/**
	 * 最大経過時間（秒）: フォーム表示からこの秒数超過の送信は期限切れと判定
	 */
	private const MAX_ELAPSED = 1800;

	protected function init(): void {}

	/**
	 * チャレンジデータを生成する（フロントエンドに渡す）
	 *
	 * @return array{_ts: int, _ts_sig: string}
	 */
	public function generate_challenge(): array {
		$ts  = time();
		$sig = hash_hmac( 'sha256', (string) $ts, wp_salt( 'nonce' ) );

		return [
			'_ts'     => $ts,
			'_ts_sig' => $sig,
		];
	}

	/**
	 * リクエストのボットガードパラメータを検証する
	 *
	 * @param \WP_REST_Request $request RESTリクエスト
	 * @return true|\WP_Error 検証成功ならtrue、失敗ならWP_Error
	 */
	public function verify( \WP_REST_Request $request ): true|\WP_Error {
		// 開発環境で無効化可能
		if ( defined( 'CONF_MANAGER_DISABLE_BOT_GUARD' ) && CONF_MANAGER_DISABLE_BOT_GUARD ) {
			return true;
		}

		// ハニーポット検証: 値が空でなければボット
		$hp = $request->get_param( '_hp' );
		if ( ! empty( $hp ) ) {
			AuditLog::instance()->log( 'bot_guard_honeypot', 'security', null, [
				'ip' => RateLimiter::instance()->get_client_ip(),
			] );

			return new \WP_Error(
				'bot_detected',
				__( 'The request could not be processed.', 'conferencemanager' ),
				[ 'status' => 403 ]
			);
		}

		// タイムスタンプ検証
		$ts     = $request->get_param( '_ts' );
		$ts_sig = $request->get_param( '_ts_sig' );

		if ( empty( $ts ) || empty( $ts_sig ) ) {
			return new \WP_Error(
				'missing_challenge',
				__( 'Security verification data is missing. Please reload the page.', 'conferencemanager' ),
				[ 'status' => 403 ]
			);
		}

		$ts = (int) $ts;

		// 署名検証（改竄防止）
		$expected_sig = hash_hmac( 'sha256', (string) $ts, wp_salt( 'nonce' ) );
		if ( ! hash_equals( $expected_sig, (string) $ts_sig ) ) {
			return new \WP_Error(
				'invalid_challenge',
				__( 'Security verification failed. Please reload the page.', 'conferencemanager' ),
				[ 'status' => 403 ]
			);
		}

		// 経過時間チェック
		$elapsed = time() - $ts;

		if ( $elapsed < self::MIN_ELAPSED ) {
			AuditLog::instance()->log( 'bot_guard_too_fast', 'security', null, [
				'elapsed' => $elapsed,
				'ip'      => RateLimiter::instance()->get_client_ip(),
			] );

			return new \WP_Error(
				'too_fast',
				__( 'Too many requests. Please wait a moment and try again.', 'conferencemanager' ),
				[ 'status' => 429 ]
			);
		}

		if ( $elapsed > self::MAX_ELAPSED ) {
			return new \WP_Error(
				'challenge_expired',
				__( 'Security verification has expired. Please reload the page.', 'conferencemanager' ),
				[ 'status' => 403 ]
			);
		}

		return true;
	}
}
