<?php

namespace App\Tests\EventSubscriber;

use App\EventSubscriber\CsrfProtectionSubscriber;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

class CsrfProtectionSubscriberTest extends TestCase
{
    private CsrfTokenManagerInterface $csrfTokenManager;
    private CsrfProtectionSubscriber $subscriber;

    protected function setUp(): void
    {
        $this->csrfTokenManager = $this->createStub(CsrfTokenManagerInterface::class);
        $this->subscriber = new CsrfProtectionSubscriber($this->csrfTokenManager);
    }

    public function testGetSubscribedEvents(): void
    {
        $events = CsrfProtectionSubscriber::getSubscribedEvents();
        $this->assertArrayHasKey('kernel.request', $events);
        $this->assertArrayHasKey('kernel.response', $events);
    }

    public function testOnKernelRequestInvalidToken(): void
    {
        $request = new Request();
        $request->setMethod('POST');
        $request->request->set('_csrf_token', 'invalid_token');
        
        $this->csrfTokenManager->method('isTokenValid')->willReturn(false);

        $event = $this->createRequestEvent($request);
        $this->subscriber->onKernelRequest($event);

        $response = $event->getResponse();
        $this->assertNotNull($response);
        $this->assertSame(403, $response->getStatusCode());
    }

    public function testOnKernelRequestEmptyToken(): void
    {
        $request = new Request();
        $request->setMethod('POST');
        $request->request->set('_csrf_token', '');
        
        $event = $this->createRequestEvent($request);
        $this->subscriber->onKernelRequest($event);

        $this->assertNull($event->getResponse());
    }

    public function testOnKernelRequestValidToken(): void
    {
        $request = new Request();
        $request->setMethod('POST');
        $request->request->set('_csrf_token', 'valid_token');
        
        $this->csrfTokenManager->method('isTokenValid')->willReturn(true);

        $event = $this->createRequestEvent($request);
        $this->subscriber->onKernelRequest($event);

        $this->assertNull($event->getResponse());
    }

    public function testOnKernelRequestIgnoreSubRequest(): void
    {
        $event = new RequestEvent(
            $this->createStub(HttpKernelInterface::class),
            new Request(),
            HttpKernelInterface::SUB_REQUEST
        );

        $this->subscriber->onKernelRequest($event);
        $this->assertNull($event->getResponse());
    }

    public function testOnKernelRequestIgnoreNonPost(): void
    {
        $request = new Request();
        $request->setMethod('GET');

        $event = $this->createRequestEvent($request);
        $this->subscriber->onKernelRequest($event);
        $this->assertNull($event->getResponse());
    }

    public function testOnKernelRequestIgnoreExcludedRoute(): void
    {
        $request = new Request();
        $request->setMethod('POST');
        $request->attributes->set('_route', 'app_logout');

        $event = $this->createRequestEvent($request);
        $this->subscriber->onKernelRequest($event);
        $this->assertNull($event->getResponse());
    }

    public function testOnKernelRequestIgnoreJson(): void
    {
        $request = new Request();
        $request->setMethod('POST');
        $request->headers->set('Content-Type', 'application/json; charset=utf-8');

        $event = $this->createRequestEvent($request);
        $this->subscriber->onKernelRequest($event);
        $this->assertNull($event->getResponse());
    }

    public function testOnKernelResponseInjectsToken(): void
    {
        $request = new Request();
        $response = new Response('<html><body><form method="post"></form></body></html>');
        $response->headers->set('Content-Type', 'text/html');

        $token = $this->createStub(CsrfToken::class);
        $token->method('getValue')->willReturn('fake_token');
        $this->csrfTokenManager->method('getToken')->willReturn($token);

        $event = $this->createResponseEvent($request, $response);
        $this->subscriber->onKernelResponse($event);

        $content = $response->getContent();
        $this->assertStringContainsString('name="_csrf_token"', $content);
        $this->assertStringContainsString('value="fake_token"', $content);
    }

    public function testOnKernelResponseIgnoreSubRequest(): void
    {
        $response = new Response('test');
        $event = new ResponseEvent(
            $this->createStub(HttpKernelInterface::class),
            new Request(),
            HttpKernelInterface::SUB_REQUEST,
            $response
        );

        $this->subscriber->onKernelResponse($event);
        $this->assertSame('test', $response->getContent());
    }

    public function testOnKernelResponseIgnoreNonHtml(): void
    {
        $response = new Response('{}');
        $response->headers->set('Content-Type', 'application/json');

        $event = $this->createResponseEvent(new Request(), $response);
        $this->subscriber->onKernelResponse($event);
        $this->assertSame('{}', $response->getContent());
    }

    public function testOnKernelResponseIgnoreNoForm(): void
    {
        $response = new Response('<html></html>');
        $response->headers->set('Content-Type', 'text/html');

        $event = $this->createResponseEvent(new Request(), $response);
        $this->subscriber->onKernelResponse($event);
        $this->assertSame('<html></html>', $response->getContent());
    }

    public function testOnKernelResponseIgnoreEmptyContent(): void
    {
        $response = new Response('');
        $response->headers->set('Content-Type', 'text/html');

        $event = $this->createResponseEvent(new Request(), $response);
        $this->subscriber->onKernelResponse($event);
        $this->assertSame('', $response->getContent());
    }

    private function createRequestEvent(Request $request): RequestEvent
    {
        return new RequestEvent(
            $this->createStub(HttpKernelInterface::class),
            $request,
            HttpKernelInterface::MAIN_REQUEST
        );
    }

    private function createResponseEvent(Request $request, Response $response): ResponseEvent
    {
        return new ResponseEvent(
            $this->createStub(HttpKernelInterface::class),
            $request,
            HttpKernelInterface::MAIN_REQUEST,
            $response
        );
    }
}
