<?php

declare(strict_types=1);

namespace ApiModel\Middleware;

use ApiModel\Domain\Repository\PlatformAuditRepositoryInterface;
use ApiModel\Service\AuthService;
use ApiModel\Infrastructure\Logging\StructuredLog;
use ApiModel\Infrastructure\Metrics\MetricsRegistry;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class AccessLogMiddleware
{
    /** @var list<string> */
    private const SKIP_PREFIXES = ['/health', '/metrics', '/docs', '/openapi'];

    /** @var list<string> */
    private const SKIP_EXACT_PATHS = ['/auth/login', '/auth/logout', '/auth/refresh'];

    public function __construct(
        private readonly ?PlatformAuditRepositoryInterface $audit = null,
    ) {}

    public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $t0 = microtime(true);
        $response = $handler->handle($request);
        $durationMs = (microtime(true) - $t0) * 1000.0;
        $status = $response->getStatusCode();
        $path = $request->getUri()->getPath();
        $basePath = (string) str_replace('/index.php', '', $_SERVER['SCRIPT_NAME'] ?? '');
        if ($basePath !== '' && str_starts_with($path, $basePath)) {
            $path = substr($path, strlen($basePath));
        }
        $authContext = self::extractJwtContext($request);

        StructuredLog::emit('info', 'http.access', [
            'method' => $request->getMethod(),
            'path' => $path,
            'status' => $status,
            'duration_ms' => round($durationMs, 3),
            'user_id' => $authContext['userId'] ?? null,
            'user_role' => $authContext['role'] ?? null,
        ]);

        MetricsRegistry::record($status, $durationMs);

        if ($this->audit !== null && $authContext !== null && ! $this->shouldSkip($path)) {
            $this->persistAccessLog($request, $response, $authContext, $durationMs);
        }

        return $response;
    }

    private function shouldSkip(string $path): bool
    {
        foreach (self::SKIP_PREFIXES as $prefix) {
            if (str_starts_with($path, $prefix)) {
                return true;
            }
        }

        return in_array($path, self::SKIP_EXACT_PATHS, true);
    }

    /** @param array{userId: string, role: string, language: string} $authContext */
    private function persistAccessLog(
        ServerRequestInterface $request,
        ResponseInterface $response,
        array $authContext,
        float $durationMs,
    ): void {
        try {
            $path = $request->getUri()->getPath();
            $basePath = (string) str_replace('/index.php', '', $_SERVER['SCRIPT_NAME'] ?? '');
            if ($basePath !== '' && str_starts_with($path, $basePath)) {
                $path = substr($path, strlen($basePath));
            }
            $method = $request->getMethod();
            $status = $response->getStatusCode();

            $this->audit->log(
                'API_REQUEST',
                $authContext['userId'],
                null,
                json_encode([
                    'method' => $method,
                    'request' => $path,
                    'status' => $status,
                    'message' => $method . ' ' . $path,
                    'class' => 'http',
                    'duration_ms' => round($durationMs, 3),
                ], JSON_UNESCAPED_UNICODE),
            );
        } catch (\Throwable) {
        }
    }

    /** @return array{userId: string, role: string, language: string}|null */
    private static function extractJwtContext(ServerRequestInterface $request): ?array
    {
        $authorization = $request->getHeaderLine('Authorization');
        if (! preg_match('/Bearer\s+(\S+)/i', $authorization, $matches)) {
            return null;
        }

        try {
            return AuthService::verifyBearer($matches[1]);
        } catch (\Throwable) {
            return null;
        }
    }
}
