<?php
declare(strict_types=1);

namespace Instinctor\Core\Security;


if (!defined('ABSPATH')) exit;
use Instinctor\Core\Config;

/**
 * Spam — anti-spam utilities for Instinctor forms.
 *
 * Time-based honeypot: hidden field + submission timing.
 * Bots fill hidden fields instantly; this rejects submissions
 * faster than MIN_SUBMIT_SECS seconds.
 *
 * Usage in template:
 *   echo Spam::honeypotField();
 *
 * On submit:
 *   if (!Spam::validateHoneypot()) { // reject }
 *
 * No configuration required.
 */
class Spam
{
    private const HONEYPOT_FIELD  = '_instinctor_hp';
    private const TIMING_FIELD    = '_instinctor_ts';
    private const MIN_SUBMIT_SECS = 3;
    private const HMAC_KEY_OPTION = 'instinctor_honeypot_secret';

    /**
     * Render hidden honeypot input + timing token inside a <form>.
     */
    public static function honeypotField(): string
    {
        $ts    = time();
        $token = self::signTimestamp($ts);

        return sprintf(
            '<div style="position:absolute;left:-9999px;top:-9999px;opacity:0;pointer-events:none;" aria-hidden="true">' .
            '<input type="text" name="%s" value="" tabindex="-1" autocomplete="off">' .
            '</div>' .
            '<input type="hidden" name="%s" value="%s">',
            esc_attr(self::HONEYPOT_FIELD),
            esc_attr(self::TIMING_FIELD),
            esc_attr($ts . ':' . $token)
        );
    }

    /**
     * Validate honeypot on form submission.
     * Returns false if submission looks like a bot.
     */
    public static function validateHoneypot(array $post = []): bool
    {
        if (empty($post)) {
            $post = $_POST; // phpcs:ignore WordPress.Security.NonceVerification
        }

        if (!empty($post[self::HONEYPOT_FIELD])) {
            return false;
        }

        $tsField = $post[self::TIMING_FIELD] ?? '';
        if (empty($tsField)) {
            return false;
        }

        [$ts, $sig] = array_pad(explode(':', $tsField, 2), 2, '');

        if (!is_numeric($ts) || !hash_equals(self::signTimestamp((int) $ts), $sig)) {
            return false;
        }

        $elapsed = time() - (int) $ts;
        if ($elapsed < self::MIN_SUBMIT_SECS) {
            return false;
        }

        if ($elapsed > 3600) {
            return false;
        }

        return true;
    }

    private static function signTimestamp(int $ts): string
    {
        return substr(hash_hmac('sha256', (string) $ts, self::getHmacSecret()), 0, 16);
    }

    private static function getHmacSecret(): string
    {
        if (function_exists('get_option')) {
            $secret = get_option(self::HMAC_KEY_OPTION, '');
            if (empty($secret)) {
                $secret = bin2hex(random_bytes(32));
                update_option(self::HMAC_KEY_OPTION, $secret);
            }
            return $secret;
        }

        $secret = Config::get(self::HMAC_KEY_OPTION, '');
        if (empty($secret)) {
            $secret = bin2hex(random_bytes(32));
            Config::set(self::HMAC_KEY_OPTION, $secret);
        }
        return $secret;
    }
}
