# Exemplos Práticos de Middleware - OfficeGest ERP

Este documento contém exemplos práticos de implementação e uso de middlewares no contexto do OfficeGest ERP, organizados por nível de complexidade.

## 🟢 Nível Básico - Para Desenvolvedores Junior

### 1. Middleware de Log Simples

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class SimpleLogMiddleware implements MiddlewareInterface
{
    public function handle(Request $request, Closure $next): mixed
    {
        // Log da requisição
        error_log("Request: " . $request->getMethod() . " " . $request->getRequestUri());

        // Processar requisição
        $response = $next($request);

        // Log da resposta
        error_log("Response: HTTP " . $response->getStatusCode());

        return $response;
    }
}
```

**Uso:**
```php
Route::get('/api/test', function() {
    return ['message' => 'Hello World'];
})->middleware('simple_log');
```

### 2. Middleware de Validação de API Key

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class ApiKeyMiddleware implements MiddlewareInterface
{
    private array $validApiKeys = [
        'mobile_app' => 'sk_mobile_12345',
        'web_app' => 'sk_web_67890',
        'integration' => 'sk_int_abcdef'
    ];

    public function handle(Request $request, Closure $next): mixed
    {
        $apiKey = $request->header('X-API-Key');

        if (!$apiKey || !in_array($apiKey, $this->validApiKeys)) {
            return Response::json([
                'error' => 'Invalid API Key',
                'message' => 'Please provide a valid API key in X-API-Key header'
            ], 401);
        }

        // Adicionar informações do cliente ao request
        $clientName = array_search($apiKey, $this->validApiKeys);
        $request->attributes->set('api_client', $clientName);

        return $next($request);
    }
}
```

**Uso:**
```php
// Proteger endpoint para aplicações externas
Route::middleware('api_key')->group(function() {
    Route::get('/api/external/vendas', [ExternalVendasController::class, 'index']);
    Route::get('/api/external/stocks', [ExternalStocksController::class, 'index']);
});
```

### 3. Middleware de Verificação de Horário Comercial

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use DateTime;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class BusinessHoursMiddleware implements MiddlewareInterface
{
    private array $businessHours = [
        'monday' => ['08:00', '18:00'],
        'tuesday' => ['08:00', '18:00'],
        'wednesday' => ['08:00', '18:00'],
        'thursday' => ['08:00', '18:00'],
        'friday' => ['08:00', '18:00'],
        'saturday' => ['09:00', '13:00'],
        'sunday' => null // Fechado
    ];

    public function handle(Request $request, Closure $next): mixed
    {
        $now = new DateTime();
        $dayOfWeek = strtolower($now->format('l'));
        $currentTime = $now->format('H:i');

        $todayHours = $this->businessHours[$dayOfWeek];

        if (!$todayHours) {
            return Response::json([
                'error' => 'Service Unavailable',
                'message' => 'Service is closed on Sundays',
                'next_opening' => 'Monday 08:00'
            ], 503);
        }

        [$openTime, $closeTime] = $todayHours;

        if ($currentTime < $openTime || $currentTime > $closeTime) {
            return Response::json([
                'error' => 'Service Unavailable',
                'message' => "Service is available from {$openTime} to {$closeTime}",
                'current_time' => $currentTime
            ], 503);
        }

        return $next($request);
    }
}
```

## 🟡 Nível Intermediário - Para Desenvolvedores Pleno

### 1. Middleware de Cache Inteligente

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class SmartCacheMiddleware implements MiddlewareInterface
{
    private $cache;
    private array $cacheConfig = [
        'GET /api/vendas' => 300, // 5 minutos
        'GET /api/stocks' => 60,  // 1 minuto
        'GET /api/reports' => 1800, // 30 minutos
    ];

    public function __construct()
    {
        $this->cache = app('cache');
    }

    public function handle(Request $request, Closure $next): mixed
    {
        // Só cachear GET requests
        if ($request->getMethod() !== 'GET') {
            return $next($request);
        }

        $cacheKey = $this->generateCacheKey($request);
        $ttl = $this->getCacheTTL($request);

        if (!$ttl) {
            return $next($request);
        }

        // Verificar cache
        $cached = $this->cache->get($cacheKey);
        if ($cached) {
            $response = Response::json(json_decode($cached, true));
            $response->header('X-Cache', 'HIT');
            $response->header('X-Cache-Key', $cacheKey);
            return $response;
        }

        // Processar requisição
        $response = $next($request);

        // Cachear apenas respostas de sucesso
        if ($response instanceof Response && $response->getStatusCode() === 200) {
            $this->cache->put($cacheKey, $response->getContent(), $ttl);
        }

        $response->header('X-Cache', 'MISS');
        $response->header('X-Cache-TTL', (string) $ttl);

        return $response;
    }

    private function generateCacheKey(Request $request): string
    {
        $user = app('guard')->user();
        $userId = $user['id'] ?? 'guest';
        $companyId = $user['empresa'] ?? 'default';

        $key = sprintf(
            'api_cache:%s:%s:%s:%s',
            $companyId,
            $userId,
            $request->getMethod(),
            md5($request->getRequestUri() . serialize($request->query->all()))
        );

        return $key;
    }

    private function getCacheTTL(Request $request): ?int
    {
        $pattern = $request->getMethod() . ' middleware-examples.md' . $request->getPathInfo();

        foreach ($this->cacheConfig as $route => $ttl) {
            if (str_starts_with($pattern, $route)) {
                return $ttl;
            }
        }

        return null;
    }
}
```

### 2. Middleware de Audit Trail

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class AuditTrailMiddleware implements MiddlewareInterface
{
    private $database;

    public function __construct()
    {
        $this->database = app('database');
    }

    public function handle(Request $request, Closure $next): mixed
    {
        $startTime = microtime(true);
        $user = app('guard')->user();

        // Processar requisição
        $response = $next($request);

        // Registrar audit apenas para operações importantes
        if ($this->shouldAudit($request, $response)) {
            $this->logAuditTrail($request, $response, $user, $startTime);
        }

        return $response;
    }

    private function shouldAudit(Request $request, $response): bool
    {
        // Não auditar GETs simples de consulta
        if ($request->getMethod() === 'GET' && !str_contains($request->getPathInfo(), 'admin')) {
            return false;
        }

        // Auditar todas as operações de escrita
        if (in_array($request->getMethod(), ['POST', 'PUT', 'PATCH', 'DELETE'])) {
            return true;
        }

        // Auditar operações administrativas
        if (str_contains($request->getPathInfo(), '/admin/')) {
            return true;
        }

        // Auditar relatórios importantes
        if (str_contains($request->getPathInfo(), '/reports/')) {
            return true;
        }

        return false;
    }

    private function logAuditTrail(Request $request, $response, ?array $user, float $startTime): void
    {
        $duration = round((microtime(true) - $startTime) * 1000, 2);
        $statusCode = $response instanceof Response ? $response->getStatusCode() : 200;

        $auditData = [
            'user_id' => $user['id'] ?? null,
            'user_name' => $user['nome'] ?? 'Anonymous',
            'company_id' => $user['empresa'] ?? null,
            'method' => $request->getMethod(),
            'url' => $request->getRequestUri(),
            'ip_address' => $request->getClientIp(),
            'user_agent' => $request->header('User-Agent'),
            'status_code' => $statusCode,
            'duration_ms' => $duration,
            'request_data' => $this->sanitizeRequestData($request),
            'created_at' => date('Y-m-d H:i:s')
        ];

        $this->database->insert('audit_trail', $auditData);
    }

    private function sanitizeRequestData(Request $request): string
    {
        $data = $request->all();

        // Remover campos sensíveis
        $sensitiveFields = ['password', 'token', 'api_key', 'card_number'];
        foreach ($sensitiveFields as $field) {
            if (isset($data[$field])) {
                $data[$field] = '[REDACTED]';
            }
        }

        return json_encode($data);
    }
}
```

### 3. Middleware de Controle de Acesso por IP

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class IpAccessControlMiddleware implements MiddlewareInterface
{
    private array $config;

    public function __construct()
    {
        $this->config = [
            'whitelist' => [
                'admin' => [
                    '192.168.1.0/24',  // Rede interna
                    '10.0.0.0/8',      // VPN
                    '127.0.0.1'        // Localhost
                ],
                'api' => [
                    '203.0.113.0/24',  // IPs de clientes API
                    '198.51.100.0/24'
                ]
            ],
            'blacklist' => [
                '192.0.2.1',       // IP conhecido por ataques
                '203.0.113.100'
            ]
        ];
    }

    public function handle(Request $request, Closure $next, string $accessLevel = 'api'): mixed
    {
        $clientIp = $this->getClientIp($request);

        // Verificar blacklist primeiro
        if ($this->isBlacklisted($clientIp)) {
            return $this->createAccessDeniedResponse('IP blocked');
        }

        // Verificar whitelist
        if (!$this->isWhitelisted($clientIp, $accessLevel)) {
            return $this->createAccessDeniedResponse('IP not authorized for this access level');
        }

        // Adicionar informações de IP ao request
        $request->attributes->set('client_ip', $clientIp);
        $request->attributes->set('access_level', $accessLevel);

        return $next($request);
    }

    private function getClientIp(Request $request): string
    {
        $ipHeaders = [
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_REAL_IP',
            'HTTP_CF_CONNECTING_IP',
            'REMOTE_ADDR'
        ];

        foreach ($ipHeaders as $header) {
            if (isset($_SERVER[$header]) && !empty($_SERVER[$header])) {
                $ip = trim(explode(',', $_SERVER[$header])[0]);
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
                    return $ip;
                }
            }
        }

        return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
    }

    private function isBlacklisted(string $ip): bool
    {
        return in_array($ip, $this->config['blacklist']);
    }

    private function isWhitelisted(string $ip, string $accessLevel): bool
    {
        $whitelist = $this->config['whitelist'][$accessLevel] ?? [];

        foreach ($whitelist as $allowedIp) {
            if ($this->ipInRange($ip, $allowedIp)) {
                return true;
            }
        }

        return false;
    }

    private function ipInRange(string $ip, string $range): bool
    {
        if (str_contains($range, '/')) {
            [$subnet, $bits] = explode('/', $range);
            $ip = ip2long($ip);
            $subnet = ip2long($subnet);
            $mask = -1 << (32 - $bits);
            $subnet &= $mask;
            return ($ip & $mask) === $subnet;
        }

        return $ip === $range;
    }

    private function createAccessDeniedResponse(string $reason): Response
    {
        return Response::json([
            'error' => 'Access Denied',
            'message' => $reason,
            'code' => 403
        ], 403);
    }
}
```

## 🔴 Nível Avançado - Para Desenvolvedores Senior

### 1. Middleware de Circuit Breaker

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Exception;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class CircuitBreakerMiddleware implements MiddlewareInterface
{
    private $cache;
    private array $config = [
        'failure_threshold' => 5,    // Falhas antes de abrir circuito
        'timeout' => 60,             // Tempo em segundos para tentar novamente
        'success_threshold' => 3,     // Sucessos para fechar circuito
    ];

    public function __construct()
    {
        $this->cache = app('cache');
    }

    public function handle(Request $request, Closure $next, string $service = 'default'): mixed
    {
        $circuitKey = "circuit_breaker:{$service}";
        $circuitState = $this->getCircuitState($circuitKey);

        switch ($circuitState['state']) {
            case 'open':
                if (time() - $circuitState['opened_at'] > $this->config['timeout']) {
                    // Transição para half-open
                    $this->setCircuitState($circuitKey, 'half_open', 0, 0);
                    return $this->executeWithCircuitBreaker($request, $next, $circuitKey);
                } else {
                    return $this->createCircuitOpenResponse($service);
                }

            case 'half_open':
                return $this->executeWithCircuitBreaker($request, $next, $circuitKey);

            case 'closed':
            default:
                return $this->executeWithCircuitBreaker($request, $next, $circuitKey);
        }
    }

    private function executeWithCircuitBreaker(Request $request, Closure $next, string $circuitKey): mixed
    {
        try {
            $response = $next($request);

            // Verificar se a resposta indica falha
            if ($this->isResponseFailure($response)) {
                $this->recordFailure($circuitKey);
            } else {
                $this->recordSuccess($circuitKey);
            }

            return $response;

        } catch (Exception $e) {
            $this->recordFailure($circuitKey);
            throw $e;
        }
    }

    private function getCircuitState(string $key): array
    {
        return $this->cache->get($key, [
            'state' => 'closed',
            'failure_count' => 0,
            'success_count' => 0,
            'opened_at' => null
        ]);
    }

    private function setCircuitState(string $key, string $state, int $failures = null, int $successes = null): void
    {
        $currentState = $this->getCircuitState($key);

        $newState = [
            'state' => $state,
            'failure_count' => $failures ?? $currentState['failure_count'],
            'success_count' => $successes ?? $currentState['success_count'],
            'opened_at' => $state === 'open' ? time() : $currentState['opened_at']
        ];

        $this->cache->put($key, $newState, 3600); // Cache por 1 hora
    }

    private function recordFailure(string $key): void
    {
        $state = $this->getCircuitState($key);
        $failureCount = $state['failure_count'] + 1;

        if ($failureCount >= $this->config['failure_threshold']) {
            $this->setCircuitState($key, 'open', $failureCount, 0);
            $this->logCircuitBreaker($key, 'OPENED', "Failure threshold reached: {$failureCount}");
        } else {
            $this->setCircuitState($key, $state['state'], $failureCount, $state['success_count']);
        }
    }

    private function recordSuccess(string $key): void
    {
        $state = $this->getCircuitState($key);

        if ($state['state'] === 'half_open') {
            $successCount = $state['success_count'] + 1;

            if ($successCount >= $this->config['success_threshold']) {
                $this->setCircuitState($key, 'closed', 0, 0);
                $this->logCircuitBreaker($key, 'CLOSED', "Success threshold reached: {$successCount}");
            } else {
                $this->setCircuitState($key, 'half_open', 0, $successCount);
            }
        } else {
            // Reset failure count on success in closed state
            $this->setCircuitState($key, 'closed', 0, 0);
        }
    }

    private function isResponseFailure($response): bool
    {
        if ($response instanceof Response) {
            $statusCode = $response->getStatusCode();
            return $statusCode >= 500; // Server errors
        }

        return false;
    }

    private function createCircuitOpenResponse(string $service): Response
    {
        return Response::json([
            'error' => 'Service Unavailable',
            'message' => "Circuit breaker is open for service: {$service}",
            'retry_after' => $this->config['timeout'],
            'code' => 503
        ], 503);
    }

    private function logCircuitBreaker(string $key, string $action, string $reason): void
    {
        error_log("Circuit Breaker [{$key}] {$action}: {$reason}");
    }
}
```

### 2. Middleware de Rate Limiting Avançado com Sliding Window

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class SlidingWindowRateLimitMiddleware implements MiddlewareInterface
{
    private $cache;
    private array $rateLimits = [
        'api' => ['requests' => 60, 'window' => 60],        // 60 req/min
        'premium' => ['requests' => 200, 'window' => 60],   // 200 req/min
        'burst' => ['requests' => 10, 'window' => 10],      // 10 req/10s
    ];

    public function __construct()
    {
        $this->cache = app('cache');
    }

    public function handle(Request $request, Closure $next, string $tier = 'api'): mixed
    {
        $user = app('guard')->user();
        $key = $this->generateKey($request, $user, $tier);
        $limit = $this->rateLimits[$tier] ?? $this->rateLimits['api'];

        if (!$this->allowRequest($key, $limit)) {
            return $this->createRateLimitResponse($key, $limit, $tier);
        }

        $this->recordRequest($key, $limit);

        $response = $next($request);

        // Adicionar headers informativos
        $remaining = $this->getRemainingRequests($key, $limit);
        $resetTime = $this->getResetTime($key, $limit);

        if ($response instanceof Response) {
            $response->headers([
                'X-RateLimit-Limit' => $limit['requests'],
                'X-RateLimit-Remaining' => max(0, $remaining),
                'X-RateLimit-Reset' => $resetTime,
                'X-RateLimit-Window' => $limit['window'],
                'X-RateLimit-Tier' => $tier
            ]);
        }

        return $response;
    }

    private function generateKey(Request $request, ?array $user, string $tier): string
    {
        $identifier = $user ? 'user:' . $user['id'] : 'ip:' . $request->getClientIp();
        return "rate_limit:sliding:{$tier}:{$identifier}";
    }

    private function allowRequest(string $key, array $limit): bool
    {
        $now = time();
        $windowStart = $now - $limit['window'];

        // Obter todas as requisições na janela atual
        $requests = $this->getRequestsInWindow($key, $windowStart, $now);

        return count($requests) < $limit['requests'];
    }

    private function recordRequest(string $key, array $limit): void
    {
        $now = time();
        $windowStart = $now - $limit['window'];

        // Obter requisições atuais
        $requests = $this->getRequestsInWindow($key, $windowStart, $now);

        // Adicionar nova requisição
        $requests[] = $now;

        // Limpar requisições antigas (fora da janela)
        $requests = array_filter($requests, fn($time) => $time >= $windowStart);

        // Salvar no cache
        $ttl = $limit['window'] + 10; // TTL um pouco maior que a janela
        $this->cache->put($key, $requests, $ttl);
    }

    private function getRequestsInWindow(string $key, int $windowStart, int $now): array
    {
        $requests = $this->cache->get($key, []);

        // Filtrar apenas requisições na janela atual
        return array_filter($requests, fn($time) => $time >= $windowStart && $time <= $now);
    }

    private function getRemainingRequests(string $key, array $limit): int
    {
        $now = time();
        $windowStart = $now - $limit['window'];
        $requests = $this->getRequestsInWindow($key, $windowStart, $now);

        return $limit['requests'] - count($requests);
    }

    private function getResetTime(string $key, array $limit): int
    {
        $now = time();
        $windowStart = $now - $limit['window'];
        $requests = $this->getRequestsInWindow($key, $windowStart, $now);

        if (empty($requests)) {
            return $now + $limit['window'];
        }

        // Tempo até que a requisição mais antiga saia da janela
        $oldestRequest = min($requests);
        return $oldestRequest + $limit['window'];
    }

    private function createRateLimitResponse(string $key, array $limit, string $tier): Response
    {
        $resetTime = $this->getResetTime($key, $limit);
        $retryAfter = $resetTime - time();

        return Response::json([
            'error' => 'Too Many Requests',
            'message' => "Rate limit exceeded for tier: {$tier}",
            'limit' => $limit['requests'],
            'window_seconds' => $limit['window'],
            'retry_after' => $retryAfter,
            'reset_at' => date('c', $resetTime),
            'code' => 429
        ], 429)
        ->headers([
            'Retry-After' => $retryAfter,
            'X-RateLimit-Limit' => $limit['requests'],
            'X-RateLimit-Remaining' => 0,
            'X-RateLimit-Reset' => $resetTime,
            'X-RateLimit-Tier' => $tier
        ]);
    }
}
```

### 3. Middleware de Transformação de Request/Response

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Http\Response;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class RequestResponseTransformerMiddleware implements MiddlewareInterface
{
    private array $transformers = [];

    public function __construct()
    {
        $this->registerTransformers();
    }

    public function handle(Request $request, Closure $next, string $transformer = 'default'): mixed
    {
        // Transformar request de entrada
        $transformedRequest = $this->transformRequest($request, $transformer);

        // Processar com request transformado
        $response = $next($transformedRequest);

        // Transformar response de saída
        return $this->transformResponse($response, $transformer, $request);
    }

    private function registerTransformers(): void
    {
        $this->transformers = [
            'vendas' => [
                'request' => [$this, 'transformVendasRequest'],
                'response' => [$this, 'transformVendasResponse']
            ],
            'stocks' => [
                'request' => [$this, 'transformStocksRequest'],
                'response' => [$this, 'transformStocksResponse']
            ],
            'legacy' => [
                'request' => [$this, 'transformLegacyRequest'],
                'response' => [$this, 'transformLegacyResponse']
            ]
        ];
    }

    private function transformRequest(Request $request, string $transformer): Request
    {
        if (isset($this->transformers[$transformer]['request'])) {
            return call_user_func($this->transformers[$transformer]['request'], $request);
        }

        return $request;
    }

    private function transformResponse($response, string $transformer, Request $originalRequest)
    {
        if (isset($this->transformers[$transformer]['response'])) {
            return call_user_func($this->transformers[$transformer]['response'], $response, $originalRequest);
        }

        return $response;
    }

    // Transformador para módulo de vendas
    private function transformVendasRequest(Request $request): Request
    {
        $data = $request->all();

        // Normalizar campos de cliente
        if (isset($data['cliente_id'])) {
            $data['customer_id'] = $data['cliente_id'];
            unset($data['cliente_id']);
        }

        // Normalizar datas
        if (isset($data['data_venda'])) {
            $data['sale_date'] = $this->normalizeDate($data['data_venda']);
            unset($data['data_venda']);
        }

        // Transformar array de produtos
        if (isset($data['produtos'])) {
            $data['products'] = array_map([$this, 'transformProduct'], $data['produtos']);
            unset($data['produtos']);
        }

        // Atualizar request com dados transformados
        $request->merge($data);

        return $request;
    }

    private function transformVendasResponse($response, Request $request)
    {
        if (!($response instanceof Response)) {
            return $response;
        }

        $data = json_decode($response->getContent(), true);

        if (!is_array($data)) {
            return $response;
        }

        // Transformar resposta para formato esperado pelo frontend
        $transformed = $this->transformVendasData($data);

        // Adicionar metadados
        $transformed['_metadata'] = [
            'transformed_at' => date('c'),
            'transformer' => 'vendas',
            'request_id' => $request->header('X-Request-ID', uniqid())
        ];

        return Response::json($transformed);
    }

    private function transformVendasData(array $data): array
    {
        // Se é uma lista de vendas
        if (isset($data[0]) && is_array($data[0])) {
            return array_map([$this, 'transformSingleVenda'], $data);
        }

        // Se é uma única venda
        return $this->transformSingleVenda($data);
    }

    private function transformSingleVenda(array $venda): array
    {
        return [
            'id' => $venda['id'] ?? null,
            'numero' => $venda['numero'] ?? $venda['number'] ?? null,
            'cliente' => [
                'id' => $venda['customer_id'] ?? $venda['cliente_id'] ?? null,
                'nome' => $venda['customer_name'] ?? $venda['cliente_nome'] ?? null,
                'nif' => $venda['customer_nif'] ?? $venda['cliente_nif'] ?? null
            ],
            'data_venda' => $venda['sale_date'] ?? $venda['data_venda'] ?? null,
            'total' => (float) ($venda['total'] ?? 0),
            'estado' => $venda['status'] ?? $venda['estado'] ?? 'pendente',
            'produtos' => $this->transformProductsArray($venda['products'] ?? $venda['produtos'] ?? [])
        ];
    }

    private function transformProduct(array $product): array
    {
        return [
            'id' => $product['produto_id'] ?? $product['id'],
            'codigo' => $product['codigo'] ?? $product['code'],
            'nome' => $product['nome'] ?? $product['name'],
            'quantidade' => (float) ($product['quantidade'] ?? $product['quantity'] ?? 1),
            'preco_unitario' => (float) ($product['preco'] ?? $product['price'] ?? 0),
            'total' => (float) ($product['total'] ?? 0)
        ];
    }

    private function transformProductsArray(array $products): array
    {
        return array_map([$this, 'transformProduct'], $products);
    }

    // Transformador para sistema legado
    private function transformLegacyRequest(Request $request): Request
    {
        $data = $request->all();

        // Converter campos em snake_case para camelCase
        $transformed = [];
        foreach ($data as $key => $value) {
            $camelKey = $this->snakeToCamel($key);
            $transformed[$camelKey] = $value;
        }

        $request->replace($transformed);
        return $request;
    }

    private function transformLegacyResponse($response, Request $request)
    {
        if (!($response instanceof Response)) {
            return $response;
        }

        $data = json_decode($response->getContent(), true);

        if (!is_array($data)) {
            return $response;
        }

        // Converter camelCase de volta para snake_case para compatibilidade
        $transformed = $this->arrayKeysToSnakeCase($data);

        return Response::json($transformed);
    }

    private function normalizeDate(string $date): string
    {
        try {
            $dateTime = new \DateTime($date);
            return $dateTime->format('Y-m-d H:i:s');
        } catch (\Exception $e) {
            return $date; // Retorna original se não conseguir parsear
        }
    }

    private function snakeToCamel(string $string): string
    {
        return lcfirst(str_replace('_', '', ucwords($string, '_')));
    }

    private function camelToSnake(string $string): string
    {
        return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $string));
    }

    private function arrayKeysToSnakeCase(array $array): array
    {
        $result = [];

        foreach ($array as $key => $value) {
            $newKey = is_string($key) ? $this->camelToSnake($key) : $key;

            if (is_array($value)) {
                $result[$newKey] = $this->arrayKeysToSnakeCase($value);
            } else {
                $result[$newKey] = $value;
            }
        }

        return $result;
    }
}
```

## 🔧 Middleware de Composição e Pipeline

### Middleware Orchestrator - Para Casos Complexos

```php
<?php
namespace Og\Modules\Common\Http\Middleware;

use Closure;
use Og\Modules\Common\Http\Request;
use Og\Modules\Common\Middleware\MiddlewareInterface;

class MiddlewareOrchestrator implements MiddlewareInterface
{
    private array $pipelines = [];

    public function handle(Request $request, Closure $next, string $pipeline = 'default'): mixed
    {
        if (!isset($this->pipelines[$pipeline])) {
            throw new \InvalidArgumentException("Pipeline '{$pipeline}' not found");
        }

        $middlewares = $this->pipelines[$pipeline];

        return $this->processPipeline($request, $middlewares, $next);
    }

    public function registerPipeline(string $name, array $middlewares): void
    {
        $this->pipelines[$name] = $middlewares;
    }

    private function processPipeline(Request $request, array $middlewares, Closure $destination): mixed
    {
        $pipeline = array_reduce(
            array_reverse($middlewares),
            function ($carry, $middlewareConfig) {
                return function ($request) use ($carry, $middlewareConfig) {
                    [$class, $params] = $this->parseMiddlewareConfig($middlewareConfig);
                    $middleware = new $class();
                    return $middleware->handle($request, $carry, ...$params);
                };
            },
            $destination
        );

        return $pipeline($request);
    }

    private function parseMiddlewareConfig($config): array
    {
        if (is_string($config)) {
            return [$config, []];
        }

        if (is_array($config) && isset($config['class'])) {
            return [$config['class'], $config['params'] ?? []];
        }

        throw new \InvalidArgumentException('Invalid middleware configuration');
    }
}

// Uso em um service provider ou configuração
$orchestrator = new MiddlewareOrchestrator();

$orchestrator->registerPipeline('erp_full', [
    ['class' => IpAccessControlMiddleware::class, 'params' => ['admin']],
    ['class' => GuardAuthMiddleware::class],
    ['class' => AuditTrailMiddleware::class],
    ['class' => SlidingWindowRateLimitMiddleware::class, 'params' => ['premium']],
    ['class' => RequestResponseTransformerMiddleware::class, 'params' => ['vendas']],
    ['class' => CircuitBreakerMiddleware::class, 'params' => ['vendas_service']]
]);

// Em uma rota
Route::get('/api/vendas/complex', [VendasController::class, 'complexOperation'])
      ->middleware('orchestrator:erp_full');
```

Estes exemplos mostram como implementar middlewares progressivamente mais complexos, desde verificações simples até sistemas sofisticados de circuit breakers e transformação de dados, todos adaptados ao contexto de um ERP como o OfficeGest.
