<?php

declare(strict_types=1);

namespace Tests\Framework\Security;

use App\Framework\Auth\Auth;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\Middlewares\AuthMiddleware;
use App\Framework\Http\Next;
use App\Framework\Http\Request;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Session\SessionInterface;
use PHPUnit\Framework\TestCase;

/**
 * Critical Security Tests for Authentication
 * Tests gegen OWASP A07:2021 – Identification and Authentication Failures
 */
final class AuthenticationSecurityTest extends TestCase
{
    private SessionInterface $session;

    private Auth $auth;

    private AuthMiddleware $middleware;

    private RequestStateManager $stateManager;

    protected function setUp(): void
    {
        $this->session = $this->createMock(SessionInterface::class);
        $this->auth = $this->createMock(Auth::class);
        $this->middleware = new AuthMiddleware($this->auth);
        $this->stateManager = $this->createMock(RequestStateManager::class);
    }

    /**
     * Test: Nicht-authentifizierte Benutzer werden blockiert
     * OWASP A07:2021 – Identification and Authentication Failures
     */
    public function test_blocks_unauthenticated_access(): void
    {
        // Arrange: Benutzer nicht authentifiziert
        $this->auth->method('isAuthenticated')->willReturn(false);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);
        $next->expects($this->never())->method('__invoke');

        // Act & Assert: Exception erwartet
        $this->expectException(\App\Framework\Http\Exception\HttpException::class);

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    /**
     * Test: Authentifizierte Benutzer werden durchgelassen
     */
    public function test_allows_authenticated_access(): void
    {
        // Arrange: Benutzer authentifiziert
        $this->auth->method('isAuthenticated')->willReturn(true);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);
        $next->expects($this->once())->method('__invoke')->willReturn($context);

        // Act
        $result = $this->middleware->__invoke($context, $next, $this->stateManager);

        // Assert
        $this->assertSame($context, $result);
    }

    /**
     * Test: Session Hijacking Schutz
     * Validiert Session-Fingerprinting
     */
    public function test_session_fingerprint_validation(): void
    {
        // Arrange: Authentifiziert aber Session-Fingerprint geändert
        $this->auth->method('isAuthenticated')->willReturn(true);
        $this->auth->method('validateSessionFingerprint')->willReturn(false);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);

        // Act & Assert: Session Hijacking erkannt
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Session fingerprint validation failed');

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    /**
     * Test: Concurrent Login Detection
     * Erkennt verdächtige gleichzeitige Logins
     */
    public function test_concurrent_login_detection(): void
    {
        // Arrange: Benutzer von verschiedenen IPs eingeloggt
        $this->auth->method('isAuthenticated')->willReturn(true);
        $this->auth->method('validateSessionFingerprint')->willReturn(true);
        $this->auth->method('detectConcurrentSessions')->willReturn(true);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);

        // Act & Assert: Concurrent Session erkannt
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Concurrent session detected');

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    /**
     * Test: Session Timeout Validierung
     */
    public function test_session_timeout_validation(): void
    {
        // Arrange: Session abgelaufen
        $this->auth->method('isAuthenticated')->willReturn(true);
        $this->auth->method('isSessionExpired')->willReturn(true);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);

        // Act & Assert: Session Timeout
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Session expired');

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    /**
     * Test: Rate Limiting für Authentication Failures
     * Verhindert Brute Force Attacks
     */
    public function test_authentication_rate_limiting(): void
    {
        // Arrange: Zu viele fehlgeschlagene Login-Versuche
        $this->auth->method('isAuthenticated')->willReturn(false);
        $this->auth->method('isRateLimited')->willReturn(true);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);

        // Act & Assert: Rate Limit erreicht
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Authentication rate limit exceeded');

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    /**
     * Test: IP Whitelist Validation
     * Admin-Bereiche nur von bestimmten IPs
     */
    public function test_ip_whitelist_validation(): void
    {
        // Arrange: IP nicht in Whitelist
        $this->auth->method('isAuthenticated')->willReturn(true);
        $this->auth->method('isIpWhitelisted')->willReturn(false);

        $request = $this->createRequestWithIp('192.168.1.100');
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);

        // Act & Assert: IP nicht erlaubt
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Access from this IP address is not allowed');

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    /**
     * Test: Account Lockout nach fehlgeschlagenen Versuchen
     */
    public function test_account_lockout_after_failed_attempts(): void
    {
        // Arrange: Account gesperrt nach zu vielen Versuchen
        $this->auth->method('isAuthenticated')->willReturn(false);
        $this->auth->method('isAccountLocked')->willReturn(true);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);

        // Act & Assert: Account gesperrt
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Account is locked due to too many failed attempts');

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    /**
     * Test: Two-Factor Authentication Validation
     */
    public function test_two_factor_authentication_required(): void
    {
        // Arrange: 2FA erforderlich aber nicht bereitgestellt
        $this->auth->method('isAuthenticated')->willReturn(true);
        $this->auth->method('requires2FA')->willReturn(true);
        $this->auth->method('is2FAValid')->willReturn(false);

        $request = $this->createMock(Request::class);
        $context = new MiddlewareContext($request);
        $next = $this->createMock(Next::class);

        // Act & Assert: 2FA erforderlich
        $this->expectException(\RuntimeException::class);
        $this->expectExceptionMessage('Two-factor authentication required');

        $this->middleware->__invoke($context, $next, $this->stateManager);
    }

    // Helper Methods

    private function createRequestWithIp(string $ip): Request
    {
        $request = $this->createMock(Request::class);
        $request->method('getClientIp')->willReturn($ip);

        return $request;
    }
}
