<?php

declare(strict_types=1);

namespace BotGuard\Services\Detection\Modules;

use BotGuard\Services\Detection\ModuleResult;
use BotGuard\Services\Detection\SignalBag;

/**
 * Referrer analysis module.
 *
 * Classifies the Referer into direct / search / social / unknown and applies a
 * mild signal: organic (search/social) and internal referrers lean human;
 * a malformed referer leans bot. Referrer alone is weak (privacy settings strip
 * it), so this module contributes a small weight and never fully condemns.
 */
final class ReferrerModule extends AbstractModule
{
    private const SEARCH = ['google.', 'bing.', 'duckduckgo.', 'yahoo.', 'yandex.', 'baidu.', 'ecosia.'];
    private const SOCIAL = ['facebook.', 'twitter.', 't.co', 'x.com', 'instagram.', 'linkedin.', 'reddit.', 'youtube.', 'tiktok.', 'pinterest.'];

    public function key(): string
    {
        return 'referrer';
    }

    public function analyze(SignalBag $signals): ModuleResult
    {
        $referer = trim($signals->referer());
        $type = $this->classifyReferer($referer);

        $this->detail('referer', $referer);
        $this->detail('type', $type);

        // A referrer that is present and well-formed is a positive (human) signal;
        // "direct" (empty) is neutral and common, so we score it near the middle.
        $score = match ($type) {
            'search', 'social', 'internal' => 0.85,
            'direct'                        => 0.6,
            'unknown'                       => 0.5,
            'malformed'                     => 0.25,
            default                         => 0.5,
        };

        $passed = [];
        $failed = [];
        if (in_array($type, ['search', 'social', 'internal'], true)) {
            $passed[] = "Organic/internal referrer ({$type})";
        } elseif ($type === 'malformed') {
            $failed[] = 'Malformed referrer header';
        } else {
            $passed[] = "Referrer type: {$type}";
        }

        return $this->scored($score, $passed, $failed, ['referer' => $referer, 'type' => $type]);
    }

    private function classifyReferer(string $referer): string
    {
        if ($referer === '') {
            return 'direct';
        }

        $host = parse_url($referer, PHP_URL_HOST);
        if (! is_string($host) || $host === '') {
            return 'malformed';
        }

        $host = strtolower($host);

        foreach (self::SEARCH as $needle) {
            if (str_contains($host, $needle)) {
                return 'search';
            }
        }
        foreach (self::SOCIAL as $needle) {
            if (str_contains($host, $needle)) {
                return 'social';
            }
        }

        // Same host as the current app URL => internal navigation.
        $appHost = parse_url((string) config('app.url', ''), PHP_URL_HOST);
        if (is_string($appHost) && $appHost !== '' && str_contains($host, strtolower($appHost))) {
            return 'internal';
        }

        return 'unknown';
    }
}
