<?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\HttpKernel\KernelEvents;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

class CsrfProtectionSubscriberTest extends TestCase
{
    private function createSubscriber(?CsrfTokenManagerInterface $csrfManager = null): CsrfProtectionSubscriber
    {
        $csrfManager ??= $this->createMock(CsrfTokenManagerInterface::class);

        return new CsrfProtectionSubscriber($csrfManager);
    }

    public function testSubscribedEvents(): void
    {
        $events = CsrfProtectionSubscriber::getSubscribedEvents();

        self::assertArrayHasKey(KernelEvents::REQUEST, $events);
        self::assertArrayHasKey(KernelEvents::RESPONSE, $events);
    }

    public function testIgnoresGetRequests(): void
    {
        $subscriber = $this->createSubscriber();

        $request = Request::create('/test', 'GET');
        $request->attributes->set('_route', 'app_test');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);

        $subscriber->onKernelRequest($event);

        self::assertNull($event->getResponse());
    }

    public function testIgnoresExcludedRoutes(): void
    {
        $subscriber = $this->createSubscriber();

        $request = Request::create('/stripe/webhook', 'POST');
        $request->attributes->set('_route', 'app_stripe_webhook');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);

        $subscriber->onKernelRequest($event);

        self::assertNull($event->getResponse());
    }

    public function testIgnoresJsonRequests(): void
    {
        $subscriber = $this->createSubscriber();

        $request = Request::create('/test', 'POST', [], [], [], ['CONTENT_TYPE' => 'application/json']);
        $request->attributes->set('_route', 'app_test');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);

        $subscriber->onKernelRequest($event);

        self::assertNull($event->getResponse());
    }

    public function testIgnoresPostWithoutToken(): void
    {
        $subscriber = $this->createSubscriber();

        $request = Request::create('/test', 'POST');
        $request->attributes->set('_route', 'app_test');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);

        $subscriber->onKernelRequest($event);

        self::assertNull($event->getResponse());
    }

    public function testRejectsInvalidToken(): void
    {
        $csrfManager = $this->createMock(CsrfTokenManagerInterface::class);
        $csrfManager->method('isTokenValid')->willReturn(false);

        $subscriber = $this->createSubscriber($csrfManager);

        $request = Request::create('/test', 'POST', ['_csrf_token' => 'bad']);
        $request->attributes->set('_route', 'app_test');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);

        $subscriber->onKernelRequest($event);

        self::assertNotNull($event->getResponse());
        self::assertSame(403, $event->getResponse()->getStatusCode());
    }

    public function testAcceptsValidToken(): void
    {
        $csrfManager = $this->createMock(CsrfTokenManagerInterface::class);
        $csrfManager->method('isTokenValid')->willReturn(true);

        $subscriber = $this->createSubscriber($csrfManager);

        $request = Request::create('/test', 'POST', ['_csrf_token' => 'valid']);
        $request->attributes->set('_route', 'app_test');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST);

        $subscriber->onKernelRequest($event);

        self::assertNull($event->getResponse());
    }

    public function testInjectsTokenInHtmlResponse(): void
    {
        $csrfManager = $this->createMock(CsrfTokenManagerInterface::class);
        $csrfManager->method('getToken')->willReturn(new CsrfToken('form_protection', 'abc123'));

        $subscriber = $this->createSubscriber($csrfManager);

        $response = new Response('<form method="post"><button>Submit</button></form>');
        $response->headers->set('Content-Type', 'text/html');

        $request = Request::create('/test');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response);

        $subscriber->onKernelResponse($event);

        self::assertStringContainsString('name="_csrf_token"', $response->getContent());
        self::assertStringContainsString('value="abc123"', $response->getContent());
    }

    public function testDoesNotInjectInNonHtml(): void
    {
        $subscriber = $this->createSubscriber();

        $response = new Response('{"ok":true}');
        $response->headers->set('Content-Type', 'application/json');

        $request = Request::create('/test');
        $kernel = $this->createMock(HttpKernelInterface::class);
        $event = new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $response);

        $subscriber->onKernelResponse($event);

        self::assertStringNotContainsString('_csrf_token', $response->getContent());
    }
}
