<?php

declare(strict_types=1);

namespace BotGuard\Tests\Unit;

use BotGuard\Helpers\UserAgentParser;
use BotGuard\Services\Detection\Modules\BrowserFingerprintModule;
use BotGuard\Services\Detection\Modules\HoneypotModule;
use BotGuard\Services\Detection\Modules\HttpHeaderModule;
use BotGuard\Services\Detection\Modules\JavaScriptModule;
use BotGuard\Services\Detection\Modules\LinkInteractionModule;
use BotGuard\Services\Detection\Modules\MouseModule;
use BotGuard\Services\Detection\Modules\UserAgentModule;
use PHPUnit\Framework\TestCase;

/**
 * Focused per-module tests: each module should score the human fixture higher
 * than the bot fixture on its own dimension.
 */
final class ModulesTest extends TestCase
{
    public function testUserAgentModuleSeparatesHumanFromBot(): void
    {
        $module = new UserAgentModule();
        $human = $module->analyze(SignalFixtures::human())->score;
        $bot = $module->analyze(SignalFixtures::bot())->score;

        $this->assertGreaterThan(0.7, $human);
        $this->assertLessThan(0.5, $bot);
    }

    public function testHeaderModuleSeparatesHumanFromBot(): void
    {
        $module = new HttpHeaderModule();
        $this->assertGreaterThan(
            $module->analyze(SignalFixtures::bot())->score,
            $module->analyze(SignalFixtures::human())->score,
        );
    }

    public function testFingerprintModuleFlagsSoftwareRenderer(): void
    {
        $module = new BrowserFingerprintModule();
        $bot = $module->analyze(SignalFixtures::bot());

        $this->assertLessThan(0.5, $bot->score);
        $this->assertContains('Hardware WebGL renderer', $bot->failed);
    }

    public function testJavaScriptModuleCatchesWebdriver(): void
    {
        $module = new JavaScriptModule();
        $bot = $module->analyze(SignalFixtures::bot());

        $this->assertContains('navigator.webdriver not set', $bot->failed);
    }

    public function testHoneypotCatchesFilledDecoy(): void
    {
        $module = new HoneypotModule();
        $bot = $module->analyze(SignalFixtures::bot());
        $human = $module->analyze(SignalFixtures::human());

        $this->assertSame(1.0, $human->score);
        $this->assertLessThan(0.2, $bot->score);
        $this->assertContains('Hidden email decoy empty', $bot->failed);
    }

    public function testUserAgentParserDetectsHeadless(): void
    {
        $parsed = UserAgentParser::parse('Mozilla/5.0 (X11; Linux x86_64) HeadlessChrome/126.0 Safari/537.36');
        $this->assertTrue($parsed['is_headless']);
        $this->assertTrue($parsed['is_bot']);
        $this->assertSame('bot', $parsed['device_type']);
    }

    public function testUserAgentParserDetectsRealChrome(): void
    {
        $parsed = UserAgentParser::parse('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36');
        $this->assertSame('Chrome', $parsed['browser']);
        $this->assertSame('126.0.0.0', $parsed['version']);
        $this->assertFalse($parsed['is_bot']);
        $this->assertSame('Windows 10/11', $parsed['os']);
    }

    public function testEmptyUserAgentIsBot(): void
    {
        $parsed = UserAgentParser::parse('');
        $this->assertTrue($parsed['empty']);
        $this->assertSame('bot', $parsed['device_type']);
    }

    public function testMouseModuleSeparatesHumanFromBot(): void
    {
        $module = new MouseModule();
        $human = $module->analyze(SignalFixtures::human())->score;
        $bot = $module->analyze(SignalFixtures::bot())->score;

        $this->assertGreaterThan(0.8, $human);
        $this->assertLessThan(0.5, $bot); // no mouse data on a non-touch client
    }

    public function testLinkInteractionModuleFlagsInstantClicks(): void
    {
        $module = new LinkInteractionModule();
        $human = $module->analyze(SignalFixtures::human());
        $bot = $module->analyze(SignalFixtures::bot());

        $this->assertGreaterThan(0.7, $human->score);
        $this->assertLessThan(0.5, $bot->score);
        $this->assertContains('No instant-click pattern', $bot->failed);
    }

    public function testKeyboardNeutralWhenNoTyping(): void
    {
        // The slider flow requires no typing; absence must not read as bot.
        $score = (new \BotGuard\Services\Detection\Modules\KeyboardModule())
            ->analyze(SignalFixtures::human())->score;
        $this->assertGreaterThanOrEqual(0.8, $score);
    }
}
