<?php

declare(strict_types=1);

namespace App\Tests\Unit\Shared\Infrastructure\RateLimit;

use App\Shared\Infrastructure\RateLimit\LoginRateLimitResult;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class LoginRateLimitResultTest extends TestCase
{
    #[Test]
    #[DataProvider('fibonacciDelayProvider')]
    public function fibonacciDelayCalculatesCorrectly(int $attempts, int $expectedDelay): void
    {
        self::assertSame($expectedDelay, LoginRateLimitResult::fibonacciDelay($attempts));
    }

    /**
     * @return iterable<string, array{int, int}>
     */
    public static function fibonacciDelayProvider(): iterable
    {
        yield '0 attempts = no delay' => [0, 0];
        yield '1 attempt = no delay' => [1, 0];
        yield '2 attempts = 1s' => [2, 1];
        yield '3 attempts = 1s' => [3, 1];
        yield '4 attempts = 2s' => [4, 2];
        yield '5 attempts = 3s' => [5, 3];
        yield '6 attempts = 5s' => [6, 5];
        yield '7 attempts = 8s' => [7, 8];
        yield '8 attempts = 13s' => [8, 13];
        yield '9 attempts = 21s' => [9, 21];
        yield '10 attempts = 34s' => [10, 34];
        yield '11 attempts = 55s' => [11, 55];
        yield '12 attempts = 89s (max)' => [12, 89];
        yield '20 attempts = 89s (capped)' => [20, 89];
        yield '100 attempts = 89s (capped)' => [100, 89];
    }

    #[Test]
    public function allowedResultHasCorrectProperties(): void
    {
        $result = LoginRateLimitResult::allowed(
            attempts: 3,
            delaySeconds: 1,
            requiresCaptcha: false,
        );

        self::assertTrue($result->isAllowed);
        self::assertSame(3, $result->attempts);
        self::assertSame(1, $result->delaySeconds);
        self::assertFalse($result->requiresCaptcha);
        self::assertFalse($result->ipBlocked);
        self::assertSame(1, $result->retryAfter);
    }

    #[Test]
    public function allowedWithZeroDelayHasNullRetryAfter(): void
    {
        $result = LoginRateLimitResult::allowed(
            attempts: 1,
            delaySeconds: 0,
            requiresCaptcha: false,
        );

        self::assertNull($result->retryAfter);
    }

    #[Test]
    public function blockedResultHasCorrectProperties(): void
    {
        $result = LoginRateLimitResult::blocked(retryAfter: 900);

        self::assertFalse($result->isAllowed);
        self::assertSame(0, $result->attempts);
        self::assertSame(900, $result->delaySeconds);
        self::assertFalse($result->requiresCaptcha);
        self::assertTrue($result->ipBlocked);
        self::assertSame(900, $result->retryAfter);
    }

    #[Test]
    public function toHeadersIncludesAllRelevantHeaders(): void
    {
        $result = LoginRateLimitResult::allowed(
            attempts: 6,
            delaySeconds: 5,
            requiresCaptcha: true,
        );

        $headers = $result->toHeaders();

        self::assertSame('6', $headers['X-Login-Attempts']);
        self::assertSame('5', $headers['X-Login-Delay']);
        self::assertSame('5', $headers['Retry-After']);
        self::assertSame('true', $headers['X-Captcha-Required']);
        self::assertArrayNotHasKey('X-IP-Blocked', $headers);
    }

    #[Test]
    public function toHeadersForBlockedIp(): void
    {
        $result = LoginRateLimitResult::blocked(retryAfter: 600);

        $headers = $result->toHeaders();

        self::assertSame('true', $headers['X-IP-Blocked']);
        self::assertSame('600', $headers['Retry-After']);
    }

    #[Test]
    public function getFormattedDelayFormatsSeconds(): void
    {
        $result = LoginRateLimitResult::allowed(attempts: 2, delaySeconds: 1, requiresCaptcha: false);
        self::assertSame('1 seconde', $result->getFormattedDelay());

        $result = LoginRateLimitResult::allowed(attempts: 6, delaySeconds: 5, requiresCaptcha: false);
        self::assertSame('5 secondes', $result->getFormattedDelay());

        $result = LoginRateLimitResult::allowed(attempts: 8, delaySeconds: 13, requiresCaptcha: false);
        self::assertSame('13 secondes', $result->getFormattedDelay());
    }

    #[Test]
    public function getFormattedDelayFormatsMinutes(): void
    {
        $result = LoginRateLimitResult::blocked(retryAfter: 60);
        self::assertSame('1 minute', $result->getFormattedDelay());

        $result = LoginRateLimitResult::blocked(retryAfter: 900);
        self::assertSame('15 minutes', $result->getFormattedDelay());
    }

    #[Test]
    public function getFormattedDelayReturnsEmptyForZero(): void
    {
        $result = LoginRateLimitResult::allowed(attempts: 1, delaySeconds: 0, requiresCaptcha: false);
        self::assertSame('', $result->getFormattedDelay());
    }
}
