<?php

declare(strict_types=1);

namespace App\Framework\Security\RequestSigning;

use App\Framework\Http\HttpMiddleware;
use App\Framework\Http\HttpResponse;
use App\Framework\Http\MiddlewareContext;
use App\Framework\Http\MiddlewarePriority;
use App\Framework\Http\MiddlewarePriorityAttribute;
use App\Framework\Http\Next;
use App\Framework\Http\RequestStateManager;
use App\Framework\Http\Status;
use App\Framework\Logging\Logger;

/**
 * Middleware for validating HTTP request signatures
 */
#[MiddlewarePriorityAttribute(MiddlewarePriority::SECURITY, -10)]
final readonly class RequestSigningMiddleware implements HttpMiddleware
{
    public function __construct(
        private RequestVerifier $verifier,
        private Logger $logger,
        private bool $requireSignature = false,
        private array $exemptPaths = [],
    ) {
    }

    public function __invoke(MiddlewareContext $context, Next $next, RequestStateManager $stateManager): MiddlewareContext
    {
        $request = $context->request;

        // Check if path is exempt from signature validation
        if ($this->isExemptPath($request->path)) {
            return $next($context);
        }

        // Check if signature header is present
        $signatureHeader = $request->headers->getFirst('Signature');

        if ($signatureHeader === null) {
            if ($this->requireSignature) {
                $this->logger->warning('Request signature required but not provided', [
                    'path' => $request->path,
                    'method' => $request->method->value,
                    'remote_addr' => $request->server->get('REMOTE_ADDR'),
                ]);

                return $context->withResponse(
                    new HttpResponse(
                        status: Status::UNAUTHORIZED,
                        body: json_encode(['error' => 'Request signature required']),
                        headers: ['Content-Type' => 'application/json']
                    )
                );
            }

            // Signature not required, continue
            return $next($context);
        }

        // Verify the signature
        $result = $this->verifier->verify($request);

        if ($result->isFailure()) {
            $this->logger->warning('Request signature verification failed', [
                'error' => $result->errorMessage,
                'path' => $request->path,
                'method' => $request->method->value,
                'remote_addr' => $request->server->get('REMOTE_ADDR'),
            ]);

            return $context->withResponse(
                new HttpResponse(
                    status: Status::UNAUTHORIZED,
                    body: json_encode(['error' => 'Invalid request signature']),
                    headers: ['Content-Type' => 'application/json']
                )
            );
        }

        // Verify digest if present
        if (! $this->verifier->verifyDigest($request)) {
            $this->logger->warning('Request digest verification failed', [
                'path' => $request->path,
                'method' => $request->method->value,
                'remote_addr' => $request->server->get('REMOTE_ADDR'),
            ]);

            return $context->withResponse(
                new HttpResponse(
                    status: Status::UNAUTHORIZED,
                    body: json_encode(['error' => 'Invalid request digest']),
                    headers: ['Content-Type' => 'application/json']
                )
            );
        }

        // Store verification result in request state for use by other middleware/controllers
        $stateManager->set('signature_verification', $result);

        $this->logger->info('Request signature verified successfully', [
            'key_id' => $result->signature->keyId,
            'algorithm' => $result->signature->algorithm->value,
            'path' => $request->path,
        ]);

        return $next($context);
    }

    /**
     * Check if the request path is exempt from signature validation
     */
    private function isExemptPath(string $path): bool
    {
        foreach ($this->exemptPaths as $exemptPath) {
            if (str_starts_with($path, $exemptPath)) {
                return true;
            }
        }

        return false;
    }
}
