<?php

use App\Http\Middleware\ForumRateLimitMiddleware;
use cPad\Plugins\Forum\Services\Security\BotProtectionService;
use Illuminate\Http\Exceptions\ThrottleRequestsException;
use Illuminate\Http\Request;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Symfony\Component\HttpFoundation\Response;

uses(Tests\TestCase::class);

it('reports forum throttle violations to bot protection before rethrowing', function () {
    $request = Request::create('/forum/topic/example-topic/reply', 'POST');
    $request->setRouteResolver(static fn (): object => new class {
        public function getName(): string
        {
            return 'forum.topic.reply';
        }
    });

    $throttle = \Mockery::mock(ThrottleRequests::class);
    $botProtection = \Mockery::mock(BotProtectionService::class);

    $exception = new ThrottleRequestsException('Too Many Attempts.', null, [
        'Retry-After' => '42',
        'X-RateLimit-Limit' => '3',
        'X-RateLimit-Remaining' => '0',
    ]);

    $throttle->shouldReceive('handle')
        ->once()
        ->with($request, \Mockery::type(Closure::class), 'forum-post-write')
        ->andThrow($exception);

    $botProtection->shouldReceive('recordRateLimitViolation')
        ->once()
        ->with(
            $request,
            'forum_reply_create',
            \Mockery::on(static function (array $context): bool {
                return $context['limiter'] === 'forum-post-write'
                    && $context['bucket'] === 'minute'
                    && $context['max_attempts'] === 3
                    && $context['retry_after'] === 42;
            })
        );

    $middleware = new ForumRateLimitMiddleware($throttle, $botProtection);

    $next = static fn (): Response => response('ok');

    $middleware->handle($request, $next);
})->throws(ThrottleRequestsException::class);

it('classifies forum hourly limiter violations using the actual limit bucket', function () {
    $request = Request::create('/forum/topic/example-topic/reply', 'POST');
    $request->setRouteResolver(static fn (): object => new class {
        public function getName(): string
        {
            return 'forum.topic.reply';
        }
    });

    $throttle = \Mockery::mock(ThrottleRequests::class);
    $botProtection = \Mockery::mock(BotProtectionService::class);

    $exception = new ThrottleRequestsException('Too Many Attempts.', null, [
        'Retry-After' => '120',
        'X-RateLimit-Limit' => '10',
        'X-RateLimit-Remaining' => '0',
    ]);

    $throttle->shouldReceive('handle')
        ->once()
        ->with($request, \Mockery::type(Closure::class), 'forum-post-write')
        ->andThrow($exception);

    $botProtection->shouldReceive('recordRateLimitViolation')
        ->once()
        ->with(
            $request,
            'forum_reply_create',
            \Mockery::on(static function (array $context): bool {
                return $context['bucket'] === 'hour'
                    && $context['max_attempts'] === 10
                    && $context['retry_after'] === 120;
            })
        );

    $middleware = new ForumRateLimitMiddleware($throttle, $botProtection);

    $next = static fn (): Response => response('ok');

    $middleware->handle($request, $next);
})->throws(ThrottleRequestsException::class);

it('classifies thread creation minute and hour limiter buckets correctly', function () {
    $request = Request::create('/forum/example-board/new', 'POST');
    $request->setRouteResolver(static fn (): object => new class {
        public function getName(): string
        {
            return 'forum.topic.store';
        }
    });

    $throttle = \Mockery::mock(ThrottleRequests::class);
    $botProtection = \Mockery::mock(BotProtectionService::class);

    $minuteException = new ThrottleRequestsException('Too Many Attempts.', null, [
        'Retry-After' => '30',
        'X-RateLimit-Limit' => '3',
        'X-RateLimit-Remaining' => '0',
    ]);

    $hourException = new ThrottleRequestsException('Too Many Attempts.', null, [
        'Retry-After' => '120',
        'X-RateLimit-Limit' => '10',
        'X-RateLimit-Remaining' => '0',
    ]);

    $throttle->shouldReceive('handle')
        ->once()
        ->with($request, \Mockery::type(Closure::class), 'forum-thread-create')
        ->andThrow($minuteException);

    $throttle->shouldReceive('handle')
        ->once()
        ->with($request, \Mockery::type(Closure::class), 'forum-thread-create')
        ->andThrow($hourException);

    $botProtection->shouldReceive('recordRateLimitViolation')
        ->once()
        ->with(
            $request,
            'forum_topic_create',
            \Mockery::on(static function (array $context): bool {
                return $context['limiter'] === 'forum-thread-create'
                    && $context['bucket'] === 'minute'
                    && $context['max_attempts'] === 3
                    && $context['retry_after'] === 30;
            })
        );

    $botProtection->shouldReceive('recordRateLimitViolation')
        ->once()
        ->with(
            $request,
            'forum_topic_create',
            \Mockery::on(static function (array $context): bool {
                return $context['limiter'] === 'forum-thread-create'
                    && $context['bucket'] === 'hour'
                    && $context['max_attempts'] === 10
                    && $context['retry_after'] === 120;
            })
        );

    $middleware = new ForumRateLimitMiddleware($throttle, $botProtection);
    $next = static fn (): Response => response('ok');

    try {
        $middleware->handle($request, $next);
    } catch (ThrottleRequestsException) {
    }

    $middleware->handle($request, $next);
})->throws(ThrottleRequestsException::class);