<?php
// RouterTest.php
// Created: 2022-01-20
// Updated: 2024-07-31

declare(strict_types=1);

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,HttpPut,HttpRouter,RouteHandler};

/**
 * This test isn't super representative of the current functionality
 *  it mostly just does the same tests that were done against the previous implementation
 */
#[CoversClass(HttpGet::class)]
#[CoversClass(HttpMiddleware::class)]
#[CoversClass(HttpPost::class)]
#[CoversClass(HttpPut::class)]
#[CoversClass(HttpRouter::class)]
#[CoversClass(RouteHandler::class)]
final class RouterTest extends TestCase {
    public function testRouter(): void {
        $router1 = new HttpRouter;

        $router1->get('/', fn() => 'get');
        $router1->post('/', fn() => 'post');
        $router1->delete('/', fn() => 'delete');
        $router1->patch('/', fn() => 'patch');
        $router1->put('/', fn() => 'put');
        $router1->add('custom', '/', fn() => 'wacky');

        $this->assertEquals('get', $router1->resolve('GET', '/')->dispatch([]));
        $this->assertEquals('wacky', $router1->resolve('CUSTOM', '/')->dispatch([]));

        $router1->use('/', function() { /* this one intentionally does nothing */ });

        // registration order should matter
        $router1->use('/deep', fn() => 'deep');

        $postRoot = $router1->resolve('POST', '/');
        $this->assertNull($postRoot->runMiddleware([]));
        $this->assertEquals('post', $postRoot->dispatch([]));

        $this->assertEquals('deep', $router1->resolve('GET', '/deep/nothing')->runMiddleware([]));

        $router1->use('/user/([A-Za-z0-9]+)/below', fn(string $user) => 'warioware below ' . $user);

        $router1->get('/user/static', fn() => 'the static one');
        $router1->get('/user/static/below', fn() => 'below the static one');
        $router1->get('/user/([A-Za-z0-9]+)', fn(string $user) => $user);
        $router1->get('/user/([A-Za-z0-9]+)/below', fn(string $user) => 'below ' . $user);

        $this->assertEquals('below the static one', $router1->resolve('GET', '/user/static/below')->dispatch([]));

        $getWariowareBelowFlashwave = $router1->resolve('GET', '/user/flashwave/below');
        $this->assertEquals('warioware below flashwave', $getWariowareBelowFlashwave->runMiddleware([]));
        $this->assertEquals('below flashwave', $getWariowareBelowFlashwave->dispatch([]));

        $router2 = new HttpRouter;
        $router2->use('/', fn() => 'meow');
        $router2->get('/rules', fn() => 'rules page');
        $router2->get('/contact', fn() => 'contact page');
        $router2->get('/25252', fn() => 'numeric test');

        $getRules = $router2->resolve('GET', '/rules');
        $this->assertEquals('meow', $getRules->runMiddleware([]));
        $this->assertEquals('rules page', $getRules->dispatch([]));

        $get25252 = $router2->resolve('GET', '/25252');
        $this->assertEquals('meow', $get25252->runMiddleware([]));
        $this->assertEquals('numeric test', $get25252->dispatch([]));

        $router3 = $router1->scopeTo('/scoped');
        $router3->get('/static', fn() => 'wrong');
        $router1->get('/scoped/static/0', fn() => 'correct');
        $router3->get('/variable', fn() => 'wrong');
        $router3->get('/variable/([0-9]+)', fn(string $num) => $num === '0' ? 'correct' : 'VERY wrong');
        $router3->get('/variable/([a-z]+)', fn(string $char) => $char === 'a' ? 'correct' : 'VERY wrong');

        $this->assertEquals('correct', $router3->resolve('GET', '/static/0')->dispatch([]));
        $this->assertEquals('correct', $router1->resolve('GET', '/scoped/variable/0')->dispatch([]));
        $this->assertEquals('correct', $router3->resolve('GET', '/variable/a')->dispatch([]));
    }

    public function testAttribute(): void {
        $router = new HttpRouter;
        $handler = new class extends RouteHandler {
            #[HttpGet('/')]
            public function getIndex() {
                return 'index';
            }

            #[HttpPost('/avatar')]
            public function postAvatar() {
                return 'avatar';
            }

            #[HttpPut('/static')]
            public static function putStatic() {
                return 'static';
            }

            #[HttpGet('/meow')]
            #[HttpPost('/meow')]
            public function multiple() {
                return 'meow';
            }

            #[HttpMiddleware('/mw')]
            public function useMw() {
                return 'this intercepts';
            }

            #[HttpGet('/mw')]
            public function getMw() {
                return 'this is intercepted';
            }

            public function hasNoAttr() {
                return 'not a route';
            }
        };

        $router->register($handler);
        $this->assertFalse($router->resolve('GET', '/soap')->hasHandler());

        $patchAvatar = $router->resolve('PATCH', '/avatar');
        $this->assertFalse($patchAvatar->hasHandler());
        $this->assertTrue($patchAvatar->hasOtherMethods());
        $this->assertEquals(['POST'], $patchAvatar->getSupportedMethods());

        $this->assertEquals('index', $router->resolve('GET', '/')->dispatch([]));
        $this->assertEquals('avatar', $router->resolve('POST', '/avatar')->dispatch([]));
        $this->assertEquals('static', $router->resolve('PUT', '/static')->dispatch([]));
        $this->assertEquals('meow', $router->resolve('GET', '/meow')->dispatch([]));
        $this->assertEquals('meow', $router->resolve('POST', '/meow')->dispatch([]));

        // stopping on middleware is the dispatcher's job
        $getMw = $router->resolve('GET', '/mw');
        $this->assertEquals('this intercepts', $getMw->runMiddleware([]));
        $this->assertEquals('this is intercepted', $getMw->dispatch([]));

        $scoped = $router->scopeTo('/scoped');
        $scoped->register($handler);

        $this->assertEquals('index', $scoped->resolve('GET', '/')->dispatch([]));
        $this->assertEquals('avatar', $router->resolve('POST', '/scoped/avatar')->dispatch([]));
        $this->assertEquals('static', $scoped->resolve('PUT', '/static')->dispatch([]));
        $this->assertEquals('meow', $router->resolve('GET', '/scoped/meow')->dispatch([]));
        $this->assertEquals('meow', $scoped->resolve('POST', '/meow')->dispatch([]));
    }

    public function testEEPROMSituation(): void {
        $router = new HttpRouter;

        $router->options('/uploads/([A-Za-z0-9\-_]+)(?:\.(t|json))?', function() {});
        $router->get('/uploads/([A-Za-z0-9\-_]+)(?:\.(t|json))?', function() {});
        $router->delete('/uploads/([A-Za-z0-9\-_]+)', function() {});

        $resolved = $router->resolve('DELETE', '/uploads/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

        $this->assertEquals(['OPTIONS', 'GET', 'DELETE'], $resolved->getSupportedMethods());
    }
}