<?php
if (!defined('ABSPATH')) {
    exit;
}

class BotBlocker_Detector {
    private static $registry = null;
    private static $trust = null;

    public static function init() {
        add_action('bot_blocker_verify_identity', [__CLASS__, 'verify_identity_async'], 10, 2);
    }

    public static function registry() {
        if (self::$registry !== null) {
            return self::$registry;
        }

        $raw = include BOT_BLOCKER_DIR . 'data/bot-registry.php';
        self::$registry = self::sanitize_registry(is_array($raw) ? $raw : []);
        return self::$registry;
    }

    public static function trust() {
        if (self::$trust === null) {
            $raw = include BOT_BLOCKER_DIR . 'data/trust-families.php';
            self::$trust = is_array($raw) ? $raw : [];
        }
        return self::$trust;
    }

    public static function detect($ua, $path, $ip) {
        $ua = (string) $ua;
        $path = (string) $path;
        $result = ['matched' => false, 'bot_name' => '', 'category' => '', 'claimed_identity' => '', 'verification_state' => '', 'matched_rule' => ''];

        if ($ua === '') {
            return $result;
        }

        $low = wp_strip_all_tags(strtolower($ua));

        foreach (['sqlmap', 'nikto', 'wpscan', 'acunetix', 'masscan', 'nmap', 'dirbuster'] as $token) {
            if (strpos($low, $token) !== false) {
                return ['matched' => true, 'bot_name' => $token, 'category' => 'scanner_bots', 'claimed_identity' => '', 'verification_state' => '', 'matched_rule' => 'scanner-token'];
            }
        }

        foreach (['python-requests', 'aiohttp', 'scrapy', 'go-http-client', 'curl/', 'wget/', 'node-fetch', 'libwww-perl'] as $token) {
            if (strpos($low, $token) !== false) {
                return ['matched' => true, 'bot_name' => $token, 'category' => 'scraper_bots', 'claimed_identity' => '', 'verification_state' => '', 'matched_rule' => 'scraper-token'];
            }
        }

        $sensitive_paths = preg_split('/\r\n|\r|\n/', (string) BotBlocker_Settings::get()['sensitive_paths']);
        foreach ($sensitive_paths as $sensitive_path) {
            $sensitive_path = trim((string) $sensitive_path);
            if ($sensitive_path !== '' && strpos($path, $sensitive_path) !== false) {
                return ['matched' => true, 'bot_name' => __('Sensitive path probe', 'sera-bot-blocker'), 'category' => 'sensitive_paths', 'claimed_identity' => '', 'verification_state' => '', 'matched_rule' => 'sensitive-path'];
            }
        }

        foreach (self::trust() as $family => $config) {
            foreach ((array) ($config['tokens'] ?? []) as $token) {
                if ($token !== '' && stripos($ua, $token) !== false) {
                    $state = self::get_identity_state($ip, $family);
                    return [
                        'matched'            => true,
                        'bot_name'           => $family,
                        'category'           => $state === 'verified' ? 'verified_bots' : ($state === 'failed' ? 'fake_trusted_bots' : 'claimed_trusted_bots'),
                        'claimed_identity'   => $family,
                        'verification_state' => $state,
                        'matched_rule'       => 'trust-family:' . $token,
                    ];
                }
            }
        }

        foreach (self::registry() as $entry) {
            foreach ((array) ($entry['tokens'] ?? []) as $token) {
                if ($token !== '' && stripos($ua, $token) !== false) {
                    return [
                        'matched'            => true,
                        'bot_name'           => (string) ($entry['name'] ?? $token),
                        'category'           => (string) ($entry['category'] ?? 'service_bots'),
                        'claimed_identity'   => '',
                        'verification_state' => '',
                        'matched_rule'       => (string) $token,
                    ];
                }
            }
        }

        return $result;
    }

    public static function verify_identity_async($ip, $family) {
        $settings = BotBlocker_Settings::get();
        if (empty($settings['dns_verification_enabled'])) {
            return;
        }

        $trust = self::trust();
        if (empty($trust[$family]['suffixes']) || !filter_var($ip, FILTER_VALIDATE_IP)) {
            return;
        }

        $state = self::perform_identity_verification($ip, (array) $trust[$family]['suffixes']);
        $ttl = $state === 'pending' ? HOUR_IN_SECONDS : 12 * HOUR_IN_SECONDS;
        set_transient(self::identity_cache_key($family, $ip), $state, $ttl);
        delete_transient(self::identity_pending_key($family, $ip));
    }

    private static function sanitize_registry(array $registry) {
        $blocked_tokens = [
            'bot',
            'search',
            'crawler',
            'monitor',
            'preview',
            'spider',
            'Magento-2',
        ];

        $clean_registry = [];
        foreach ($registry as $entry) {
            if (empty($entry['tokens']) || empty($entry['name']) || empty($entry['category'])) {
                continue;
            }

            $tokens = [];
            foreach ((array) $entry['tokens'] as $token) {
                $token = trim((string) $token);
                if ($token === '') {
                    continue;
                }
                if (in_array(strtolower($token), array_map('strtolower', $blocked_tokens), true)) {
                    continue;
                }
                if (strlen($token) < 4 && preg_match('/^[A-Za-z]+$/', $token)) {
                    continue;
                }
                $tokens[] = $token;
            }

            $tokens = array_values(array_unique($tokens));
            usort($tokens, static function ($left, $right) {
                return strlen($right) <=> strlen($left);
            });

            if (!$tokens) {
                continue;
            }

            $entry['tokens'] = $tokens;
            $clean_registry[] = $entry;
        }

        return $clean_registry;
    }

    private static function get_identity_state($ip, $family) {
        $settings = BotBlocker_Settings::get();
        if (empty($settings['dns_verification_enabled']) || !filter_var($ip, FILTER_VALIDATE_IP)) {
            return 'pending';
        }

        $cache_key = self::identity_cache_key($family, $ip);
        $cached = get_transient($cache_key);
        if (is_string($cached) && in_array($cached, ['verified', 'failed', 'pending'], true)) {
            return $cached;
        }

        $pending_key = self::identity_pending_key($family, $ip);
        if (!get_transient($pending_key)) {
            set_transient($pending_key, 1, 30 * MINUTE_IN_SECONDS);
            wp_schedule_single_event(time() + 5, 'bot_blocker_verify_identity', [$ip, $family]);
        }

        return 'pending';
    }

    private static function perform_identity_verification($ip, array $suffixes) {
        $host = @gethostbyaddr($ip);
        if (!$host || $host === $ip) {
            return 'pending';
        }

        $host_lc = strtolower(rtrim($host, '.'));
        $suffix_match = false;
        foreach ($suffixes as $suffix) {
            $suffix = strtolower(ltrim((string) $suffix, '.'));
            if ($suffix !== '' && self::ends_with($host_lc, $suffix)) {
                $suffix_match = true;
                break;
            }
        }

        if (!$suffix_match) {
            return 'failed';
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            $records = dns_get_record($host, DNS_AAAA);
            if (!is_array($records)) {
                return 'failed';
            }
            foreach ($records as $record) {
                if (!empty($record['ipv6']) && strcasecmp($record['ipv6'], $ip) === 0) {
                    return 'verified';
                }
            }
            return 'failed';
        }

        $ips = @gethostbynamel($host);
        if (!$ips || !in_array($ip, $ips, true)) {
            return 'failed';
        }

        return 'verified';
    }

    private static function identity_cache_key($family, $ip) {
        return 'bot_blocker_identity_' . md5($family . '|' . $ip);
    }

    private static function identity_pending_key($family, $ip) {
        return 'bot_blocker_identity_pending_' . md5($family . '|' . $ip);
    }

    private static function ends_with($haystack, $needle) {
        if ($needle === '') {
            return true;
        }
        return substr($haystack, -strlen($needle)) === $needle;
    }
}
