<?php

declare(strict_types=1);

namespace Apexl\Io\Tests\FeatureTests;

use Apexl\Io\Routing\RoutePatternException;
use Apexl\Io\TestInfrastructure\FeatureTest;
use PHPUnit\Framework\Attributes\Test;
use Slim\Routing\RouteCollectorProxy;

/**
 * Slim does not normalise route patterns, and the two ways that bites are both silent.
 *
 * A pattern with no leading slash is stored exactly as given, and no request can match it —
 * request targets always begin with one — so the route registers, appears in `routes:dump`,
 * and answers 404 forever. Inside a group it is worse: `RouteCollectorProxy::map()`
 * concatenates, so a group on `/admin` holding `users` registers `/adminusers` — a real,
 * reachable route at a path nobody meant, while `/admin/users` 404s.
 *
 * Nothing reports either, which is why registration refuses them.
 */
final class RoutePatternGuardTest extends FeatureTest
{
    #[Test]
    public function aPatternWithoutALeadingSlashIsRefused(): void
    {
        $this->expectException(RoutePatternException::class);
        $this->expectExceptionMessage('no request path can match it');

        self::$application->get('guard-no-slash', fn() => null);
    }

    /** The message names the path that would have been registered instead. */
    #[Test]
    public function aGroupMemberWithoutALeadingSlashIsRefused(): void
    {
        $this->expectException(RoutePatternException::class);
        $this->expectExceptionMessage("would register '/guard-grpoops' rather than '/guard-grp/oops'");

        self::$application->group('/guard-grp', function (RouteCollectorProxy $routes): void {
            $routes->get('oops', fn() => null);
        });
    }

    #[Test]
    public function aNestedGroupWithoutALeadingSlashIsRefused(): void
    {
        $this->expectException(RoutePatternException::class);

        self::$application->group('/guard-outer', function (RouteCollectorProxy $routes): void {
            $routes->group('inner', function (RouteCollectorProxy $inner): void {
                $inner->get('/thing', fn() => null);
            });
        });
    }

    /** An empty pattern is how a route at the group's own path is declared. */
    #[Test]
    public function anEmptyPatternIsAllowedAtAGroupsOwnPath(): void
    {
        self::$application->group('/guard-root', function (RouteCollectorProxy $routes): void {
            $routes->get('', fn() => null)->setName('guard.root');
        });

        self::assertSame(
            '/guard-root',
            self::$application->getRouteCollector()->getNamedRoute('guard.root')->getPattern(),
        );
    }

    #[Test]
    public function aWellFormedPatternIsUntouched(): void
    {
        self::$application->get('/guard-fine/{id}', fn() => null)->setName('guard.fine');

        self::assertSame(
            '/guard-fine/{id}',
            self::$application->getRouteCollector()->getNamedRoute('guard.fine')->getPattern(),
        );
    }
}
