<?php

declare(strict_types=1);

namespace BotGuard\Tests\Unit;

use BotGuard\Core\Exceptions\HttpException;
use BotGuard\Core\Request;
use BotGuard\Core\Router;
use BotGuard\Core\Validator;
use BotGuard\Services\Captcha\PuzzleProvider;
use BotGuard\Services\Detection\SignalBag;
use PHPUnit\Framework\TestCase;

/**
 * Framework-level unit tests: router matching, validation rules, the puzzle
 * provider and SignalBag accessors.
 */
final class FrameworkTest extends TestCase
{
    private function request(string $method, string $uri): Request
    {
        return new Request([], [], ['REQUEST_METHOD' => $method, 'REQUEST_URI' => $uri], [], '');
    }

    public function testRouterMatchesWithParams(): void
    {
        $router = new Router();
        $router->get('/users/{id}', 'UserController@show');

        $match = $router->match($this->request('GET', '/users/42'));
        $this->assertSame('UserController@show', $match['handler']);
        $this->assertSame('42', $match['params']['id']);
    }

    public function testRouterThrows404ForUnknownPath(): void
    {
        $this->expectException(HttpException::class);
        $this->expectExceptionCode(404);
        (new Router())->match($this->request('GET', '/nope'));
    }

    public function testRouterThrows405ForWrongMethod(): void
    {
        $router = new Router();
        $router->post('/submit', 'C@m');

        try {
            $router->match($this->request('GET', '/submit'));
            $this->fail('Expected HttpException');
        } catch (HttpException $e) {
            $this->assertSame(405, $e->getStatusCode());
        }
    }

    public function testRouterGroupAppliesPrefixAndMiddleware(): void
    {
        $router = new Router();
        $router->group(['prefix' => '/admin', 'middleware' => ['auth']], static function (Router $r): void {
            $r->get('/x', 'C@x', ['admin']);
        });

        $match = $router->match($this->request('GET', '/admin/x'));
        $this->assertSame('C@x', $match['handler']);
        $this->assertSame(['auth', 'admin'], $match['middleware']);
    }

    public function testValidatorRules(): void
    {
        $v = Validator::make(
            ['email' => 'bad', 'password' => '123', 'password_confirmation' => '456'],
            ['email' => 'required|email', 'password' => 'required|minlen:8|confirmed']
        );

        $this->assertTrue($v->fails());
        $this->assertArrayHasKey('email', $v->errors());
        $this->assertArrayHasKey('password', $v->errors());
    }

    public function testValidatorPasses(): void
    {
        $v = Validator::make(
            ['url' => 'https://example.com', 'n' => '5'],
            ['url' => 'required|url', 'n' => 'numeric|min:1|max:10']
        );

        $this->assertTrue($v->passes());
    }

    public function testPuzzleProviderVerifiesWithinTolerance(): void
    {
        $provider = new PuzzleProvider();
        $issued = $provider->issue();
        $target = (int) $issued['answer'];

        $ok = $provider->verify(['position' => $target + 3, 'solve_time_ms' => 1500], (string) $target);
        $this->assertTrue($ok->verified);

        $off = $provider->verify(['position' => $target + 100, 'solve_time_ms' => 1500], (string) $target);
        $this->assertFalse($off->verified);

        $noChallenge = $provider->verify(['position' => 10], null);
        $this->assertFalse($noChallenge->verified);
    }

    public function testSignalBagDotAccess(): void
    {
        $bag = SignalBag::fromArrays(
            ['ip' => '1.2.3.4', 'headers' => ['accept-language' => 'en']],
            ['timing' => ['time_on_page_ms' => 500], 'javascript' => ['enabled' => true]]
        );

        $this->assertSame('1.2.3.4', $bag->ip());
        $this->assertSame('en', $bag->header('Accept-Language'));
        $this->assertSame(500, $bag->clientInt('timing.time_on_page_ms'));
        $this->assertTrue($bag->clientBool('javascript.enabled'));
        $this->assertNull($bag->client('does.not.exist'));
    }
}
