<?php

declare(strict_types=1);

namespace BotGuard\Services\Detection\Modules;

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

/**
 * HTTP header fingerprinting module.
 *
 * Real browsers send a rich, self-consistent set of headers. Automated clients
 * frequently omit Accept-Language / Accept-Encoding, skip the Sec-Fetch-* and
 * Sec-CH-UA client hints (Chromium), or present combinations no browser would.
 * This module rewards a coherent header fingerprint.
 */
final class HttpHeaderModule extends AbstractModule
{
    public function key(): string
    {
        return 'headers';
    }

    public function analyze(SignalBag $signals): ModuleResult
    {
        $accept = $signals->header('accept') ?? '';
        $lang = $signals->header('accept-language') ?? '';
        $encoding = $signals->header('accept-encoding') ?? '';
        $ua = strtolower($signals->userAgent());
        $isChromium = str_contains($ua, 'chrome') || str_contains($ua, 'edg/');
        $parsed = UserAgentParser::parse($signals->userAgent());

        $this->detail('accept', $accept);
        $this->detail('accept_language', $lang);
        $this->detail('accept_encoding', $encoding);

        // Core headers every browser sends.
        $this->check('Accept header present', $accept !== '', 1.5);
        $this->check('Accept-Language present', $lang !== '', 2.0);
        $this->check('Accept-Encoding present', $encoding !== '', 1.5);

        // gzip/br compression is universally advertised by browsers.
        $this->check('Advertises compression', str_contains($encoding, 'gzip') || str_contains($encoding, 'br'), 1.0);

        // Browsers request text/html on top-level navigations.
        $this->check('Accepts text/html', $accept === '' || str_contains($accept, 'text/html') || str_contains($accept, '*/*'), 1.0);

        // Chromium client hints (Sec-CH-UA) and Sec-Fetch metadata.
        if ($isChromium) {
            $this->check('Sec-CH-UA present (Chromium)', $signals->hasHeader('sec-ch-ua'), 1.5);
            $this->check('Sec-Fetch-* present (Chromium)', $signals->hasHeader('sec-fetch-site') || $signals->hasHeader('sec-fetch-mode'), 1.5);
        }

        // Connection header sanity (should not be a bizarre value).
        $connection = strtolower($signals->header('connection') ?? '');
        $this->check('Sane Connection header', $connection === '' || in_array($connection, ['keep-alive', 'close', 'upgrade'], true), 0.5);

        // Inconsistency: UA claims a browser but the browser-only headers are all absent.
        $browserHeadersMissing = $accept === '' && $lang === '' && $encoding === '';
        $this->check('Header set consistent with UA', ! ($parsed['browser'] !== 'Unknown' && $browserHeadersMissing), 2.0);

        return $this->result();
    }
}
