<?php

declare(strict_types=1);

namespace HyperfTest;

use Hyperf\HttpServer\MiddlewareManager;
use Hyperf\Testing\Http\TestResponse;

use function Hyperf\Support\make;

/**
 * 自定义 TestCase 基类
 *
 * 修复 Hyperf Testing Client 不处理 PriorityMiddleware 的问题：
 * Server.php 在分发请求前会调用 MiddlewareManager::sortMiddlewares()
 * 将 PriorityMiddleware 对象转为字符串类名，但 Testing Client 缺少此步骤。
 */
abstract class TestCase extends \Hyperf\Testing\TestCase
{
    private bool $middlewaresSorted = false;

    protected function doRequest(string $method, ...$args): TestResponse
    {
        $client = make(\Hyperf\Testing\Http\Client::class);

        // Client 构造时触发路由注册，此时排序中间件
        if (!$this->middlewaresSorted) {
            $this->sortAllRegisteredMiddlewares();
            $this->middlewaresSorted = true;
        }

        if (\Swoole\Coroutine::getCid() > 0) {
            return $this->createTestResponse(
                $client->{$method}(...$args)
            );
        }

        $response = null;
        $exception = null;

        \Swoole\Coroutine\run(function () use ($client, $method, $args, &$response, &$exception): void {
            try {
                $response = $this->createTestResponse(
                    $client->{$method}(...$args)
                );
            } catch (\Throwable $throwable) {
                $exception = $throwable;
            }
        });

        if ($exception !== null) {
            throw $exception;
        }

        if ($response === null) {
            throw new \RuntimeException('Test response not initialized.');
        }

        return $response;
    }

    /**
     * 将所有已注册的 PriorityMiddleware 对象转为字符串类名
     */
    private function sortAllRegisteredMiddlewares(): void
    {
        $reflection = new \ReflectionClass(MiddlewareManager::class);
        $prop = $reflection->getProperty('container');
        $prop->setAccessible(true);
        $container = $prop->getValue();

        foreach ($container as $server => $paths) {
            foreach ($paths as $path => $methods) {
                foreach ($methods as $method => $middlewares) {
                    $container[$server][$path][$method] = MiddlewareManager::sortMiddlewares($middlewares);
                }
            }
        }

        $prop->setValue(null, $container);
    }
}
