<?php

declare(strict_types=1);

namespace Tests\Framework\Http\Parser;

use App\Framework\Cache\Compression\NullCompression;
use App\Framework\Cache\CompressionCacheDecorator;
use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Cache\GeneralCache;
use App\Framework\Cache\Serializer\PhpSerializer;
use App\Framework\Http\Parser\CookieParser;
use App\Framework\Http\Parser\Exception\ParserSecurityException;
use App\Framework\Http\Parser\ParserCache;
use App\Framework\Http\Parser\ParserConfig;
use PHPUnit\Framework\TestCase;

final class CookieParserTest extends TestCase
{
    private CookieParser $parser;

    protected function setUp(): void
    {
        $this->parser = $this->createCookieParser();
    }

    private function createCookieParser(?ParserConfig $config = null): CookieParser
    {
        // Create parser cache with proper serialization
        $baseCache = new GeneralCache(new InMemoryCache(), new \App\Framework\Serializer\Php\PhpSerializer());
        $compressionCache = new CompressionCacheDecorator(
            $baseCache,
            new NullCompression(),
            new PhpSerializer()
        );
        $cache = new ParserCache($compressionCache);

        return new CookieParser($config ?? new ParserConfig(), $cache);
    }

    public function testParseEmptyCookieHeader(): void
    {
        $result = $this->parser->parseCookieHeader('');
        $this->assertSame([], $result);
    }

    public function testParseSingleCookie(): void
    {
        $result = $this->parser->parseCookieHeader('sessionId=abc123');
        $this->assertSame(['sessionId' => 'abc123'], $result);
    }

    public function testParseMultipleCookies(): void
    {
        $result = $this->parser->parseCookieHeader('sessionId=abc123; userId=456; theme=dark');
        $this->assertSame([
            'sessionId' => 'abc123',
            'userId' => '456',
            'theme' => 'dark',
        ], $result);
    }

    public function testParseUrlEncodedValues(): void
    {
        $result = $this->parser->parseCookieHeader('name=John%20Doe; email=test%40example.com');
        $this->assertSame([
            'name' => 'John Doe',
            'email' => 'test@example.com',
        ], $result);
    }

    public function testParseEmptyValues(): void
    {
        $result = $this->parser->parseCookieHeader('empty=; valid=value');
        $this->assertSame([
            'empty' => '',
            'valid' => 'value',
        ], $result);
    }

    public function testIgnoreInvalidPairs(): void
    {
        $result = $this->parser->parseCookieHeader('valid=value; invalid_no_equals; another=test');
        $this->assertSame([
            'valid' => 'value',
            'another' => 'test',
        ], $result);
    }

    public function testHandleExtraSpaces(): void
    {
        $result = $this->parser->parseCookieHeader('  key1  =  value1  ;  key2  =  value2  ');
        $this->assertSame([
            'key1' => 'value1',
            'key2' => 'value2',
        ], $result);
    }

    public function testParseSetCookieHeader(): void
    {
        $setCookie = 'sessionId=abc123; Expires=Wed, 09 Jun 2021 10:18:14 GMT; Path=/; Domain=.example.com; Secure; HttpOnly; SameSite=Lax';

        $result = $this->parser->parseSetCookieHeader($setCookie);

        $this->assertSame('sessionId', $result['name']);
        $this->assertSame('abc123', $result['value']);
        $this->assertSame('Wed, 09 Jun 2021 10:18:14 GMT', $result['expires']);
        $this->assertSame('/', $result['path']);
        $this->assertSame('.example.com', $result['domain']);
        $this->assertTrue($result['secure']);
        $this->assertTrue($result['httponly']);
        $this->assertSame('Lax', $result['samesite']);
    }

    public function testParseSetCookieWithMaxAge(): void
    {
        $setCookie = 'token=xyz789; Max-Age=3600; Path=/api';

        $result = $this->parser->parseSetCookieHeader($setCookie);

        $this->assertSame('token', $result['name']);
        $this->assertSame('xyz789', $result['value']);
        $this->assertSame(3600, $result['max-age']);
        $this->assertSame('/api', $result['path']);
    }

    public function testParseSetCookieWithUrlEncodedValue(): void
    {
        $setCookie = 'data=hello%20world%21; Path=/';

        $result = $this->parser->parseSetCookieHeader($setCookie);

        $this->assertSame('data', $result['name']);
        $this->assertSame('hello world!', $result['value']);
    }

    public function testParseInvalidSetCookieThrowsException(): void
    {
        $this->expectException(\InvalidArgumentException::class);
        $this->parser->parseSetCookieHeader('invalid_cookie_format');
    }

    public function testParseToCookiesObject(): void
    {
        $cookies = $this->parser->parseToCookies('foo=bar; baz=qux');

        $this->assertSame('bar', $cookies->get('foo')?->value);
        $this->assertSame('qux', $cookies->get('baz')?->value);
        $this->assertNull($cookies->get('nonexistent'));
    }

    // Security Tests

    public function testCookieCountLimitExceeded(): void
    {
        $config = new ParserConfig(maxCookieCount: 2);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Cookie count exceeded: 3 cookies > 2 maximum');

        $parser->parseCookieHeader('cookie1=value1; cookie2=value2; cookie3=value3');
    }

    public function testCookieNameTooLong(): void
    {
        $config = new ParserConfig(maxCookieNameLength: 10);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Cookie name too long');

        $parser->parseCookieHeader('verylongcookiename=value');
    }

    public function testCookieValueTooLong(): void
    {
        $config = new ParserConfig(maxCookieValueLength: 10);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Cookie value too long');

        $parser->parseCookieHeader('cookie=verylongcookievaluethatexceedslimit');
    }

    public function testMaliciousScriptInjectionDetected(): void
    {
        $config = new ParserConfig(scanForMaliciousContent: true);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Malicious content detected');

        $parser->parseCookieHeader('evil=<script>alert("xss")</script>');
    }

    public function testMaliciousJavaScriptUrlDetected(): void
    {
        $config = new ParserConfig(scanForMaliciousContent: true);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Malicious content detected');

        $parser->parseCookieHeader('redirect=javascript:alert("xss")');
    }

    public function testMaliciousEventHandlerDetected(): void
    {
        $config = new ParserConfig(scanForMaliciousContent: true);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Malicious content detected');

        $parser->parseCookieHeader('data=onclick=alert("xss")');
    }

    public function testExcessiveUrlEncodingDetected(): void
    {
        $config = new ParserConfig(scanForMaliciousContent: true);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Excessive URL encoding detected');

        $excessive = str_repeat('%20', 15); // More than 10 % characters
        $parser->parseCookieHeader("data={$excessive}");
    }

    public function testControlCharactersDetected(): void
    {
        $config = new ParserConfig(scanForMaliciousContent: true);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Control characters detected');

        $parser->parseCookieHeader("data=value\x00nullbyte");
    }

    public function testCrlfInjectionDetected(): void
    {
        $config = new ParserConfig(scanForMaliciousContent: true);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Malicious content detected');

        $parser->parseCookieHeader("data=value\r\nSet-Cookie: evil=injected");
    }

    // Set-Cookie Security Tests

    public function testSetCookieNameTooLong(): void
    {
        $config = new ParserConfig(maxCookieNameLength: 5);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Cookie name too long');

        $parser->parseSetCookieHeader('verylongname=value; Path=/');
    }

    public function testSetCookieValueTooLong(): void
    {
        $config = new ParserConfig(maxCookieValueLength: 5);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Cookie value too long');

        $parser->parseSetCookieHeader('name=verylongvalue; Path=/');
    }

    public function testSetCookieMaliciousContent(): void
    {
        $config = new ParserConfig(scanForMaliciousContent: true);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Malicious content detected');

        $parser->parseSetCookieHeader('evil=<script>alert("xss")</script>; Path=/');
    }

    public function testMultipleSetCookieCountLimit(): void
    {
        $config = new ParserConfig(maxCookieCount: 2);
        $parser = $this->createCookieParser($config);

        $this->expectException(ParserSecurityException::class);
        $this->expectExceptionMessage('Cookie count exceeded');

        $parser->parseSetCookieHeaders([
            'cookie1=value1; Path=/',
            'cookie2=value2; Path=/',
            'cookie3=value3; Path=/',
        ]);
    }

    // Security Configuration Tests

    public function testSecurityDisabled(): void
    {
        $config = new ParserConfig(
            scanForMaliciousContent: false,
            maxCookieCount: 1000,
            maxCookieNameLength: 1000,
            maxCookieValueLength: 1000
        );
        $parser = $this->createCookieParser($config);

        // Should not throw exception when security is disabled
        $result = $parser->parseCookieHeader('evil=<script>alert("xss")</script>');
        $this->assertSame(['evil' => '<script>alert("xss")</script>'], $result);
    }

    public function testWithinSecurityLimits(): void
    {
        $config = new ParserConfig(
            maxCookieCount: 5,
            maxCookieNameLength: 20,
            maxCookieValueLength: 50,
            scanForMaliciousContent: true
        );
        $parser = $this->createCookieParser($config);

        // Should work fine within limits
        $result = $parser->parseCookieHeader('session=abc123; theme=dark; lang=en');
        $this->assertSame([
            'session' => 'abc123',
            'theme' => 'dark',
            'lang' => 'en',
        ], $result);
    }
}
