<?php
namespace Modules\Servicos;

use Gestoreasy\Core\Module\ModuleInterface;
use Gestoreasy\Core\Routing\Router;
use Gestoreasy\Core\Http\Request;
use Gestoreasy\Core\Http\Response;
use Gestoreasy\Core\Tenant\ConnectionManager;
use Gestoreasy\Core\Auth\AuthMiddleware;
use Gestoreasy\Core\Auth\Rbac;
use Gestoreasy\Core\View\View;
use Gestoreasy\Core\Config\Config;
use Gestoreasy\Core\Fiscal\Nfse\ProviderFactory;
use Gestoreasy\Core\Fiscal\Nfse\NfseProviderInterface;

class Module implements ModuleInterface {
    public function register(Router $router): void {
        // Views (HTML)
        $router->get('/servicos/dashboard', function(Request $r){
            return View::renderWithLayout('servicos/dashboard', ['title' => 'Serviços: Dashboard']);
        }, [AuthMiddleware::requireAuthHtml()]);

        $router->get('/servicos/veiculos', function(Request $r){
            return View::renderWithLayout('servicos/veiculos', ['title' => 'Serviços: Veículos']);
        }, [AuthMiddleware::requireAuthHtml()]);

        $router->get('/servicos/ordens', function(Request $r){
            return View::renderWithLayout('servicos/ordens', ['title' => 'Serviços: Ordens de Serviço']);
        }, [AuthMiddleware::requireAuthHtml()]);

        $router->get('/servicos/checklists', function(Request $r){
            return View::renderWithLayout('servicos/checklists', ['title' => 'Serviços: Checklists']);
        }, [AuthMiddleware::requireAuthHtml()]);

        $router->get('/servicos/documentos', function(Request $r){
            return View::renderWithLayout('servicos/documentos', ['title' => 'Serviços: Documentos']);
        }, [AuthMiddleware::requireAuthHtml()]);

        $router->get('/servicos/relatorios', function(Request $r){
            return View::renderWithLayout('servicos/relatorios', ['title' => 'Serviços: Relatórios']);
        }, [AuthMiddleware::requireAuthHtml()]);

        // NFS-e: emissão
        $router->get('/servicos/nfse', function(Request $r){
            return View::renderWithLayout('servicos/nfse', ['title' => 'Serviços: Emissão de NFS-e']);
        }, [AuthMiddleware::requireAuthHtml()]);

        // APIs básicas
        // Veículos: listagem simples e criação
        $router->get('/api/servicos/veiculos', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $q = trim((string)($r->query['q'] ?? ''));
            $params = [];
            $sql = 'SELECT v.id, v.placa, v.chassi, v.renavam, v.marca, v.modelo, v.ano, v.cor, v.tipo, v.cliente_id, COALESCE(c.nome, c.nome_fantasia, c.razao_social) AS cliente_nome FROM vehicles v LEFT JOIN clients c ON c.id = v.cliente_id';
            if ($q !== '') { $sql .= ' WHERE v.placa LIKE ? OR v.modelo LIKE ? OR v.marca LIKE ?'; $params = ["%$q%","%$q%","%$q%"]; }
            $sql .= ' ORDER BY v.id DESC LIMIT 200';
            $stmt = $pdo->prepare($sql); $stmt->execute($params);
            return Response::json(['data' => $stmt->fetchAll()]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        $router->post('/api/servicos/veiculos', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $placa = trim((string)($r->body['placa'] ?? ''));
            $modelo = trim((string)($r->body['modelo'] ?? ''));
            $marca = trim((string)($r->body['marca'] ?? ''));
            $clienteId = isset($r->body['cliente_id']) ? (int)$r->body['cliente_id'] : null;
            $tipo = (string)($r->body['tipo'] ?? 'carro');
            // Campos opcionais adicionais
            $chassi = trim((string)($r->body['chassi'] ?? ''));
            $renavam = trim((string)($r->body['renavam'] ?? ''));
            $ano = isset($r->body['ano']) ? (int)$r->body['ano'] : null;
            $cor = trim((string)($r->body['cor'] ?? ''));
            $placaInfo = $r->body['placa_info'] ?? null;
            $placaInfoJson = null;
            if (is_array($placaInfo)) { $placaInfoJson = json_encode($placaInfo, JSON_UNESCAPED_UNICODE); }
            elseif (is_string($placaInfo) && $placaInfo !== '') { $placaInfoJson = $placaInfo; }
            if ($placa === '' || $modelo === '') return Response::json(['error' => 'placa e modelo são obrigatórios'], 422);
            // Normaliza e valida placa (Mercosul e antigo)
            $norm = strtoupper(preg_replace('/[^A-Z0-9]/i', '', $placa));
            $isOld = (bool)preg_match('/^[A-Z]{3}[0-9]{4}$/', $norm);
            $isMerc = (bool)preg_match('/^[A-Z]{3}[0-9][A-Z][0-9]{2}$/', $norm);
            if (!$isOld && !$isMerc) return Response::json(['error' => 'placa inválida (formato inválido)'], 422);
            // Evita duplicados de placa
            $chk = $pdo->prepare('SELECT id FROM vehicles WHERE placa = ? LIMIT 1');
            $chk->execute([$norm]);
            if ($chk->fetch()) return Response::json(['error' => 'placa já cadastrada'], 409);
            $stmt = $pdo->prepare('INSERT INTO vehicles (placa, modelo, marca, tipo, cliente_id, chassi, renavam, ano, cor, placa_info_json) VALUES (?,?,?,?,?,?,?,?,?,?)');
            $stmt->execute([$norm, $modelo, $marca ?: null, $tipo, $clienteId, $chassi ?: null, $renavam ?: null, $ano ?: null, $cor ?: null, $placaInfoJson]);
            $id = (int)$pdo->lastInsertId();
            return Response::json(['ok' => true, 'id' => $id]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.create'])]);

        // Veículos: obter por ID
        $router->get('/api/servicos/veiculos/{id}', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            $stmt = $pdo->prepare('SELECT id, placa, chassi, renavam, marca, modelo, ano, cor, tipo, cliente_id, placa_info_json FROM vehicles WHERE id = ? LIMIT 1');
            $stmt->execute([$id]);
            $row = $stmt->fetch();
            if (!$row) return Response::json(['error' => 'não encontrado'], 404);
            return Response::json(['data' => $row]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Veículos: atualização
        $router->put('/api/servicos/veiculos/{id}', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            $placa = trim((string)($r->body['placa'] ?? ''));
            $modelo = trim((string)($r->body['modelo'] ?? ''));
            $marca = trim((string)($r->body['marca'] ?? ''));
            $tipo = (string)($r->body['tipo'] ?? 'carro');
            $clienteId = isset($r->body['cliente_id']) ? (int)$r->body['cliente_id'] : null;
            // Campos opcionais adicionais
            $chassi = trim((string)($r->body['chassi'] ?? ''));
            $renavam = trim((string)($r->body['renavam'] ?? ''));
            $ano = isset($r->body['ano']) ? (int)$r->body['ano'] : null;
            $cor = trim((string)($r->body['cor'] ?? ''));
            $placaInfo = $r->body['placa_info'] ?? null;
            $placaInfoJson = null;
            if (is_array($placaInfo)) { $placaInfoJson = json_encode($placaInfo, JSON_UNESCAPED_UNICODE); }
            elseif (is_string($placaInfo) && $placaInfo !== '') { $placaInfoJson = $placaInfo; }
            if ($placa !== '') {
                $norm = strtoupper(preg_replace('/[^A-Z0-9]/i', '', $placa));
                $isOld = (bool)preg_match('/^[A-Z]{3}[0-9]{4}$/', $norm);
                $isMerc = (bool)preg_match('/^[A-Z]{3}[0-9][A-Z][0-9]{2}$/', $norm);
                if (!$isOld && !$isMerc) return Response::json(['error' => 'placa inválida'], 422);
                // verifica duplicidade
                $chk = $pdo->prepare('SELECT id FROM vehicles WHERE placa = ? AND id <> ? LIMIT 1');
                $chk->execute([$norm, $id]);
                if ($chk->fetch()) return Response::json(['error' => 'placa já cadastrada'], 409);
                $placa = $norm;
            }
            $stmt = $pdo->prepare('UPDATE vehicles SET placa = COALESCE(?, placa), modelo = COALESCE(?, modelo), marca = COALESCE(?, marca), tipo = COALESCE(?, tipo), cliente_id = ?, chassi = COALESCE(?, chassi), renavam = COALESCE(?, renavam), ano = COALESCE(?, ano), cor = COALESCE(?, cor), placa_info_json = COALESCE(?, placa_info_json) WHERE id = ?');
            $stmt->execute([
                $placa !== '' ? $placa : null,
                $modelo !== '' ? $modelo : null,
                $marca !== '' ? $marca : null,
                $tipo ?: null,
                $clienteId,
                $chassi !== '' ? $chassi : null,
                $renavam !== '' ? $renavam : null,
                $ano ?: null,
                $cor !== '' ? $cor : null,
                $placaInfoJson,
                $id
            ]);
            return Response::json(['ok' => true]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update'])]);

        // Veículos: consulta por placa com fallback (DB -> API externa)
        $router->get('/api/servicos/veiculos/placa/{placa}', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $placaParam = (string)($params[0] ?? '');
            $norm = strtoupper(preg_replace('/[^A-Z0-9]/i', '', $placaParam));
            $isOld = (bool)preg_match('/^[A-Z]{3}[0-9]{4}$/', $norm);
            $isMerc = (bool)preg_match('/^[A-Z]{3}[0-9][A-Z][0-9]{2}$/', $norm);
            if (!$isOld && !$isMerc) return Response::json(['ok' => false, 'error' => 'placa inválida'], 422);
            // Primeiro tenta no banco
            $stmt = $pdo->prepare('SELECT id, placa, chassi, renavam, marca, modelo, ano, cor, tipo, cliente_id, placa_info_json FROM vehicles WHERE placa = ? LIMIT 1');
            $stmt->execute([$norm]);
            $row = $stmt->fetch();
            if ($row) {
                return Response::json(['ok' => true, 'source' => 'db', 'data' => $row]);
            }
            // Fallback: API externa
            $base = (string)Config::get('PLATE_API_URL', '');
            $token = (string)Config::get('PLATE_API_TOKEN', '');
            if ($base === '') {
                // Fallback amigável: integração externa não configurada
                // Retorna estrutura mínima para permitir preenchimento manual
                return Response::json([
                    'ok' => true,
                    'source' => 'fallback',
                    'data' => [
                        'placa' => $norm,
                        'modelo' => null,
                        'marca' => null,
                        'ano' => null,
                        'cor' => null,
                        'chassi' => null,
                        'renavam' => null,
                        'tipo' => null,
                    ],
                    'alerts' => [],
                    'message' => 'integration_not_configured'
                ]);
            }
            $url = str_contains($base, '{placa}') ? str_replace('{placa}', urlencode($norm), $base) : ($base . (str_contains($base, '?') ? '&' : '?') . 'placa=' . urlencode($norm));
            $headers = "Accept: application/json\r\nUser-Agent: gestoreasy/servicos-module";
            if ($token !== '') {
                $headers .= "\r\nAuthorization: Bearer $token";
            }
            $ctx = stream_context_create(['http' => [ 'method' => 'GET', 'timeout' => 8, 'header' => $headers ]]);
            $res = @file_get_contents($url, false, $ctx);
            if ($res === false) { return Response::json(['ok' => false, 'error' => 'request_failed']); }
            $json = json_decode($res, true);
            if (!is_array($json) || count($json) === 0) { return Response::json(['ok' => false, 'error' => 'not_found']); }
            $data = $json;
            // Mapeamento básico e tolerante
            $veh = [
                'placa' => $norm,
                'modelo' => $data['modelo'] ?? ($data['modelo_veiculo'] ?? null),
                'marca'  => $data['marca'] ?? ($data['marca_modelo'] ?? null),
                'ano'    => $data['ano'] ?? ($data['ano_fabricacao'] ?? null),
                'cor'    => $data['cor'] ?? null,
                'chassi' => $data['chassi'] ?? null,
                'renavam'=> $data['renavam'] ?? null,
                'tipo'   => $data['tipo'] ?? null,
            ];
            // Sinalização de possíveis restrições graves (roubo/furto)
            $alerts = [];
            $flags = [
                (string)($data['roubo_furto'] ?? ''),
                (string)($data['indicador_roubo_furto'] ?? ''),
                (string)($data['situacao'] ?? ''),
                (string)($data['restricao'] ?? ''),
                (string)($data['alertas'] ?? ''),
            ];
            $joined = strtoupper(implode(' ', array_filter($flags, fn($v) => $v !== '')));
            if ($joined !== '' && (str_contains($joined, 'ROUBO') || str_contains($joined, 'FURTO') || str_contains($joined, 'THEFT'))) {
                $alerts[] = [
                    'code' => 'roubo_furto',
                    'severity' => 'danger',
                    'message' => 'Alerta: veículo com possível registro de roubo/furto'
                ];
            }
            return Response::json(['ok' => true, 'source' => 'external', 'data' => $veh, 'raw' => $data, 'alerts' => $alerts]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Veículos: remoção
        $router->delete('/api/servicos/veiculos/{id}', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            // Verifica vínculo com OS
            $ref = $pdo->prepare('SELECT id FROM service_orders WHERE vehicle_id = ? LIMIT 1');
            $ref->execute([$id]);
            if ($ref->fetch()) return Response::json(['error' => 'há ordens vinculadas a este veículo'], 409);
            $stmt = $pdo->prepare('DELETE FROM vehicles WHERE id = ?');
            $stmt->execute([$id]);
            return Response::json(['ok' => true]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.delete'])]);

        // Endpoint de seed removido: o seed deve ocorrer via script CLI

        // Ordens de serviço: listagem e criação simples
        $router->get('/api/servicos/ordens', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $sql = 'SELECT so.id, so.status, so.descricao, so.valor_total_cents, so.aberta_em, so.fechada_em, v.placa, v.modelo, so.cliente_id, COALESCE(c.nome, c.nome_fantasia, c.razao_social) AS cliente_nome FROM service_orders so LEFT JOIN vehicles v ON v.id = so.vehicle_id LEFT JOIN clients c ON c.id = so.cliente_id ORDER BY so.id DESC LIMIT 200';
            $stmt = $pdo->query($sql);
            return Response::json(['data' => $stmt->fetchAll()]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        $router->post('/api/servicos/ordens', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $vehicleId = (int)($r->body['vehicle_id'] ?? 0);
            $clienteId = isset($r->body['cliente_id']) ? (int)$r->body['cliente_id'] : null;
            $descricao = trim((string)($r->body['descricao'] ?? ''));
            $tecnico = trim((string)($r->body['tecnico'] ?? ''));
            if ($vehicleId <= 0) return Response::json(['error' => 'vehicle_id obrigatório'], 422);
            $stmt = $pdo->prepare('INSERT INTO service_orders (vehicle_id, cliente_id, status, descricao, tecnico_responsavel, aberta_em) VALUES (?,?,?,?,?,?)');
            $stmt->execute([$vehicleId, $clienteId, 'aberta', $descricao ?: null, $tecnico ?: null, date('Y-m-d H:i:s')]);
            return Response::json(['ok' => true, 'id' => (int)$pdo->lastInsertId()]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.create'])]);

        // Itens da OS: listagem
        $router->get('/api/servicos/ordens/{id}/itens', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $orderId = (int)($params[0] ?? 0);
            if ($orderId <= 0) return Response::json(['error' => 'id inválido'], 422);
            $chk = $pdo->prepare('SELECT id FROM service_orders WHERE id = ? LIMIT 1');
            $chk->execute([$orderId]);
            if (!$chk->fetch()) return Response::json(['error' => 'ordem não encontrada'], 404);
            $sql = 'SELECT i.id, i.order_id, i.product_id, i.descricao, i.quantidade, i.valor_unit_cents, i.subtotal_cents, p.codigo AS product_codigo, p.descricao AS product_nome FROM service_order_items i LEFT JOIN products p ON p.id = i.product_id WHERE i.order_id = ? ORDER BY i.id ASC';
            $stmt = $pdo->prepare($sql); $stmt->execute([$orderId]);
            return Response::json(['data' => $stmt->fetchAll()]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Itens da OS: inserir
        $router->post('/api/servicos/ordens/{id}/itens', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $orderId = (int)($params[0] ?? 0);
            if ($orderId <= 0) return Response::json(['error' => 'id inválido'], 422);
            $chk = $pdo->prepare('SELECT id FROM service_orders WHERE id = ? LIMIT 1');
            $chk->execute([$orderId]);
            if (!$chk->fetch()) return Response::json(['error' => 'ordem não encontrada'], 404);
            $productId = isset($r->body['product_id']) ? (int)$r->body['product_id'] : null;
            $descricao = trim((string)($r->body['descricao'] ?? ''));
            $quantidade = (float)($r->body['quantidade'] ?? 1);
            $valorUnitCents = (int)($r->body['valor_unit_cents'] ?? 0);
            if ($quantidade <= 0) return Response::json(['error' => 'quantidade deve ser > 0'], 422);
            if ($valorUnitCents < 0) return Response::json(['error' => 'valor_unit_cents inválido'], 422);
            if ($productId && $descricao === '') {
                try { $p = $pdo->prepare('SELECT descricao FROM products WHERE id = ?'); $p->execute([$productId]); $row = $p->fetch(); if ($row && ($row['descricao'] ?? '') !== '') { $descricao = (string)$row['descricao']; } } catch (\Throwable $e) {}
            }
            $subtotal = (int)round($valorUnitCents * $quantidade);
            $ins = $pdo->prepare('INSERT INTO service_order_items (order_id, product_id, descricao, quantidade, valor_unit_cents, subtotal_cents) VALUES (?,?,?,?,?,?)');
            $ins->execute([$orderId, $productId ?: null, $descricao !== '' ? $descricao : null, $quantidade, $valorUnitCents, $subtotal]);
            // Atualizar valor_total_cents da OS
            try { $sum = $pdo->prepare('SELECT COALESCE(SUM(subtotal_cents),0) AS s FROM service_order_items WHERE order_id = ?'); $sum->execute([$orderId]); $total = (int)($sum->fetch()['s'] ?? 0); $upd = $pdo->prepare('UPDATE service_orders SET valor_total_cents = ? WHERE id = ?'); $upd->execute([$total, $orderId]); } catch (\Throwable $e) {}
            $newId = 0; $dataRow = null;
            try {
                $newId = (int)$pdo->query('SELECT LAST_INSERT_ID()')->fetchColumn();
                if ($newId <= 0) {
                    $probe = $pdo->prepare('SELECT id, order_id, product_id, descricao, quantidade, valor_unit_cents, subtotal_cents FROM service_order_items WHERE order_id = ? AND descricao '.($descricao!==''? '= ?':'IS NULL').' AND quantidade = ? AND valor_unit_cents = ? AND subtotal_cents = ? ORDER BY id DESC LIMIT 1');
                    $params = [$orderId]; if ($descricao!=='') { $params[] = $descricao; } $params = array_merge($params, [$quantidade, $valorUnitCents, $subtotal]);
                    $probe->execute($params);
                    $dataRow = $probe->fetch();
                    if ($dataRow && isset($dataRow['id'])) { $newId = (int)$dataRow['id']; }
                } else {
                    $st = $pdo->prepare('SELECT id, order_id, product_id, descricao, quantidade, valor_unit_cents, subtotal_cents FROM service_order_items WHERE id = ?');
                    $st->execute([$newId]);
                    $dataRow = $st->fetch();
                }
            } catch (\Throwable $e) {}
            return Response::json(['ok' => true, 'id' => $newId, 'data' => $dataRow]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update'])]);

        // Itens da OS: remover
        $router->delete('/api/servicos/ordens/{id}/itens/{item_id}', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $orderId = (int)($params[0] ?? 0); $itemId = (int)($params[1] ?? 0);
            if ($orderId <= 0 || $itemId <= 0) return Response::json(['error' => 'ids inválidos'], 422);
            $chk = $pdo->prepare('SELECT id FROM service_orders WHERE id = ? LIMIT 1');
            $chk->execute([$orderId]);
            if (!$chk->fetch()) return Response::json(['error' => 'ordem não encontrada'], 404);
            $del = $pdo->prepare('DELETE FROM service_order_items WHERE id = ? AND order_id = ?');
            $del->execute([$itemId, $orderId]);
            // Atualizar total
            try { $sum = $pdo->prepare('SELECT COALESCE(SUM(subtotal_cents),0) AS s FROM service_order_items WHERE order_id = ?'); $sum->execute([$orderId]); $total = (int)($sum->fetch()['s'] ?? 0); $upd = $pdo->prepare('UPDATE service_orders SET valor_total_cents = ? WHERE id = ?'); $upd->execute([$total, $orderId]); } catch (\Throwable $e) {}
            return Response::json(['ok' => true]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update'])]);

        // Ordens de serviço: designar/atualizar técnico
        $router->post('/api/servicos/ordens/{id}/tecnico', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            $tecnico = trim((string)($r->body['tecnico'] ?? ''));
            $stmt = $pdo->prepare('UPDATE service_orders SET tecnico_responsavel = ? WHERE id = ?');
            $stmt->execute([$tecnico !== '' ? $tecnico : null, $id]);
            return Response::json(['ok' => true]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update'])]);

        $router->post('/api/servicos/ordens/{id}/status', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $id = (int)($params[0] ?? 0);
            $status = (string)($r->body['status'] ?? '');
            $allowed = ['aberta','em_andamento','aguardando','concluida','faturada','cancelada'];
            if (!in_array($status, $allowed, true)) return Response::json(['error' => 'status inválido'], 422);
            $stmt = $pdo->prepare('UPDATE service_orders SET status = ?, fechada_em = CASE WHEN ? IN ("concluida","faturada","cancelada") THEN NOW() ELSE NULL END WHERE id = ?');
            $stmt->execute([$status, $status, $id]);
            return Response::json(['ok' => true]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update'])]);

        // Ordens de serviço: emitir NFS-e (nota fiscal de serviço) a partir da OS
        $router->post('/api/servicos/ordens/{id}/emitir-nfs-e', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => ['code' => 'invalid_order', 'message' => 'ID inválido']], 422);

            // Busca OS
            $stmt = $pdo->prepare('SELECT id, status, descricao, valor_total_cents, cliente_id FROM service_orders WHERE id = ? LIMIT 1');
            $stmt->execute([$id]);
            $order = $stmt->fetch();
            if (!$order) return Response::json(['error' => ['code' => 'order_not_found', 'message' => 'Ordem de serviço não encontrada']], 404);
            if (in_array(($order['status'] ?? ''), ['faturada','cancelada'], true)) {
                return Response::json(['error' => ['code' => 'order_closed', 'message' => 'OS já faturada/cancelada']], 409);
            }
            // Cliente obrigatório para emissão de NFS-e
            $clienteId = (int)($order['cliente_id'] ?? 0);
            if ($clienteId <= 0) {
                return Response::json(['error' => ['code' => 'cliente_required', 'message' => 'Cliente obrigatório para emissão de NFS-e']], 422);
            }

            // Buscar empresa do tenant (para UF, IM, CNPJ, cidade, etc.)
            $company = [
                'id' => null, 'nome_fantasia' => null, 'razao_social' => null, 'cnpj' => null,
                'ie' => null, 'im' => null, 'cidade' => null, 'estado' => null
            ];
            try {
                $stE = $pdo->query('SHOW COLUMNS FROM empresas LIKE "estado"');
                $colsOk = $stE && $stE->fetch();
                $sqlEmpresa = $colsOk
                    ? 'SELECT id, nome_fantasia, razao_social, cnpj, ie, im, cidade, estado FROM empresas ORDER BY id ASC LIMIT 1'
                    : 'SELECT id, nome_fantasia, razao_social, cnpj, ie, im, cidade, uf AS estado FROM empresas ORDER BY id ASC LIMIT 1';
                $rowEmp = $pdo->query($sqlEmpresa)->fetch();
                if (is_array($rowEmp)) { $company = $rowEmp; }
            } catch (\Throwable $e) { /* ignore e mantém defaults */ }

            // Buscar cliente
            $client = [
                'id' => $clienteId, 'nome' => null, 'nome_fantasia' => null, 'razao_social' => null,
                'cpf' => null, 'cnpj' => null, 'ie' => null, 'im' => null, 'cidade' => null, 'estado' => null, 'email' => null
            ];
            try {
                $stC = $pdo->prepare('SELECT id, nome, nome_fantasia, razao_social, cpf, cnpj, ie, im, cidade, estado, email FROM clients WHERE id = ? LIMIT 1');
                $stC->execute([$clienteId]);
                $rowCli = $stC->fetch();
                if (is_array($rowCli)) { $client = $rowCli; }
            } catch (\Throwable $e) { /* ignore */ }

            // Seleciona provider por UF da empresa e emite NFS-e conforme regras
            $provider = ProviderFactory::forTenant($pdo);
            $result = $provider->emit($pdo, $order, $company, $client);

            $nfse = [
                'tipo' => 'NFS-e',
                'numero' => (string)($result['nfse']['numero'] ?? ''),
                'codigo_verificacao' => (string)($result['nfse']['codigo_verificacao'] ?? ''),
                'data_emissao' => (string)($result['nfse']['data_emissao'] ?? date('c')),
                'order_id' => $id,
                'cliente_id' => $clienteId,
                'descricao_servico' => $order['descricao'] ?: 'Serviços prestados',
                'valor_total' => (float)($result['nfse']['valor_servico'] ?? (((int)($order['valor_total_cents'] ?? 0))/100.0)),
                'valor_iss' => (float)($result['nfse']['valor_iss'] ?? 0.0),
                'prestador_inscricao_municipal' => $result['nfse']['prestador_inscricao_municipal'] ?? null,
                'municipio_codigo' => $result['nfse']['municipio_codigo'] ?? null,
                'status' => (string)($result['nfse']['status'] ?? 'autorizada'),
                'provider' => $result['provider'] ?? null
            ];

            // Salva arquivo JSON em uploads
            $uploadDir = __DIR__ . '/../../public/uploads/services/nfse/' . $id;
            if (!is_dir($uploadDir)) @mkdir($uploadDir, 0775, true);
            $fileName = 'nfse_' . $id . '_' . date('Ymd_His') . '.json';
            $filePath = $uploadDir . '/' . $fileName;
            @file_put_contents($filePath, json_encode($nfse, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
            $url = '/uploads/services/nfse/' . $id . '/' . $fileName;

            // Registra em service_documents
            $ins = $pdo->prepare('INSERT INTO service_documents (order_id, tipo, titulo, arquivo_nome, storage_path, url) VALUES (?,?,?,?,?,?)');
            $ins->execute([$id, 'nfse', 'NFS-e', $fileName, $filePath, $url]);
            $docId = (int)$pdo->lastInsertId();

            // Atualiza status conforme emissão
            $newStatus = ((string)($nfse['status'] ?? 'autorizada') === 'autorizada') ? 'faturada' : 'concluida';
            $up = $pdo->prepare('UPDATE service_orders SET status = ?, fechada_em = CASE WHEN ? = "faturada" THEN NOW() ELSE fechada_em END WHERE id = ?');
            $up->execute([$newStatus, $newStatus, $id]);

            return Response::json(['ok' => true, 'nfse' => $nfse, 'documento_id' => $docId, 'url' => $url]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update'])]);

        // Cancelar NFS-e
        $router->post('/api/servicos/nfse/cancelar', function(Request $r){
            $body = is_array($r->body ?? null) ? $r->body : [];
            $numero = trim((string)($body['numero'] ?? ''));
            $justificativa = trim((string)($body['justificativa'] ?? ''));
            if ($numero === '' || $justificativa === '') {
                return Response::json(['error' => ['code' => 'invalid_input', 'message' => 'numero e justificativa obrigatórios']], 422);
            }

            try {
                $pdo = ConnectionManager::forTenant();
                $cfg = \Gestoreasy\Core\Fiscal\TaxConfig::get($pdo);
                $opt = is_array($cfg['options'] ?? null) ? $cfg['options'] : [];
                $policy = is_array(($opt['cancellation']['nfse'] ?? null)) ? $opt['cancellation']['nfse'] : [];
                $minLen = (int)($policy['min_reason_length'] ?? 10);
                if (mb_strlen($justificativa) < $minLen) {
                    return Response::json(['error' => ['code' => 'short_reason', 'message' => 'justificativa muito curta', 'min_chars' => $minLen]], 422);
                }

                // Atualiza service_documents conforme número
                $stmt = $pdo->prepare('UPDATE service_documents SET status = "canceled" WHERE tipo = "nfse" AND (meta_json LIKE ? OR titulo LIKE ? OR arquivo_nome LIKE ?)');
                $likeNumero = '%"numero":"' . $numero . '"%';
                $likeTitulo = '%' . $numero . '%';
                $stmt->execute([$likeNumero, $likeTitulo, $likeTitulo]);
                $count = (int)$stmt->rowCount();

                // Se possível, registrar arquivo de cancelamento em uploads
                try {
                    $doc = $pdo->query('SELECT id, order_id, storage_path FROM service_documents WHERE tipo = "nfse" ORDER BY id DESC LIMIT 1')->fetch();
                    if ($doc && is_array($doc)) {
                        $id = (int)($doc['order_id'] ?? 0);
                        $uploadDir = __DIR__ . '/../../public/uploads/services/nfse/' . ($id ?: 'misc');
                        if (!is_dir($uploadDir)) @mkdir($uploadDir, 0775, true);
                        $fileName = 'nfse_cancel_' . date('Ymd_His') . '_' . preg_replace('/[^0-9A-Za-z]/','', $numero) . '.json';
                        $filePath = $uploadDir . '/' . $fileName;
                        @file_put_contents($filePath, json_encode(['tipo' => 'NFS-e', 'numero' => $numero, 'status' => 'canceled', 'justificativa' => $justificativa], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
                    }
                } catch (\Throwable $e) { /* tolerante */ }

                return Response::json(['ok' => true, 'canceled' => $count > 0, 'numero' => $numero]);
            } catch (\Throwable $e) {
                return Response::json(['error' => ['code' => 'nfse_cancel_failed', 'message' => $e->getMessage()]], 500);
            }
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update'])]);

        // Ordens de serviço: faturar (integração estoque + contas a receber)
        $router->post('/api/servicos/ordens/{id}/faturar', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => ['code' => 'invalid_order', 'message' => 'ID inválido']], 422);
            // Buscar OS + itens
            $stmt = $pdo->prepare('SELECT so.id, so.status, so.descricao, so.valor_total_cents, so.cliente_id, v.placa, v.modelo FROM service_orders so LEFT JOIN vehicles v ON v.id = so.vehicle_id WHERE so.id = ? LIMIT 1');
            $stmt->execute([$id]);
            $order = $stmt->fetch();
            if (!$order) return Response::json(['error' => ['code' => 'order_not_found', 'message' => 'Ordem de serviço não encontrada']], 404);
            if (in_array(($order['status'] ?? ''), ['faturada','cancelada'], true)) {
                return Response::json(['error' => ['code' => 'order_closed', 'message' => 'OS já faturada/cancelada']], 409);
            }
            $items = $pdo->prepare('SELECT id, product_id, descricao, quantidade, valor_unit_cents, subtotal_cents FROM service_order_items WHERE order_id = ?');
            $items->execute([$id]);
            $rows = $items->fetchAll();
            if (!$rows || count($rows) === 0) {
                return Response::json(['error' => ['code' => 'no_items', 'message' => 'Não há itens na OS para faturamento']], 422);
            }

            // Configuração de estoque negativo
            $allowNeg = false;
            try { $cfgRow = $pdo->query('SELECT permitir_estoque_negativo FROM inventory_settings LIMIT 1')->fetch(); $allowNeg = ((int)($cfgRow['permitir_estoque_negativo'] ?? 0)) === 1; } catch (\Throwable $e) { $allowNeg = false; }

            try {
                $pdo->beginTransaction();
                // Baixar estoque para itens com product_id
                foreach ($rows as $row) {
                    $pid = (int)($row['product_id'] ?? 0);
                    $qtd = (float)($row['quantidade'] ?? 0);
                    if ($pid <= 0 || $qtd <= 0) { continue; }
                    // Verificar produto
                    $pstmt = $pdo->prepare('SELECT id, estoque FROM products WHERE id = ? LIMIT 1');
                    $pstmt->execute([$pid]);
                    $prod = $pstmt->fetch();
                    if (!$prod) { $pdo->rollBack(); return Response::json(['error' => ['code' => 'product_not_found', 'message' => 'Produto não encontrado', 'product_id' => $pid]], 404); }
                    $estoqueAtual = (float)($prod['estoque'] ?? 0);
                    $before = $estoqueAtual; $after = $before - $qtd;
                    if (!$allowNeg && $after < 0) {
                        try { \Gestoreasy\Core\Audit\AuditLogger::log('stock_policy_violation', 'service_orders', $id, null, null, ['product_id' => $pid, 'available' => $estoqueAtual, 'required' => $qtd, 'endpoint' => 'servicos.ordens.faturar']); } catch (\Throwable $e) {}
                        $pdo->rollBack();
                        return Response::json(['error' => ['code' => 'insufficient_stock', 'message' => 'Estoque insuficiente para faturar OS. Disponível: ' . $estoqueAtual . ' unidades', 'product_id' => $pid, 'available' => $estoqueAtual, 'required' => $qtd]], 422);
                    }
                    // Atualização via SP com fallback
                    $spOk = false;
                    try { $call = $pdo->prepare('CALL sp_update_product_stock(?,?,?,?,?,?)'); $call->execute([$pid, -$qtd, 'service_order', $id, (string)$id, $allowNeg ? 1 : 0]); $spOk = true; } catch (\Throwable $e) { $spOk = false; }
                    if (!$spOk) {
                        $upd = $pdo->prepare('UPDATE products SET estoque = :estoque WHERE id = :id');
                        $upd->execute([':estoque' => $after, ':id' => $pid]);
                        try { $mov = $pdo->prepare('INSERT INTO inventory_movements (product_id, tipo, quantidade, motivo, stock_before, stock_after, origin_type, origin_id, origin_number) VALUES (?,?,?,?,?,?,?,?,?)'); $mov->execute([$pid, 'saida_servico', -$qtd, 'OS faturada', $before, $after, 'service_order', $id, (string)$id]); } catch (\Throwable $e) { /* ignore */ }
                    }
                }

                // Criar conta a receber
                $valorTotal = (int)($order['valor_total_cents'] ?? 0);
                $valor = ((float)$valorTotal) / 100.0;
                // Buscar nome do cliente
                $clienteNome = 'Cliente N/A';
                $clienteId = isset($order['cliente_id']) ? (int)$order['cliente_id'] : 0;
                if ($clienteId > 0) { try { $stC = $pdo->prepare('SELECT nome FROM clients WHERE id = ?'); $stC->execute([$clienteId]); $cr = $stC->fetch(); if ($cr && ($cr['nome'] ?? '') !== '') { $clienteNome = (string)$cr['nome']; } } catch (\Throwable $e) { /* ignore */ } }
                $descricaoCr = sprintf('OS #%d - %s %s', (int)$id, (string)($order['placa'] ?? ''), (string)($order['modelo'] ?? ''));
                $venc = (string)($r->body['data_vencimento'] ?? date('Y-m-d', strtotime('+7 days')));
                // Validação simples da data
                if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $venc)) { $venc = date('Y-m-d', strtotime('+7 days')); }
                $doc = 'OS_' . (string)$id;
                $obs = (string)($r->body['observacoes'] ?? '');
                $insCr = $pdo->prepare("INSERT INTO contas_receber (descricao, valor, data_vencimento, cliente, documento, observacoes, status, created_at) VALUES (?,?,?,?,?,?, 'pendente', NOW())");
                $insCr->execute([$descricaoCr, $valor, $venc, $clienteNome, $doc, $obs !== '' ? $obs : null]);
                $crId = (int)$pdo->lastInsertId();

                // Atualizar OS para faturada
                $pdo->prepare('UPDATE service_orders SET status = ?, fechada_em = NOW() WHERE id = ?')->execute(['faturada', $id]);
                $pdo->commit();

                try { \Gestoreasy\Core\Audit\AuditLogger::log('invoice', 'service_orders', $id, $order ?: [], ['contas_receber_id' => $crId, 'valor' => $valor]); } catch (\Throwable $e) {}
                return Response::json(['ok' => true, 'id' => $id, 'contas_receber_id' => $crId]);
            } catch (\Throwable $e) {
                if ($pdo->inTransaction()) { $pdo->rollBack(); }
                return Response::json(['error' => ['code' => 'invoice_failed', 'message' => $e->getMessage()]], 500);
            }
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.update','financeiro.create'])]);

        // Checklists: templates básicos
        $router->get('/api/servicos/checklist/templates', function(Request $r){
            $templates = [
                ['tipo' => 'carro', 'nome' => 'Padrão Carro', 'itens' => ['Pneus','Freios','Luzes','Óleo','Suspensão','Direção','Bateria','Arrefecimento','Lataria','Interior','Avarias Visuais','Avarias Funcionais']],
                ['tipo' => 'moto', 'nome' => 'Padrão Moto', 'itens' => ['Pneus','Freios','Luzes','Óleo','Corrente','Suspensão','Direção','Bateria','Lataria','Avarias Visuais','Avarias Funcionais']],
                ['tipo' => 'pesado', 'nome' => 'Padrão Veículo Pesado', 'itens' => ['Pneus','Freios','Luzes','Óleo','Suspensão','Direção','Bateria','Arrefecimento','Sistema de ar','Chassi','Avarias Visuais','Avarias Funcionais']]
            ];
            return Response::json(['data' => $templates]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Checklists: salvar para um veículo
        $router->post('/api/servicos/checklists', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $vehicleId = (int)($r->body['vehicle_id'] ?? 0);
            $tipo = trim((string)($r->body['tipo'] ?? ''));
            $items = $r->body['items'] ?? [];
            $obs = trim((string)($r->body['observacoes'] ?? ''));
            if ($vehicleId <= 0 || $tipo === '') return Response::json(['error' => 'vehicle_id e tipo são obrigatórios'], 422);
            $stmt = $pdo->prepare('INSERT INTO vehicle_checklists (vehicle_id, tipo, items_json, observacoes) VALUES (?,?,?,?)');
            $stmt->execute([$vehicleId, $tipo, json_encode($items, JSON_UNESCAPED_UNICODE), $obs ?: null]);
            return Response::json(['ok' => true, 'id' => (int)$pdo->lastInsertId()]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.create'])]);

        // Checklists: histórico por veículo
        $router->get('/api/servicos/checklists/{vehicle_id}', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $params = $r->get('params', []); $vehicleId = (int)($params[0] ?? 0);
            if ($vehicleId <= 0) return Response::json(['error' => 'vehicle_id inválido'], 422);
            $stmt = $pdo->prepare('SELECT id, vehicle_id, tipo, items_json, observacoes, created_at FROM vehicle_checklists WHERE vehicle_id = ? ORDER BY id DESC LIMIT 50');
            $stmt->execute([$vehicleId]);
            $rows = $stmt->fetchAll();
            // Tentar decodificar items_json
            foreach ($rows as &$rw) {
                $items = json_decode((string)($rw['items_json'] ?? '[]'), true);
                if (is_array($items)) { $rw['items'] = $items; }
                unset($rw['items_json']);
            }
            return Response::json(['data' => $rows]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Documentos: listagem simples
        $router->get('/api/servicos/documentos', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            // Incluir status quando disponível; fallback para compatibilidade
            try {
                $stmt = $pdo->query('SELECT id, order_id, tipo, titulo, arquivo_nome, url, created_at, status FROM service_documents ORDER BY id DESC LIMIT 200');
            } catch (\Throwable $e) {
                $stmt = $pdo->query('SELECT id, order_id, tipo, titulo, arquivo_nome, url, created_at FROM service_documents ORDER BY id DESC LIMIT 200');
            }
            return Response::json(['data' => $stmt->fetchAll()]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Documentos: upload e cadastro
        $router->post('/api/servicos/documentos', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $orderId = (int)($r->body['order_id'] ?? ($r->query['order_id'] ?? 0));
            $tipo = trim((string)($r->body['tipo'] ?? ($r->query['tipo'] ?? '')));
            $titulo = trim((string)($r->body['titulo'] ?? ($r->query['titulo'] ?? '')));
            if ($orderId <= 0) return Response::json(['error' => ['code' => 'invalid_order', 'message' => 'order_id obrigatório']], 422);
            $chk = $pdo->prepare('SELECT id FROM service_orders WHERE id = ? LIMIT 1');
            $chk->execute([$orderId]);
            if (!$chk->fetch()) return Response::json(['error' => ['code' => 'order_not_found', 'message' => 'Ordem de serviço não encontrada']], 404);

            // Arquivo: aceitar campo 'arquivo' ou 'file'
            $file = $r->files['arquivo'] ?? ($r->files['file'] ?? null);
            if (!$file || ($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) return Response::json(['error' => ['code' => 'file_required', 'message' => 'Arquivo requerido']], 422);

            $size = (int)($file['size'] ?? 0);
            $name = (string)($file['name'] ?? '');
            $tmp = (string)($file['tmp_name'] ?? '');
            $ext = strtolower(pathinfo($name, PATHINFO_EXTENSION));
            $allowed = ['pdf','jpg','jpeg','png','doc','docx','xls','xlsx'];
            if ($size <= 0 || $size > 10 * 1024 * 1024) return Response::json(['error' => ['code' => 'file_too_large', 'message' => 'Arquivo excede o limite de 10MB']], 413);
            if (!in_array($ext, $allowed, true)) return Response::json(['error' => ['code' => 'invalid_file_ext', 'message' => 'Extensão não suportada']], 422);

            $uploadDir = __DIR__ . '/../../public/uploads/services/' . $orderId;
            if (!is_dir($uploadDir)) @mkdir($uploadDir, 0775, true);
            $destName = uniqid('so_'.$orderId.'_') . '.' . $ext;
            $dest = $uploadDir . '/' . $destName;
            if (!@move_uploaded_file($tmp, $dest)) return Response::json(['error' => ['code' => 'upload_failed', 'message' => 'Falha ao salvar arquivo']], 500);
            $url = '/uploads/services/' . $orderId . '/' . $destName;

            $stmt = $pdo->prepare('INSERT INTO service_documents (order_id, tipo, titulo, arquivo_nome, storage_path, url) VALUES (?,?,?,?,?,?)');
            $stmt->execute([$orderId, $tipo ?: null, $titulo ?: null, $name ?: $destName, $dest, $url]);
            return Response::json(['ok' => true, 'id' => (int)$pdo->lastInsertId(), 'url' => $url]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.create'])]);

        // Relatórios: visão geral
        $router->get('/api/servicos/relatorios/overview', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $start = trim((string)($r->query['start'] ?? ''));
            $end = trim((string)($r->query['end'] ?? ''));
            $where = '';
            $params = [];
            if ($start !== '' && $end !== '') { $where = ' WHERE aberta_em IS NOT NULL AND DATE(aberta_em) BETWEEN ? AND ?'; $params = [$start, $end]; }
            elseif ($start !== '') { $where = ' WHERE aberta_em IS NOT NULL AND DATE(aberta_em) >= ?'; $params = [$start]; }
            elseif ($end !== '') { $where = ' WHERE aberta_em IS NOT NULL AND DATE(aberta_em) <= ?'; $params = [$end]; }
            $sql = 'SELECT status, COUNT(*) AS c, SUM(COALESCE(valor_total_cents,0)) AS total FROM service_orders' . $where . ' GROUP BY status';
            $stmt = $pdo->prepare($sql); $stmt->execute($params);
            $rows = $stmt->fetchAll();
            $sum = 0; foreach ($rows as $rw) { $sum += (int)($rw['total'] ?? 0); }
            return Response::json(['data' => $rows, 'total_cents' => $sum]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Relatórios: tendência (últimos 30 dias) com previsão simples (MA de 7 dias)
        $router->get('/api/servicos/relatorios/tendencia', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $sql = 'SELECT DATE(aberta_em) AS dia, COUNT(*) AS c, SUM(COALESCE(valor_total_cents,0)) AS total FROM service_orders WHERE aberta_em IS NOT NULL AND aberta_em >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY dia ORDER BY dia ASC';
            $rows = $pdo->query($sql)->fetchAll();
            // Normaliza sequência para incluir dias faltantes
            $map = [];
            foreach ($rows as $rw) { $map[$rw['dia']] = ['dia' => $rw['dia'], 'c' => (int)$rw['c'], 'total' => (int)$rw['total']]; }
            $series = [];
            for ($i = 30; $i >= 0; $i--) {
                $d = date('Y-m-d', strtotime('-'.$i.' day'));
                $series[] = isset($map[$d]) ? $map[$d] : ['dia' => $d, 'c' => 0, 'total' => 0];
            }
            // Previsão por média móvel de 7 dias
            $window = 7; $forecastDays = 7; $forecast = [];
            $counts = array_map(fn($x)=> (int)$x['c'], $series);
            $totals = array_map(fn($x)=> (int)$x['total'], $series);
            for ($f = 1; $f <= $forecastDays; $f++) {
                $countMA = 0; $totalMA = 0; $n = 0;
                for ($w = count($counts)-$window; $w < count($counts); $w++) { if ($w >= 0) { $countMA += $counts[$w]; $totalMA += $totals[$w]; $n++; } }
                $countMA = $n ? (int)round($countMA / $n) : 0;
                $totalMA = $n ? (int)round($totalMA / $n) : 0;
                $forecast[] = ['dia' => date('Y-m-d', strtotime('+'.$f.' day')), 'c' => $countMA, 'total' => $totalMA];
                // desloca janela com valor previsto (para efeito de tendência cumulativa simples)
                $counts[] = $countMA; $totals[] = $totalMA;
            }
            return Response::json(['data' => $series, 'forecast' => $forecast]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);

        // Relatórios: vendas por técnico (quantidade de OS e valor total)
        $router->get('/api/servicos/relatorios/por_tecnico', function(Request $r){
            $pdo = ConnectionManager::forTenant();
            $start = trim((string)($r->query['start'] ?? ''));
            $end = trim((string)($r->query['end'] ?? ''));
            $where = '';
            $params = [];
            if ($start !== '' && $end !== '') { $where = ' WHERE aberta_em IS NOT NULL AND DATE(aberta_em) BETWEEN ? AND ?'; $params = [$start, $end]; }
            elseif ($start !== '') { $where = ' WHERE aberta_em IS NOT NULL AND DATE(aberta_em) >= ?'; $params = [$start]; }
            elseif ($end !== '') { $where = ' WHERE aberta_em IS NOT NULL AND DATE(aberta_em) <= ?'; $params = [$end]; }
            $sql = 'SELECT COALESCE(tecnico_responsavel, "(não informado)") AS tecnico, COUNT(*) AS c, SUM(COALESCE(valor_total_cents,0)) AS total FROM service_orders' . $where . ' GROUP BY tecnico_responsavel ORDER BY total DESC LIMIT 20';
            $stmt = $pdo->prepare($sql); $stmt->execute($params);
            $rows = $stmt->fetchAll();
            return Response::json(['data' => $rows]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['servicos.read'])]);
    }

    public function name(): string { return 'Servicos'; }
    public function version(): string { return '0.1.0'; }
    public function dependencies(): array { return []; }
}