<?php

declare(strict_types=1);

namespace ApiModel\Middleware;

use ApiModel\Infrastructure\Logging\StructuredLog;
use ApiModel\Shared\Exceptions\ApiException;
use ApiModel\Shared\Http\ErrorResponse;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\SignatureInvalidException;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Exception\HttpException;
use Throwable;

final class ApiErrorHandler
{
    public static function create(ResponseFactoryInterface $responseFactory): callable
    {
        return function (
            ServerRequestInterface $request,
            Throwable $exception,
            bool $displayErrorDetails,
            bool $logErrors,
            bool $logErrorDetails,
        ) use ($responseFactory): ResponseInterface {
            if ($logErrors) {
                StructuredLog::exception($exception, 'slim.error');
            }

            if ($exception instanceof ApiException) {
                $c = (int) $exception->getCode();
                $status = $c >= 400 && $c <= 599 ? $c : 500;
                $payload = ErrorResponse::payload(
                    $status,
                    $exception->getMessage(),
                    $exception->getErrorCode(),
                    $exception->getDetails(),
                );
            } elseif ($exception instanceof ExpiredException) {
                $payload = ErrorResponse::payload(401, 'Sesión expirada', 'UNAUTHORIZED');
                $status = 401;
            } elseif ($exception instanceof SignatureInvalidException || $exception instanceof \UnexpectedValueException) {
                $payload = ErrorResponse::payload(401, 'No autorizado', 'UNAUTHORIZED');
                $status = 401;
            } elseif ($exception instanceof HttpException) {
                $status = $exception->getCode();
                if ($status < 400 || $status > 599) {
                    $status = 500;
                }
                $message = $exception->getDescription() !== '' ? $exception->getDescription() : $exception->getMessage();
                $payload = ErrorResponse::payload(
                    $status,
                    $message,
                    ErrorResponse::defaultCodeForStatus($status),
                );
            } else {
                $status = 500;
                $details = null;
                $message = 'Error interno';
                if ($displayErrorDetails) {
                    $message = $exception->getMessage();
                    $details = [
                        'type' => $exception::class,
                    ];
                }
                $payload = ErrorResponse::payload($status, $message, 'INTERNAL_ERROR', $details);
            }

            $response = $responseFactory
                ->createResponse($status)
                ->withHeader('Content-Type', 'application/json; charset=utf-8');
            $response->getBody()->write(json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR));

            return $response;
        };
    }
}
