<?php
namespace Modules\Admin;

use Gestoreasy\Core\Module\ModuleInterface;
use Gestoreasy\Core\Routing\Router;
use Gestoreasy\Core\Http\Request;
use Gestoreasy\Core\Http\Response;
use Gestoreasy\Core\Tenant\TenantAdmin;
use Gestoreasy\Core\Tenant\ConnectionManager as TenantConnectionManager;
use Gestoreasy\Core\Config\Config;
use Gestoreasy\Core\View\View;
use Gestoreasy\Core\Auth\AuthMiddleware;
use Gestoreasy\Core\Auth\Rbac;
use Gestoreasy\Core\Auth\Jwt;
use Gestoreasy\Core\Admin\AdminAuthMiddleware;
use Gestoreasy\Core\Admin\AdminJwt;
use Gestoreasy\Core\Admin\ConnectionManager as AdminDb;

class Module implements ModuleInterface {
    private function checkAdmin(Request $r): ?Response {
        // Exigir JWT administrativo em Authorization
        $auth = $r->header('authorization');
        if ($auth && str_starts_with($auth, 'Bearer ')) {
            $jwt = substr($auth, 7);
            $payload = AdminJwt::verify($jwt);
            if ($payload) return null;
        }
        return Response::json(['error' => 'Admin auth requerida (JWT)'], 403);
    }

    public function register(Router $router): void {
        // Página de login da área administrativa
        $router->get('/admin/login', function (Request $r) {
            return View::renderWithLayout('admin/login', [
                'title' => 'Admin: Login',
                'hideMenus' => true
            ]);
        });

        // Páginas HTML de recuperação de senha
        $router->get('/admin/recovery', function (Request $r) {
            return View::renderWithLayout('admin/recovery', [
                'title' => 'Admin: Recuperação',
                'hideMenus' => true
            ]);
        });
        $router->get('/admin/reset', function (Request $r) {
            $token = (string)($r->query['token'] ?? '');
            return View::renderWithLayout('admin/reset', [
                'title' => 'Admin: Reset',
                'hideMenus' => true,
                'token' => $token
            ]);
        });

        // Página HTML do painel de tenants protegida por autenticação administrativa separada
        $router->get('/admin/tenants', function (Request $r) {
            return View::renderWithLayout('admin/tenants', [
                'title' => 'Admin: Tenants'
            ]);
        }, [AdminAuthMiddleware::requireAdminHtml()]);

        // Página HTML de auditoria administrativa
        $router->get('/admin/audit', function (Request $r) {
            return View::renderWithLayout('admin/audit', [
                'title' => 'Admin: Auditoria'
            ]);
        }, [AdminAuthMiddleware::requireAdminHtml()]);
        
        // Página HTML de gestão da Landing Page
        $router->get('/admin/landing-page', function (Request $r) {
            return View::renderWithLayout('admin/landing-page', [
                'title' => 'Admin: Landing Page'
            ]);
        }, [AdminAuthMiddleware::requireAdminHtml()]);
        
        // Endpoint público para listar tenants (nome e URL de login baseada no domínio, se existir)
        $router->get('/api/public/tenants', function (Request $r) {
            try {
                $tenants = TenantAdmin::listTenants(); // lista de IDs de tenants
                // Mapear domínio e nome a partir do admin DB
                $pdo = AdminDb::db();
                $rows = $pdo->query('SELECT tenant_id, domain, name FROM tenants')->fetchAll();
                $map = [];
                foreach ($rows as $row) {
                    $map[(string)$row['tenant_id']] = [
                        'domain' => (string)($row['domain'] ?? ''),
                        'name' => (string)($row['name'] ?? ''),
                    ];
                }

                $publicTenants = [];
                foreach ($tenants as $tenantId) {
                    $info = $map[(string)$tenantId] ?? ['domain' => '', 'name' => ''];
                    $domain = trim($info['domain'] ?? '');
                    $name = $info['name'] !== '' ? $info['name'] : (string)$tenantId;
                    $loginUrl = '/' . $tenantId . '/login';
                    if ($domain !== '') {
                        $domainSanitized = preg_replace('#^https?://#', '', $domain);
                        $loginUrl = 'https://' . $domainSanitized . '/login';
                    }
                    $publicTenants[] = [
                        'id' => (string)$tenantId,
                        'nome' => $name,
                        'login_url' => $loginUrl,
                    ];
                }
                return Response::json(['data' => $publicTenants]);
            } catch (\Throwable $e) {
                return Response::json(['error' => 'Erro ao listar tenants'], 500);
            }
        });
        
        // Página pública da Landing Page
        $router->get('/', function (Request $r) {
            return View::render('landing-page/index', [
                'title' => 'GESTOR EASY - ERP SaaS Completo'
            ]);
        });
        
        $router->get('/landing', function (Request $r) {
            return View::render('landing-page/index', [
                'title' => 'GESTOR EASY - ERP SaaS Completo'
            ]);
        });

        // Página pública de detalhes de módulo
        $router->get('/modulos/{slug}', function (Request $r) {
            $params = $r->get('params', []);
            $slug = (string)($params[0] ?? '');
            $modulesMap = [
                'vendas' => ['title' => 'Gestão de Vendas', 'icon' => '🧾'],
                'compras' => ['title' => 'Gestão de Compras', 'icon' => '🛒'],
                'estoque' => ['title' => 'Controle de Estoque', 'icon' => '📦'],
                'financeiro' => ['title' => 'Financeiro', 'icon' => '💰'],
                'fiscal' => ['title' => 'Fiscal (SPED, NFe, NFCe)', 'icon' => '📑'],
                'sped' => ['title' => 'SPED', 'icon' => '🧾'],
                'fiscal2026' => ['title' => 'Fiscal 2026', 'icon' => '📄'],
                'pdv' => ['title' => 'PDV', 'icon' => '🖥️'],
                'wms' => ['title' => 'WMS', 'icon' => '🏭'],
                'bi' => ['title' => 'BI e Relatórios', 'icon' => '📊'],
                'crm-avancado' => ['title' => 'CRM Avançado', 'icon' => '🤝'],
                'clientes' => ['title' => 'Clientes', 'icon' => '👥'],
                'produtos' => ['title' => 'Produtos', 'icon' => '📦'],
                'fornecedores' => ['title' => 'Fornecedores', 'icon' => '🏭'],
                'cotacoes' => ['title' => 'Cotações', 'icon' => '🧮'],
                'pcp' => ['title' => 'PCP', 'icon' => '🏗️'],
                'lotes-series' => ['title' => 'Lotes e Séries', 'icon' => '🔢'],
                'servicos' => ['title' => 'Serviços', 'icon' => '🛠️'],
                'tecnicos' => ['title' => 'Técnicos', 'icon' => '🧰'],
                'workflows' => ['title' => 'Workflows', 'icon' => '🔄'],
                'dashboard' => ['title' => 'Dashboard', 'icon' => '📈'],
                'ecommerce' => ['title' => 'E-commerce', 'icon' => '🛍️'],
                'marketplace' => ['title' => 'Marketplace', 'icon' => '🏪'],
                'integracoes' => ['title' => 'Integrações', 'icon' => '🔌'],
                'mobile' => ['title' => 'Mobile', 'icon' => '📱'],
                'payments' => ['title' => 'Pagamentos', 'icon' => '💳'],
                'performance' => ['title' => 'Performance', 'icon' => '⚡'],
                'open-banking' => ['title' => 'Open Banking', 'icon' => '🏦'],
                'motopecas' => ['title' => 'Motopeças', 'icon' => '🏍️'],
                'autopecas' => ['title' => 'Autopeças', 'icon' => '🚗'],
                'locadora' => ['title' => 'Locadora', 'icon' => '🎬'],
                'gestor-y' => ['title' => 'Gestor-Y', 'icon' => '⚙️'],
            ];
            if (!isset($modulesMap[$slug])) {
                return new Response(404, ['error' => 'Módulo não encontrado']);
            }
            return View::render('landing-page/module', [
                'title' => 'Módulo: ' . $modulesMap[$slug]['title'],
                'module' => [
                    'slug' => $slug,
                    'title' => $modulesMap[$slug]['title'],
                    'icon' => $modulesMap[$slug]['icon'],
                ]
            ]);
        });

        // Listar tenants existentes
        $router->get('/api/admin/tenants', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $tenants = TenantAdmin::listTenants();
                return Response::json(['data' => $tenants]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });

        // Resumo consolidado dos tenants (registro admin + plano + último pagamento)
        $router->get('/api/admin/tenants/summary', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $stmt = $pdo->query('SELECT t.tenant_id, t.razao_social, t.name, t.contact_email, t.domain, t.status, t.access_enabled, t.plan_id, t.tipo, t.cpf, t.cnpj, t.ie, t.logradouro_tipo, t.logradouro, t.numero, t.complemento, t.bairro, t.cidade, t.estado, t.cep, t.created_at, p.name AS plan_name, p.modules_json, p.limits_json FROM tenants t LEFT JOIN plans p ON p.id = t.plan_id ORDER BY t.tenant_id');
                $rows = $stmt->fetchAll();
                // último pagamento por tenant
                $lastPay = $pdo->query('SELECT tenant_id, status, occurred_at, amount_cents FROM payments ORDER BY occurred_at DESC')->fetchAll();
                $mapLast = [];
                foreach ($lastPay as $lp) { if (!isset($mapLast[$lp['tenant_id']])) { $mapLast[$lp['tenant_id']] = $lp; } }
                // lista de DBs existentes
                $existing = [];
                try { $existing = TenantAdmin::listTenants(); } catch (\Throwable $e) {}
                $existingSet = array_flip($existing);
                $out = [];
                foreach ($rows as $row) {
                    $modules = [];
                    if (!empty($row['modules_json'])) { $dec = json_decode((string)$row['modules_json'], true); if (is_array($dec)) $modules = $dec; }
                    // overrides por tenant
                    $ov = $pdo->prepare('SELECT module, enabled FROM tenant_modules WHERE tenant_id = ?');
                    $ov->execute([(string)$row['tenant_id']]);
                    $overrides = [];
                    foreach ($ov->fetchAll() as $o) { $overrides[$o['module']] = (bool)$o['enabled']; }
                    $lp = $mapLast[(string)$row['tenant_id']] ?? null;
                    $out[] = [
                        'tenant_id' => (string)$row['tenant_id'],
                        'exists_in_db' => isset($existingSet[(string)$row['tenant_id']]),
                        'razao_social' => (string)($row['razao_social'] ?? ''),
                        'name' => (string)($row['name'] ?? ''),
                        'contact_email' => (string)($row['contact_email'] ?? ''),
                        'domain' => (string)($row['domain'] ?? ''),
                        'status' => (string)$row['status'],
                        'access_enabled' => (bool)$row['access_enabled'],
                        'created_at' => (string)($row['created_at'] ?? ''),
                        'tipo' => (string)($row['tipo'] ?? ''),
                        'cpf' => (string)($row['cpf'] ?? ''),
                        'cnpj' => (string)($row['cnpj'] ?? ''),
                        'ie' => (string)($row['ie'] ?? ''),
                        'logradouro_tipo' => (string)($row['logradouro_tipo'] ?? ''),
                        // campos de endereço também em nível superior para conveniência do front
                        'logradouro' => (string)($row['logradouro'] ?? ''),
                        'numero' => (string)($row['numero'] ?? ''),
                        'complemento' => (string)($row['complemento'] ?? ''),
                        'bairro' => (string)($row['bairro'] ?? ''),
                        'cidade' => (string)($row['cidade'] ?? ''),
                        'estado' => (string)($row['estado'] ?? ''),
                        'cep' => (string)($row['cep'] ?? ''),
                        'endereco' => [
                            'logradouro' => (string)($row['logradouro'] ?? ''),
                            'numero' => (string)($row['numero'] ?? ''),
                            'complemento' => (string)($row['complemento'] ?? ''),
                            'bairro' => (string)($row['bairro'] ?? ''),
                            'cidade' => (string)($row['cidade'] ?? ''),
                            'estado' => (string)($row['estado'] ?? ''),
                            'cep' => (string)($row['cep'] ?? ''),
                        ],
                        'plan' => [
                            'id' => $row['plan_id'] ? (int)$row['plan_id'] : null,
                            'name' => (string)($row['plan_name'] ?? ''),
                            'modules' => $modules,
                        ],
                        'modules_overrides' => $overrides,
                        'last_payment' => $lp ? [
                            'status' => (string)$lp['status'],
                            'occurred_at' => (string)$lp['occurred_at'],
                            'amount_cents' => (int)$lp['amount_cents'],
                        ] : null,
                    ];
                }
                return Response::json(['data' => $out]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });

        // Seed de usuário admin inicial (permite bootstrap via ADMIN_SECRET)
        $router->post('/api/admin/users/seed', function (Request $r) {
            // Único endpoint que aceita ADMIN_SECRET para bootstrap inicial
            $secretHeader = $r->header('x-admin-secret');
            $secretEnv = Config::get('ADMIN_SECRET', 'admin_secret');
            if (!$secretHeader || $secretHeader !== $secretEnv) {
                return new Response(403, ['error' => 'ADMIN_SECRET inválido para seed']);
            }
            $email = trim((string)($r->body['email'] ?? ''));
            $password = (string)($r->body['password'] ?? '');
            $name = trim((string)($r->body['name'] ?? 'Admin'));
            if ($email === '') {
                return Response::json(['error' => 'email é obrigatório'], 422);
            }
            try {
                $pdo = AdminDb::db();
                $exists = $pdo->prepare('SELECT id FROM admin_users WHERE email = ? LIMIT 1');
                $exists->execute([$email]);
                if ($exists->fetch()) {
                    return Response::json(['error' => 'Usuário já existe'], 409);
                }
                // Gerar senha forte se não fornecida
                if ($password === '') { $password = self::generateStrongPassword(16); }
                // Validar política mínima
                $policyError = self::validatePasswordPolicy($password, $email);
                if ($policyError) { return Response::json(['error' => $policyError], 422); }
                $hash = password_hash($password, PASSWORD_DEFAULT);
                $stmt = $pdo->prepare('INSERT INTO admin_users (email, password_hash, name, is_super_admin, twofa_enabled, password_changed_at) VALUES (?,?,?,?,0, ?)');
                $stmt->execute([$email, $hash, $name, 1, date('Y-m-d H:i:s')]);
                $adminId = (int)$pdo->lastInsertId();
                if ($adminId) {
                    $ph = $pdo->prepare('INSERT INTO admin_password_history (admin_user_id, password_hash) VALUES (?, ?)');
                    $ph->execute([$adminId, $hash]);
                    // Audit
                    $ip = $r->getClientIp();
                    $meta = json_encode(['email' => $email, 'generated' => (bool)($r->body['password'] ?? '') === false]);
                    $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
                    $al->execute(['create', 'admin_user', $adminId, $meta, $ip]);
                }
                return Response::json(['ok' => true, 'credentials' => ['email' => $email, 'password' => $password]], 201);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });

        // Rodar migrações para um tenant específico (ADMIN_SECRET requerido)
        $router->post('/api/admin/tenants/{tenant}/migrate', function (Request $r) {
            $secretHeader = $r->header('x-admin-secret');
            $secretEnv = Config::get('ADMIN_SECRET', 'admin_secret');
            if (!$secretHeader || $secretHeader !== $secretEnv) {
                return new Response(403, ['error' => 'ADMIN_SECRET inválido']);
            }
            $tenantId = (string)($r->get('params')[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                $result = TenantAdmin::runMigrations($tenantId);
                return Response::json(['ok' => true, 'tenant' => $tenantId, 'result' => $result]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });

        // Rodar migrações para todos os tenants existentes (ADMIN_SECRET requerido)
        $router->post('/api/admin/tenants/migrate-all', function (Request $r) {
            $secretHeader = $r->header('x-admin-secret');
            $secretEnv = Config::get('ADMIN_SECRET', 'admin_secret');
            if (!$secretHeader || $secretHeader !== $secretEnv) {
                return new Response(403, ['error' => 'ADMIN_SECRET inválido']);
            }
            try {
                $tenants = TenantAdmin::listTenants();
                $out = [];
                foreach ($tenants as $t) {
                    try {
                        $out[$t] = TenantAdmin::runMigrations($t);
                    } catch (\Throwable $e) {
                        $out[$t] = ['error' => $e->getMessage()];
                    }
                }
                return Response::json(['ok' => true, 'migrations' => $out]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });

        // Login administrativo via API (token separado)
        $router->post('/api/admin/auth/login', function (Request $r) {
            $email = trim((string)($r->body['email'] ?? ''));
            $password = (string)($r->body['password'] ?? '');
            if ($email === '' || $password === '') {
                return Response::json(['error' => 'Missing credentials'], 400);
            }
            
            // Verificar proteção contra força bruta
            $ip = $r->getClientIp();
            if (\Gestoreasy\Core\Security\BruteForceProtection::isBlocked($ip, 'admin_login')) {
                $remaining = \Gestoreasy\Core\Security\BruteForceProtection::getRemainingLockoutTime($ip, 'admin_login');
                return Response::json([
                    'error' => 'Too many failed attempts. Please try again in ' . ceil($remaining / 60) . ' minutes.'
                ], 429);
            }
            
            $pdo = AdminDb::db();
            $stmt = $pdo->prepare('SELECT * FROM admin_users WHERE email = ? LIMIT 1');
            $stmt->execute([$email]);
            $user = $stmt->fetch();
            if (!$user || !password_verify($password, $user['password_hash'])) {
                // Registrar tentativa falhada
                \Gestoreasy\Core\Security\BruteForceProtection::recordFailedAttempt($ip, 'admin_login', $email);
                
                $meta = json_encode(['email' => $email]);
                $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
                $al->execute(['login_failed', 'admin_auth', null, $meta, $ip]);
                return Response::json(['error' => 'Invalid email or password'], 401);
            }
            
            // Limpar tentativas após login bem-sucedido
            \Gestoreasy\Core\Security\BruteForceProtection::clearAttempts($ip, 'admin_login');
            // Checar rotação obrigatória (90 dias)
            $changedAt = isset($user['password_changed_at']) ? strtotime($user['password_changed_at']) : null;
            if ($changedAt && (time() - $changedAt) > (90 * 24 * 3600)) {
                return Response::json(['error' => 'Password rotation required'], 403);
            }
            $payload = [
                'sub' => (int)$user['id'],
                'email' => (string)$user['email'],
                'name' => (string)$user['name'],
                'admin' => true
            ];
            $token = AdminJwt::sign($payload);
            // Audit success
            $ip = $r->getClientIp();
            $meta = json_encode(['email' => $email]);
            $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address, admin_user_id) VALUES (?, ?, ?, ?, ?, ?)');
            $al->execute(['login_success', 'admin_auth', null, $meta, $ip, (int)$user['id']]);
            return Response::json(['token' => $token]);
        });

        // Recuperação de senha (solicitação)
        $router->post('/api/admin/auth/recovery/request', function (Request $r) {
            $email = trim((string)($r->body['email'] ?? ''));
            $finger = trim((string)($r->body['device_fingerprint'] ?? ''));
            if ($email === '') return Response::json(['error' => 'email é obrigatório'], 422);
            $pdo = AdminDb::db();
            $stmt = $pdo->prepare('SELECT id FROM admin_users WHERE email = ? LIMIT 1');
            $stmt->execute([$email]);
            $user = $stmt->fetch();
            if (!$user) return Response::json(['ok' => true]);
            $ip = $r->getClientIp();
            if (!\Gestoreasy\Core\Http\RateLimitMiddleware::check("admin_recovery_req:".$ip, 3, 3600)) {
                return Response::json(['error' => 'Too many requests'], 429);
            }
            $tokenPlain = bin2hex(random_bytes(24));
            $tokenHash = hash('sha256', $tokenPlain);
            $expires = date('Y-m-d H:i:s', time() + 15 * 60);
            $ins = $pdo->prepare('INSERT INTO admin_recovery_tokens (admin_user_id, token_hash, expires_at, ip_address, device_fingerprint) VALUES (?, ?, ?, ?, ?)');
            $ins->execute([(int)$user['id'], $tokenHash, $expires, $ip, $finger ?: null]);
            \Gestoreasy\Core\Mail\Mailer::send(
                to: $email,
                subject: 'Admin Password Recovery',
                body: "Use the link within 15 minutes: /admin/reset?token=" . urlencode($tokenPlain),
                headers: [
                    'Content-Security-Policy' => "default-src 'none'",
                    'X-Content-Type-Options' => 'nosniff',
                    'X-Frame-Options' => 'DENY'
                ]
            );
            $meta = json_encode(['email' => $email, 'finger' => $finger ? true : false]);
            $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
            $al->execute(['recovery_request', 'admin_auth', null, $meta, $ip]);
            return Response::json(['ok' => true]);
        });

        // Recuperação de senha (verificação)
        $router->post('/api/admin/auth/recovery/verify', function (Request $r) {
            $token = (string)($r->body['token'] ?? '');
            $finger = trim((string)($r->body['device_fingerprint'] ?? ''));
            if ($token === '') return Response::json(['error' => 'token é obrigatório'], 422);
            $pdo = AdminDb::db();
            $ip = $r->getClientIp();
            $hash = hash('sha256', $token);
            $stmt = $pdo->prepare('SELECT * FROM admin_recovery_tokens WHERE token_hash = ? LIMIT 1');
            $stmt->execute([$hash]);
            $row = $stmt->fetch();
            if (!$row) return Response::json(['error' => 'invalid token'], 400);
            if ($row['consumed_at']) return Response::json(['error' => 'token consumed'], 400);
            if (strtotime($row['expires_at']) < time()) return Response::json(['error' => 'token expired'], 400);
            if ($row['ip_address'] && $row['ip_address'] !== $ip) return Response::json(['error' => 'ip mismatch'], 403);
            if ($row['device_fingerprint'] && $row['device_fingerprint'] !== $finger) return Response::json(['error' => 'device mismatch'], 403);
            $pdo->prepare('UPDATE admin_recovery_tokens SET attempts_count = attempts_count + 1 WHERE id = ?')->execute([(int)$row['id']]);
            return Response::json(['ok' => true]);
        });

        // Recuperação de senha (reset)
        $router->post('/api/admin/auth/recovery/reset', function (Request $r) {
            $token = (string)($r->body['token'] ?? '');
            $newPassword = (string)($r->body['new_password'] ?? '');
            $finger = trim((string)($r->body['device_fingerprint'] ?? ''));
            if ($token === '' || $newPassword === '') return Response::json(['error' => 'token e new_password são obrigatórios'], 422);
            $pdo = AdminDb::db();
            $ip = $r->getClientIp();
            $hash = hash('sha256', $token);
            $stmt = $pdo->prepare('SELECT * FROM admin_recovery_tokens WHERE token_hash = ? LIMIT 1');
            $stmt->execute([$hash]);
            $row = $stmt->fetch();
            if (!$row) return Response::json(['error' => 'invalid token'], 400);
            if ($row['consumed_at']) return Response::json(['error' => 'token consumed'], 400);
            if (strtotime($row['expires_at']) < time()) return Response::json(['error' => 'token expired'], 400);
            if ($row['ip_address'] && $row['ip_address'] !== $ip) return Response::json(['error' => 'ip mismatch'], 403);
            if ($row['device_fingerprint'] && $row['device_fingerprint'] !== $finger) return Response::json(['error' => 'device mismatch'], 403);
            if (((int)$row['attempts_count']) >= 3) return Response::json(['error' => 'too many attempts'], 429);
            $userId = (int)$row['admin_user_id'];
            $usr = $pdo->query('SELECT email FROM admin_users WHERE id = ' . (int)$userId)->fetch();
            $policyError = self::validatePasswordPolicy($newPassword, (string)($usr['email'] ?? ''));
            if ($policyError) return Response::json(['error' => $policyError], 422);
            $hist = $pdo->prepare('SELECT password_hash FROM admin_password_history WHERE admin_user_id = ? ORDER BY changed_at DESC LIMIT 5');
            $hist->execute([$userId]);
            foreach ($hist->fetchAll() as $h) { if (password_verify($newPassword, $h['password_hash'])) { return Response::json(['error' => 'password reuse not allowed'], 422); } }
            $newHash = password_hash($newPassword, PASSWORD_DEFAULT);
            $upd = $pdo->prepare('UPDATE admin_users SET password_hash = ?, password_changed_at = ? WHERE id = ?');
            $upd->execute([$newHash, date('Y-m-d H:i:s'), $userId]);
            $ph = $pdo->prepare('INSERT INTO admin_password_history (admin_user_id, password_hash) VALUES (?, ?)');
            $ph->execute([$userId, $newHash]);
            $pdo->prepare('UPDATE admin_recovery_tokens SET consumed_at = ?, attempts_count = attempts_count + 1 WHERE id = ?')->execute([date('Y-m-d H:i:s'), (int)$row['id']]);
            $meta = json_encode(['reset' => true]);
            $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
            $al->execute(['password_reset', 'admin_auth', null, $meta, $ip]);
            return Response::json(['ok' => true]);
        });

        // API: listar logs de auditoria administrativa com filtros
        $router->get('/api/admin/audit/logs', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $pdo = AdminDb::db();
            $where = [];
            $params = [];
            // Filtros opcionais
            if (!empty($r->query['action'])) { $where[] = 'action = ?'; $params[] = (string)$r->query['action']; }
            if (!empty($r->query['ip'])) { $where[] = 'ip_address = ?'; $params[] = (string)$r->query['ip']; }
            if (!empty($r->query['admin_user_id'])) { $where[] = 'admin_user_id = ?'; $params[] = (int)$r->query['admin_user_id']; }
            if (!empty($r->query['email'])) { $where[] = 'metadata LIKE ?'; $params[] = '%' . $r->query['email'] . '%'; }
            if (!empty($r->query['start'])) { $where[] = 'created_at >= ?'; $params[] = (string)$r->query['start']; }
            if (!empty($r->query['end'])) { $where[] = 'created_at <= ?'; $params[] = (string)$r->query['end']; }
            $sql = 'SELECT id, action, resource, resource_id, metadata, ip_address, admin_user_id, created_at FROM admin_audit_logs';
            if ($where) { $sql .= ' WHERE ' . implode(' AND ', $where); }
            $sql .= ' ORDER BY id DESC LIMIT ? OFFSET ?';
            $limit = isset($r->query['limit']) ? max(1, min(100, (int)$r->query['limit'])) : 50;
            $page = isset($r->query['page']) ? max(0, (int)$r->query['page']) : 0;
            $stmt = $pdo->prepare($sql);
            foreach ($params as $i => $p) { $stmt->bindValue($i + 1, $p); }
            $stmt->bindValue(count($params) + 1, $limit, \PDO::PARAM_INT);
            $stmt->bindValue(count($params) + 2, $page * $limit, \PDO::PARAM_INT);
            $stmt->execute();
            $rows = $stmt->fetchAll();
            foreach ($rows as &$row) {
                $dec = json_decode((string)($row['metadata'] ?? ''), true);
                $row['metadata'] = $dec ?: (string)($row['metadata'] ?? '');
            }
            return Response::json(['ok' => true, 'data' => $rows, 'page' => $page, 'limit' => $limit]);
        });

        // Impersonate: entrar como um tenant sem credenciais do tenant
        $router->post('/api/admin/tenants/impersonate', function (Request $r) {
            $tenantId = trim((string)($r->body['tenant_id'] ?? ''));
            if ($tenantId === '') return Response::json(['error' => 'tenant_id required'], 422);
            // Gerar token JWT normal do sistema principal com claim de tenant
            $jwt = Jwt::sign(['tenant' => $tenantId, 'impersonated' => true]);
            // Duas cookies: auth_token (acessível ao front) e x-tenant-id
            // Mantemos alinhado ao fluxo de login padrão (sem HttpOnly) para permitir que o front defina Authorization
            $cookies = [
                'auth_token=' . urlencode($jwt) . '; Path=/; SameSite=Lax',
                'x-tenant-id=' . urlencode($tenantId) . '; Path=/'
            ];
            return new Response(302, '', ['Location' => '/clientes', 'Set-Cookie' => $cookies]);
        }, [AdminAuthMiddleware::requireAdminApi()]);

        // Criar novo tenant (DB + registro admin); gera tenant_id automaticamente se não informado
        $router->post('/api/admin/tenants', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $tenantId = trim((string)($r->body['tenant_id'] ?? ''));
            $razaoSocial = trim((string)($r->body['razao_social'] ?? ''));
            $name = trim((string)($r->body['name'] ?? ''));
            $email = trim((string)($r->body['contact_email'] ?? ''));
            $domain = trim((string)($r->body['domain'] ?? ''));
            $planId = $r->body['plan_id'] ?? null;
            $tipo = strtoupper(trim((string)($r->body['tipo'] ?? '')));
            $cpf = preg_replace('/\D+/', '', (string)($r->body['cpf'] ?? ''));
            $cnpj = preg_replace('/\D+/', '', (string)($r->body['cnpj'] ?? ''));
            $ie = preg_replace('/\D+/', '', (string)($r->body['ie'] ?? ''));
            $logradouroTipo = trim((string)($r->body['logradouro_tipo'] ?? ''));
            $logradouro = trim((string)($r->body['logradouro'] ?? ''));
            $numero = trim((string)($r->body['numero'] ?? ''));
            $complemento = trim((string)($r->body['complemento'] ?? ''));
            $bairro = trim((string)($r->body['bairro'] ?? ''));
            $cidade = trim((string)($r->body['cidade'] ?? ''));
            $estado = strtoupper(trim((string)($r->body['estado'] ?? '')));
            $cep = preg_replace('/\D+/', '', (string)($r->body['cep'] ?? ''));
            try {
                $pdoAdmin = AdminDb::db();
                // Validações básicas
                if ($razaoSocial !== '' && (strlen($razaoSocial) < 5 || strlen($razaoSocial) > 100)) {
                    return Response::json(['error' => 'Razão Social inválida (5 a 100 caracteres)'], 422);
                }
                if ($tipo !== '' && !in_array($tipo, ['PF','PJ'])) {
                    return Response::json(['error' => 'tipo inválido: use PF ou PJ'], 422);
                }
                if ($domain !== '' && !preg_match('/^[a-z0-9][a-z0-9\-\.]*\.[a-z]{2,}$/i', $domain)) {
                    return Response::json(['error' => 'domínio inválido'], 422);
                }
                if ($tipo === 'PF' && $cnpj !== '') { return Response::json(['error' => 'PF não deve informar CNPJ'], 422); }
                if ($tipo === 'PJ' && $cpf !== '') { return Response::json(['error' => 'PJ não deve informar CPF'], 422); }
                if ($cpf !== '' && !in_array(strlen($cpf), [11])) { return Response::json(['error' => 'CPF inválido'], 422); }
                if ($cnpj !== '' && !in_array(strlen($cnpj), [14])) { return Response::json(['error' => 'CNPJ inválido'], 422); }
                if ($ie !== '' && !in_array(strlen($ie), [9])) { return Response::json(['error' => 'Inscrição Estadual inválida'], 422); }
                if ($estado !== '' && !preg_match('/^[A-Z]{2}$/', $estado)) { return Response::json(['error' => 'Estado inválido (UF)'], 422); }
                if ($cep !== '' && !preg_match('/^\d{8}$/', $cep)) { return Response::json(['error' => 'CEP inválido'], 422); }
                // Validar tamanho do tenant_id se fornecido
                if ($tenantId !== '' && strlen($tenantId) > 46) { 
                    return Response::json(['error' => 'tenant_id muito longo (máximo 46 caracteres)'], 422); 
                }
                // Gerar ID se vazio
                if ($tenantId === '') {
                    $base = $name !== '' ? strtolower($name) : ('tenant-' . substr(bin2hex(random_bytes(4)), 0, 6));
                    $slug = preg_replace('/[^a-z0-9_\-]+/', '-', $base);
                    $slug = preg_replace('/-+/', '-', $slug);
                    $slug = trim($slug, '-');
                    if ($slug === '') { $slug = 'tenant-' . substr(bin2hex(random_bytes(4)), 0, 6); }
                    
                    // Limitar tamanho do slug para evitar nomes de banco muito longos
                    // MySQL tem limite de 64 caracteres para nomes de banco
                    // Com prefixo 'gestoreasy_tenant_' (18 chars), sobram 46 chars para tenant_id
                    // Reservamos 5 chars para sufixo único (-xxxx), então limitamos a 41 chars
                    if (strlen($slug) > 41) {
                        $slug = substr($slug, 0, 41);
                        $slug = rtrim($slug, '-'); // Remove traços no final
                    }
                    
                    $existingSet = array_flip((array)TenantAdmin::listTenants());
                    $try = $slug; $i = 0;
                    while (true) {
                        $chk = $pdoAdmin->prepare('SELECT id FROM tenants WHERE tenant_id = ? LIMIT 1');
                        $chk->execute([$try]);
                        $existsAdmin = (bool)$chk->fetch();
                        $existsDb = isset($existingSet[$try]);
                        if (!$existsAdmin && !$existsDb) { $tenantId = $try; break; }
                        $i++;
                        $try = $slug . '-' . substr(bin2hex(random_bytes(2)), 0, 4);
                        if ($i > 10) { return Response::json(['error' => 'Falha ao gerar tenant_id único'], 500); }
                    }
                }

                // Criar DB do tenant e semear base
                $res = TenantAdmin::createTenant($tenantId);

                // Registrar no admin DB (upsert básico)
                $chk = $pdoAdmin->prepare('SELECT id FROM tenants WHERE tenant_id = ? LIMIT 1');
                $chk->execute([$tenantId]);
                if ($row = $chk->fetch()) {
                    $upd = $pdoAdmin->prepare('UPDATE tenants SET razao_social = ?, name = ?, contact_email = ?, domain = ?, status = ?, plan_id = ?, access_enabled = access_enabled, tipo = ?, cpf = ?, cnpj = ?, ie = ?, logradouro_tipo = ?, logradouro = ?, numero = ?, complemento = ?, bairro = ?, cidade = ?, estado = ?, cep = ? WHERE tenant_id = ?');
                    $upd->execute([$razaoSocial ?: null, $name ?: null, $email ?: null, $domain ?: null, 'active', $planId ?: null, $tipo ?: null, $cpf ?: null, $cnpj ?: null, $ie ?: null, $logradouroTipo ?: null, $logradouro ?: null, $numero ?: null, $complemento ?: null, $bairro ?: null, $cidade ?: null, $estado ?: null, $cep ?: null, $tenantId]);
                } else {
                    // Inserção com 20 placeholders, incluindo access_enabled como parâmetro
                    // Se plan_id foi fornecido, buscar módulos do plano e habilitar acesso total
                    $modulesToEnable = [];
                    if ($planId) {
                        try {
                            $planStmt = $pdoAdmin->prepare('SELECT modules_json FROM plans WHERE id = ? AND active = 1 LIMIT 1');
                            $planStmt->execute([$planId]);
                            $plan = $planStmt->fetch();
                            if ($plan && !empty($plan['modules_json'])) {
                                $modulesJson = json_decode($plan['modules_json'], true);
                                if (is_array($modulesJson)) {
                                    $modulesToEnable = $modulesJson;
                                }
                            }
                        } catch (\Throwable $e) {
                            error_log("[Admin] Erro ao buscar módulos do plano: " . $e->getMessage());
                        }
                    }
                    
                    $ins = $pdoAdmin->prepare('INSERT INTO tenants (tenant_id, razao_social, name, contact_email, domain, status, plan_id, access_enabled, tipo, cpf, cnpj, ie, logradouro_tipo, logradouro, numero, complemento, bairro, cidade, estado, cep) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
                    $ins->execute([$tenantId, $razaoSocial ?: null, $name ?: null, $email ?: null, $domain ?: null, 'active', $planId ?: null, 1, $tipo ?: null, $cpf ?: null, $cnpj ?: null, $ie ?: null, $logradouroTipo ?: null, $logradouro ?: null, $numero ?: null, $complemento ?: null, $bairro ?: null, $cidade ?: null, $estado ?: null, $cep ?: null]);
                    
                    // Habilitar módulos do plano para o tenant
                    if (!empty($modulesToEnable)) {
                        try {
                            $moduleIns = $pdoAdmin->prepare('INSERT INTO tenant_modules (tenant_id, module, enabled) VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE enabled = 1');
                            foreach ($modulesToEnable as $module) {
                                $moduleIns->execute([$tenantId, $module]);
                            }
                        } catch (\Throwable $e) {
                            error_log("[Admin] Erro ao habilitar módulos do plano: " . $e->getMessage());
                        }
                    }
                }

                return Response::json(['ok' => true, 'result' => $res, 'tenant_id' => $tenantId], 201);
            } catch (\Throwable $e) {
                return Response::json(['error' => $e->getMessage()], 400);
            }
        });

        // Atualizar registro do tenant (nome, email, status, plano, acesso)
        $router->put('/api/admin/tenants/{tenant}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                $pdo = AdminDb::db();
                $fields = ['razao_social','name','contact_email','domain','status','plan_id','access_enabled',
                    'tipo','cpf','cnpj','ie','logradouro_tipo','logradouro','numero','complemento','bairro','cidade','estado','cep'
                ];
                $set = [];
                $vals = [];
                foreach ($fields as $f) {
                    if (array_key_exists($f, $r->body)) { $set[] = "$f = ?"; $vals[] = $r->body[$f]; }
                }
                if (!$set) return Response::json(['error' => 'nada para atualizar'], 400);
                $vals[] = $tenantId;
                $sql = 'UPDATE tenants SET ' . implode(', ', $set) . ' WHERE tenant_id = ?';
                $stmt = $pdo->prepare($sql);
                $stmt->execute($vals);
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });

        // Remover tenant: drop DB e registro admin
        $router->delete('/api/admin/tenants/{tenant}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                // Remover registro admin
                $pdo = AdminDb::db();
                $pdo->prepare('DELETE FROM tenants WHERE tenant_id = ?')->execute([$tenantId]);
                // Tentar dropar DB
                try {
                    $prefix = Config::get('DB_BASE_PREFIX', 'gestoreasy_tenant_');
                    $dbName = $prefix . $tenantId;
                    $server = new \PDO('mysql:host=' . Config::get('DB_HOST', '127.0.0.1') . ';charset=utf8mb4', Config::get('DB_USERNAME', 'root'), Config::get('DB_PASSWORD', ''), [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]);
                    $server->exec('DROP DATABASE IF EXISTS `' . str_replace('`','',$dbName) . '`');
                } catch (\Throwable $e) { /* opcional */ }
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });

        // Rodar migrações para um tenant
        $router->post('/api/admin/tenants/{tenant}/migrate', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                $res = TenantAdmin::runMigrations($tenantId);
                return Response::json(['ok' => true, 'result' => $res]);
            } catch (\Throwable $e) {
                return Response::json(['error' => $e->getMessage()], 500);
            }
        });

        // Rodar apenas migrações dos módulos (sem esquema base), tolerante
        $router->post('/api/admin/tenants/{tenant}/migrate-modules', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                $pdo = TenantConnectionManager::forTenant($tenantId);
                $modulesPath = __DIR__ . '/../../modules';
                $applied = [];
                if (is_dir($modulesPath)) {
                    $moduleDirs = glob($modulesPath . '/*', GLOB_ONLYDIR) ?: [];
                    // Priorizar módulos com dependências conhecidas (Produtos antes de Compras)
                    usort($moduleDirs, function($a, $b){
                        $pa = basename($a);
                        $pb = basename($b);
                        $priority = [
                            'Produtos' => 10,
                            'Compras' => 20,
                        ];
                        $wa = $priority[$pa] ?? 100;
                        $wb = $priority[$pb] ?? 100;
                        if ($wa === $wb) return strcmp($pa, $pb);
                        return $wa <=> $wb;
                    });
                    foreach ($moduleDirs as $moduleDir) {
                        $migrationsDir = $moduleDir . '/migrations';
                        if (!is_dir($migrationsDir)) continue;
                        $files = glob($migrationsDir . '/*.sql') ?: [];
                        usort($files, function ($a, $b) {
                            $wa = (function(string $filename){
                                $lower = strtolower($filename);
                                if (str_starts_with($lower, 'initial')) return 0;
                                if (preg_match('/^(\d+)/', $lower, $m)) return (int)$m[1];
                                return 10000;
                            })(basename($a));
                            $wb = (function(string $filename){
                                $lower = strtolower($filename);
                                if (str_starts_with($lower, 'initial')) return 0;
                                if (preg_match('/^(\d+)/', $lower, $m)) return (int)$m[1];
                                return 10000;
                            })(basename($b));
                            if ($wa === $wb) return strcmp($a, $b);
                            return $wa <=> $wb;
                        });
                    // Garantir engine e charset em MySQL
                    try { $pdo->exec('SET default_storage_engine=InnoDB'); } catch (\Throwable $e) {}
                    try { $pdo->exec("SET NAMES 'utf8mb4'"); } catch (\Throwable $e) {}
                    foreach ($files as $file) {
                        $sql = file_get_contents($file);
                        if ($sql && trim($sql) !== '') {
                            $parts = preg_split('/;\s*\n|;\s*$/m', $sql) ?: [];
                            foreach ($parts as $stmt) {
                                if (trim($stmt) === '') continue;
                                try { $pdo->exec($stmt); } catch (\Throwable $e) { /* tolerante */ }
                            }
                            $applied[] = basename($file);
                        }
                    }
                    }
                }
                return Response::json(['ok' => true, 'migrations' => $applied]);
            } catch (\Throwable $e) {
                return Response::json(['error' => $e->getMessage()], 500);
            }
        });

        // Bootstrap de tenant: aplicar migrações e (opcional) semear dados demo
        $router->post('/api/admin/tenants/{tenant}/bootstrap', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            $demo = isset($r->query['demo']) ? ((string)$r->query['demo'] === 'true') : true;
            try {
                $mres = TenantAdmin::runMigrations($tenantId);
                $created = ['clients' => 0, 'products' => 0];
                if ($demo) {
                    $pdo = TenantConnectionManager::forTenant($tenantId);
                    // Clientes demo
                    $clients = [
                        ['nome' => 'Cliente Demo A', 'tipo' => 'PF', 'cpf' => null, 'cnpj' => null],
                        ['nome' => 'Cliente Demo B', 'tipo' => 'PJ', 'cpf' => null, 'cnpj' => '12345678000195']
                    ];
                    foreach ($clients as $c) {
                        $check = $pdo->prepare('SELECT id FROM clients WHERE nome = ? LIMIT 1');
                        $check->execute([$c['nome']]);
                        if (!$check->fetch()) {
                            $ins = $pdo->prepare('INSERT INTO clients (nome, tipo, cpf, cnpj) VALUES (?,?,?,?)');
                            $ins->execute([$c['nome'], $c['tipo'], $c['cpf'], $c['cnpj']]);
                            $created['clients']++;
                        }
                    }
                    // Produtos demo
                    $products = [
                        ['codigo' => 'SKU-DEMO-1', 'ean' => '7891234567001', 'ncm' => '19059090', 'cest' => null, 'cfop' => '5102', 'preco' => 9.90],
                        ['codigo' => 'SKU-DEMO-2', 'ean' => '7891234567002', 'ncm' => '22021000', 'cest' => null, 'cfop' => '5102', 'preco' => 29.90],
                    ];
                    foreach ($products as $p) {
                        $check = $pdo->prepare('SELECT id FROM products WHERE codigo = ? LIMIT 1');
                        $check->execute([$p['codigo']]);
                        if (!$check->fetch()) {
                            $ins = $pdo->prepare('INSERT INTO products (codigo, ean, ncm, cest, cfop, preco) VALUES (?,?,?,?,?,?)');
                            $ins->execute([$p['codigo'], $p['ean'], $p['ncm'], $p['cest'], $p['cfop'], $p['preco']]);
                            $created['products']++;
                        }
                    }
                }
                return Response::json(['ok' => true, 'migrations' => $mres, 'created' => $created]);
            } catch (\Throwable $e) {
                return Response::json(['error' => $e->getMessage()], 500);
            }
        });

        // Pagamentos do tenant: listar
        $router->get('/api/admin/tenants/{tenant}/payments', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                $pdo = AdminDb::db();
                $stmt = $pdo->prepare('SELECT id, amount_cents, status, occurred_at, provider, external_id, notes FROM payments WHERE tenant_id = ? ORDER BY occurred_at DESC');
                $stmt->execute([$tenantId]);
                return Response::json(['ok' => true, 'data' => $stmt->fetchAll()]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });

        // Pagamentos do tenant: registrar
        $router->post('/api/admin/tenants/{tenant}/payments', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            $amount = (int)($r->body['amount_cents'] ?? 0);
            $status = (string)($r->body['status'] ?? 'paid');
            $occurred = (string)($r->body['occurred_at'] ?? date('Y-m-d H:i:s'));
            $provider = (string)($r->body['provider'] ?? 'manual');
            $external = (string)($r->body['external_id'] ?? '');
            $notes = (string)($r->body['notes'] ?? '');
            if ($amount <= 0) return Response::json(['error' => 'amount_cents inválido'], 422);
            try {
                $pdo = AdminDb::db();
                $ins = $pdo->prepare('INSERT INTO payments (tenant_id, amount_cents, status, occurred_at, provider, external_id, notes) VALUES (?,?,?,?,?,?,?)');
                $ins->execute([$tenantId, $amount, $status, $occurred, $provider, $external ?: null, $notes ?: null]);
                return Response::json(['ok' => true], 201);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });

        // Controle de acesso manual do tenant
        $router->post('/api/admin/tenants/{tenant}/access', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            $enabled = (bool)($r->body['enabled'] ?? true);
            try { $pdo = AdminDb::db(); $pdo->prepare('UPDATE tenants SET access_enabled = ? WHERE tenant_id = ?')->execute([$enabled ? 1 : 0, $tenantId]); return Response::json(['ok' => true]); }
            catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });

        // Gestão de planos
        $router->get('/api/admin/plans', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $rows = $pdo->query('SELECT id, name, price_brl, cycle, active, modules_json, limits_json, deleted_at, created_at, updated_at FROM plans ORDER BY name')->fetchAll();
                return Response::json(['data' => $rows]);
            }
            catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        $router->post('/api/admin/plans', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $name = trim((string)($r->body['name'] ?? ''));
            // aceitar tanto price quanto price_brl (preferir price_brl)
            $price = (float)($r->body['price_brl'] ?? ($r->body['price'] ?? 0));
            $cycle = (string)($r->body['cycle'] ?? 'mensal');
            $modules = $r->body['modules'] ?? null; // array
            $limits = $r->body['limits'] ?? null; // array
            if ($name === '') return Response::json(['error' => 'name requerido'], 422);
            $allowedCycles = ['mensal','trimestral','semestral','anual'];
            if (!in_array($cycle, $allowedCycles, true)) return Response::json(['error' => 'cycle inválido'], 422);
            try {
                $pdo = AdminDb::db();
                $stmt = $pdo->prepare('INSERT INTO plans (name, price_brl, cycle, active, modules_json, limits_json) VALUES (?,?,?,?,?,?)');
                $stmt->execute([$name, $price, $cycle, 1, $modules ? json_encode($modules) : null, $limits ? json_encode($limits) : null]);
                $id = (int)$pdo->lastInsertId();
                // audit
                try {
                    $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
                    $meta = json_encode(['name' => $name, 'price_brl' => $price, 'cycle' => $cycle]);
                    $al->execute(['create', 'plans', $id, $meta, (string)($r->server['REMOTE_ADDR'] ?? '')]);
                } catch (\Throwable $e2) {}
                return Response::json(['ok' => true, 'data' => ['id' => $id]], 201);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });
        $router->put('/api/admin/plans/{id}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id requerido'], 422);
            try {
                $pdo = AdminDb::db();
                // aceitar tanto 'price' (mantido por compatibilidade) quanto 'price_brl'
                $fields = ['name', 'price', 'price_brl', 'cycle', 'active'];
                $set = [];
                $vals = [];
                foreach ($fields as $f) {
                    if (!array_key_exists($f, $r->body)) continue;
                    if ($f === 'price') { $set[] = 'price_brl = ?'; $vals[] = (float)$r->body['price']; }
                    else if ($f === 'price_brl') { $set[] = 'price_brl = ?'; $vals[] = (float)$r->body['price_brl']; }
                    else if ($f === 'cycle') {
                        $allowedCycles = ['mensal','trimestral','semestral','anual'];
                        $cy = (string)$r->body['cycle'];
                        if (!in_array($cy, $allowedCycles, true)) return Response::json(['error' => 'cycle inválido'], 422);
                        $set[] = 'cycle = ?'; $vals[] = $cy;
                    } else { $set[] = "$f = ?"; $vals[] = $r->body[$f]; }
                }
                if (array_key_exists('modules', $r->body)) { $set[] = 'modules_json = ?'; $vals[] = json_encode($r->body['modules']); }
                if (array_key_exists('limits', $r->body)) { $set[] = 'limits_json = ?'; $vals[] = json_encode($r->body['limits']); }
                if (!$set) return Response::json(['error' => 'nada para atualizar'], 400);
                $vals[] = $id;
                $sql = 'UPDATE plans SET ' . implode(', ', $set) . ' WHERE id = ?';
                $stmt = $pdo->prepare($sql);
                $stmt->execute($vals);
                // audit
                try {
                    $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
                    $al->execute(['update', 'plans', $id, json_encode($r->body ?? []), (string)($r->server['REMOTE_ADDR'] ?? '')]);
                } catch (\Throwable $e2) {}
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });
        $router->delete('/api/admin/plans/{id}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id requerido'], 422);
            try {
                $pdo = AdminDb::db();
                // soft-delete: marcar deleted_at e active=0
                $pdo->prepare('UPDATE plans SET active = 0, deleted_at = CURRENT_TIMESTAMP WHERE id = ?')->execute([$id]);
                // audit
                try {
                    $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
                    $al->execute(['delete', 'plans', $id, json_encode(['reason' => 'soft_delete']), (string)($r->server['REMOTE_ADDR'] ?? '')]);
                } catch (\Throwable $e2) {}
                return Response::json(['ok' => true]);
            }
            catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });

        // Restaurar plano soft-deletado
        $router->post('/api/admin/plans/{id}/restore', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $id = (int)($params[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id requerido'], 422);
            try {
                $pdo = AdminDb::db();
                $pdo->prepare('UPDATE plans SET deleted_at = NULL, active = 1 WHERE id = ?')->execute([$id]);
                // audit
                try {
                    $al = $pdo->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
                    $al->execute(['restore', 'plans', $id, json_encode(['note' => 'restored_from_soft_delete']), (string)($r->server['REMOTE_ADDR'] ?? '')]);
                } catch (\Throwable $e2) {}
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });

        // Módulos por tenant (overrides): obter
        $router->get('/api/admin/tenants/{tenant}/modules', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                $pdo = AdminDb::db();
                $stmt = $pdo->prepare('SELECT module, enabled FROM tenant_modules WHERE tenant_id = ? ORDER BY module');
                $stmt->execute([$tenantId]);
                return Response::json(['ok' => true, 'data' => $stmt->fetchAll()]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        // Módulos por tenant (overrides): salvar
        $router->post('/api/admin/tenants/{tenant}/modules', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            $mods = $r->body['modules'] ?? [];
            if (!is_array($mods)) return Response::json(['error' => 'modules deve ser array'], 422);
            try {
                $pdo = AdminDb::db();
                $pdo->prepare('DELETE FROM tenant_modules WHERE tenant_id = ?')->execute([$tenantId]);
                foreach ($mods as $m => $enabled) {
                    $ins = $pdo->prepare('INSERT INTO tenant_modules (tenant_id, module, enabled) VALUES (?,?,?)');
                    $ins->execute([$tenantId, (string)$m, $enabled ? 1 : 0]);
                }
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 400); }
        });

        // Semear admin novamente (opcional)
        $router->post('/api/admin/tenants/{tenant}/seed-admin', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            try {
                $pdo = TenantConnectionManager::forTenant($tenantId);
                $res = TenantAdmin::seedDefaultAdmin($pdo);
                return Response::json(['ok' => true, 'result' => $res]);
            } catch (\Throwable $e) {
                return Response::json(['error' => $e->getMessage()], 500);
            }
        });

        // ==== LANDING PAGE MANAGEMENT ====
        
        // Configurações da Landing Page
        $router->get('/api/admin/landing-page/config', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $stmt = $pdo->query('SELECT * FROM landing_page_config ORDER BY categoria, chave');
                $rows = $stmt->fetchAll();
                // Agrupar por categoria
                $config = [];
                foreach ($rows as $row) {
                    $cat = $row['categoria'] ?? 'geral';
                    if (!isset($config[$cat])) $config[$cat] = [];
                    $config[$cat][$row['chave']] = [
                        'valor' => $row['valor'],
                        'tipo' => $row['tipo'],
                        'descricao' => $row['descricao']
                    ];
                }
                return Response::json(['data' => $config]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        $router->put('/api/admin/landing-page/config', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $body = $r->body;
                $stmt = $pdo->prepare('INSERT INTO landing_page_config (chave, valor, tipo, categoria, descricao) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE valor = ?, tipo = ?, categoria = ?, descricao = ?, updated_at = CURRENT_TIMESTAMP');
                foreach ($body as $chave => $dados) {
                    $valor = is_array($dados) ? ($dados['valor'] ?? '') : $dados;
                    $tipo = is_array($dados) ? ($dados['tipo'] ?? 'text') : 'text';
                    $categoria = is_array($dados) ? ($dados['categoria'] ?? 'geral') : 'geral';
                    $descricao = is_array($dados) ? ($dados['descricao'] ?? null) : null;
                    $stmt->execute([$chave, $valor, $tipo, $categoria, $descricao, $valor, $tipo, $categoria, $descricao]);
                }
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        // Planos da Landing Page
        $router->get('/api/admin/landing-page/plans', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $stmt = $pdo->query('SELECT lpp.*, p.name as plan_name FROM landing_page_plans lpp LEFT JOIN plans p ON p.id = lpp.plan_id ORDER BY lpp.ordem, lpp.nome');
                return Response::json(['data' => $stmt->fetchAll()]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        $router->post('/api/admin/landing-page/plans', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $body = $r->body;
                $stmt = $pdo->prepare('INSERT INTO landing_page_plans (plan_id, nome, descricao, preco, ciclo, destaque, ativo, features, ordem) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)');
                $stmt->execute([
                    !empty($body['plan_id']) ? (int)$body['plan_id'] : null,
                    $body['nome'] ?? '',
                    $body['descricao'] ?? null,
                    (float)($body['preco'] ?? 0),
                    $body['ciclo'] ?? 'mensal',
                    !empty($body['destaque']) ? 1 : 0,
                    !empty($body['ativo']) ? 1 : 0,
                    !empty($body['features']) ? json_encode($body['features']) : null,
                    (int)($body['ordem'] ?? 0)
                ]);
                return Response::json(['ok' => true, 'data' => ['id' => (int)$pdo->lastInsertId()]], 201);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        $router->put('/api/admin/landing-page/plans/{id}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $id = (int)($r->get('params')[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $body = $r->body;
                $fields = [];
                $params = [];
                if (isset($body['nome'])) { $fields[] = 'nome = ?'; $params[] = $body['nome']; }
                if (isset($body['descricao'])) { $fields[] = 'descricao = ?'; $params[] = $body['descricao']; }
                if (isset($body['preco'])) { $fields[] = 'preco = ?'; $params[] = (float)$body['preco']; }
                if (isset($body['ciclo'])) { $fields[] = 'ciclo = ?'; $params[] = $body['ciclo']; }
                if (isset($body['destaque'])) { $fields[] = 'destaque = ?'; $params[] = $body['destaque'] ? 1 : 0; }
                if (isset($body['ativo'])) { $fields[] = 'ativo = ?'; $params[] = $body['ativo'] ? 1 : 0; }
                if (isset($body['features'])) { $fields[] = 'features = ?'; $params[] = json_encode($body['features']); }
                if (isset($body['ordem'])) { $fields[] = 'ordem = ?'; $params[] = (int)$body['ordem']; }
                if (isset($body['plan_id'])) { $fields[] = 'plan_id = ?'; $params[] = !empty($body['plan_id']) ? (int)$body['plan_id'] : null; }
                if (empty($fields)) return Response::json(['error' => 'nada para atualizar'], 400);
                $params[] = $id;
                $pdo->prepare('UPDATE landing_page_plans SET ' . implode(', ', $fields) . ' WHERE id = ?')->execute($params);
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        $router->delete('/api/admin/landing-page/plans/{id}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $id = (int)($r->get('params')[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $pdo->prepare('DELETE FROM landing_page_plans WHERE id = ?')->execute([$id]);
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        // Contatos da Landing Page
        $router->get('/api/admin/landing-page/contacts', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $stmt = $pdo->query('SELECT * FROM landing_page_contacts ORDER BY ordem, tipo');
                return Response::json(['data' => $stmt->fetchAll()]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        $router->post('/api/admin/landing-page/contacts', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $body = $r->body;
                $stmt = $pdo->prepare('INSERT INTO landing_page_contacts (tipo, valor, label, icone, ordem, ativo) VALUES (?, ?, ?, ?, ?, ?)');
                $stmt->execute([
                    $body['tipo'] ?? 'email',
                    $body['valor'] ?? '',
                    $body['label'] ?? null,
                    $body['icone'] ?? null,
                    (int)($body['ordem'] ?? 0),
                    !empty($body['ativo']) ? 1 : 0
                ]);
                return Response::json(['ok' => true, 'data' => ['id' => (int)$pdo->lastInsertId()]], 201);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        $router->put('/api/admin/landing-page/contacts/{id}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $id = (int)($r->get('params')[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $body = $r->body;
                $fields = [];
                $params = [];
                if (isset($body['tipo'])) { $fields[] = 'tipo = ?'; $params[] = $body['tipo']; }
                if (isset($body['valor'])) { $fields[] = 'valor = ?'; $params[] = $body['valor']; }
                if (isset($body['label'])) { $fields[] = 'label = ?'; $params[] = $body['label']; }
                if (isset($body['icone'])) { $fields[] = 'icone = ?'; $params[] = $body['icone']; }
                if (isset($body['ordem'])) { $fields[] = 'ordem = ?'; $params[] = (int)$body['ordem']; }
                if (isset($body['ativo'])) { $fields[] = 'ativo = ?'; $params[] = $body['ativo'] ? 1 : 0; }
                if (empty($fields)) return Response::json(['error' => 'nada para atualizar'], 400);
                $params[] = $id;
                $pdo->prepare('UPDATE landing_page_contacts SET ' . implode(', ', $fields) . ' WHERE id = ?')->execute($params);
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        $router->delete('/api/admin/landing-page/contacts/{id}', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $id = (int)($r->get('params')[0] ?? 0);
            if ($id <= 0) return Response::json(['error' => 'id inválido'], 422);
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $pdo->prepare('DELETE FROM landing_page_contacts WHERE id = ?')->execute([$id]);
                return Response::json(['ok' => true]);
            } catch (\Throwable $e) { return Response::json(['error' => $e->getMessage()], 500); }
        });
        
        // Criar Trial (Tenant) - ENDPOINT PÚBLICO
        $router->post('/api/public/landing-page/trial', function (Request $r) {
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                $body = $r->body;
                
                // Validar dados
                $nome = trim($body['nome'] ?? '');
                $email = trim($body['email'] ?? '');
                $telefone = trim($body['telefone'] ?? '');
                $empresa = trim($body['empresa'] ?? '');
                
                if (empty($nome) || empty($email)) {
                    return Response::json(['error' => 'Nome e email são obrigatórios'], 422);
                }
                
                // Validar email
                if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
                    return Response::json(['error' => 'Email inválido'], 422);
                }
                
                // Buscar configuração do trial
                $configStmt = $pdo->prepare('SELECT valor FROM landing_page_config WHERE chave = ?');
                $configStmt->execute(['trial_plano_id']);
                $trialPlanId = $configStmt->fetchColumn();
                $trialPlanId = !empty($trialPlanId) ? (int)$trialPlanId : null;
                
                $configStmt->execute(['trial_dias']);
                $trialDias = (int)($configStmt->fetchColumn() ?? 5);
                
                // Gerar tenant_id único (limitado a 46 caracteres para MySQL)
                // Formato: trial_<hash_email>_<timestamp>
                $hashEmail = substr(md5($email), 0, 8);
                $timestamp = time();
                $tenantId = 'trial_' . $hashEmail . '_' . $timestamp;
                
                // Garantir que não excede 46 caracteres
                if (strlen($tenantId) > 46) {
                    $tenantId = 'trial_' . $hashEmail . '_' . substr((string)$timestamp, -6);
                }
                
                // Validar formato (apenas letras, números, underscore e hífen)
                if (!preg_match('/^[a-zA-Z0-9_-]+$/', $tenantId)) {
                    // Se inválido, usar apenas hash e timestamp
                    $tenantId = 'trial_' . $hashEmail . '_' . $timestamp;
                    $tenantId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $tenantId);
                }
                
                // Criar tenant usando TenantAdmin (pode falhar se já existe)
                $tentativas = 0;
                $maxTentativas = 3;
                $result = null;
                
                while ($tentativas < $maxTentativas) {
                    try {
                        $result = TenantAdmin::createTenant($tenantId);
                        break; // Sucesso
                    } catch (\Throwable $e) {
                        $tentativas++;
                        if ($tentativas >= $maxTentativas) {
                            // Última tentativa: usar apenas timestamp
                            $tenantId = 'trial_' . $timestamp . '_' . rand(1000, 9999);
                            try {
                                $result = TenantAdmin::createTenant($tenantId);
                                break;
                            } catch (\Throwable $e2) {
                                throw new \Exception("Não foi possível criar tenant após $maxTentativas tentativas: " . $e2->getMessage());
                            }
                        } else {
                            // Tentar com ID diferente
                            $tenantId = 'trial_' . $hashEmail . '_' . $timestamp . '_' . $tentativas;
                        }
                    }
                }
                
                // Registrar no admin DB
                try {
                    $tenantStmt = $pdo->prepare('INSERT INTO tenants (tenant_id, name, contact_email, status, plan_id, access_enabled, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())');
                    $tenantStmt->execute([
                        $tenantId,
                        $empresa ?: $nome,
                        $email,
                        'active',
                        $trialPlanId,
                        1
                    ]);
                } catch (\Throwable $e) {
                    // Se já existe, atualizar
                    try {
                        $updateStmt = $pdo->prepare('UPDATE tenants SET name = ?, contact_email = ?, plan_id = ?, access_enabled = 1 WHERE tenant_id = ?');
                        $updateStmt->execute([$empresa ?: $nome, $email, $trialPlanId, $tenantId]);
                    } catch (\Throwable $e2) {
                        error_log("Erro ao registrar tenant no admin: " . $e2->getMessage());
                    }
                }
                
                return Response::json([
                    'ok' => true,
                    'data' => [
                        'tenant_id' => $tenantId,
                        'message' => "Trial criado com sucesso! Acesso disponível por $trialDias dias.",
                        // Se houver domínio definido para o tenant, usar domínio; senão, fallback para rota local
                        'login_url' => (function() use ($pdo, $tenantId) {
                            try {
                                $stmt = $pdo->prepare('SELECT domain FROM tenants WHERE tenant_id = ? LIMIT 1');
                                $stmt->execute([$tenantId]);
                                $domain = (string)($stmt->fetchColumn() ?? '');
                                if ($domain !== '') {
                                    $domainSanitized = preg_replace('#^https?://#', '', $domain);
                                    return 'https://' . $domainSanitized . '/login';
                                }
                            } catch (\Throwable $e) {}
                            return '/' . $tenantId . '/login';
                        })(),
                        'email' => $email
                    ]
                ], 201);
            } catch (\Throwable $e) {
                error_log("Erro ao criar trial: " . $e->getMessage());
                return Response::json(['error' => 'Erro ao criar trial. Tente novamente mais tarde.'], 500);
            }
        });
        
        // API pública para obter dados da landing page
        $router->get('/api/public/landing-page', function (Request $r) {
            try {
                $pdo = AdminDb::db();
                $this->ensureLandingPageTables($pdo);
                
                // Configurações
                $configStmt = $pdo->query('SELECT chave, valor, tipo FROM landing_page_config');
                $configs = [];
                foreach ($configStmt->fetchAll() as $row) {
                    $configs[$row['chave']] = $row['tipo'] === 'json' ? json_decode($row['valor'], true) : $row['valor'];
                }
                
                // Planos ativos
                $plansStmt = $pdo->query('SELECT * FROM landing_page_plans WHERE ativo = 1 ORDER BY ordem, nome');
                $plans = [];
                foreach ($plansStmt->fetchAll() as $plan) {
                    $plan['features'] = !empty($plan['features']) ? json_decode($plan['features'], true) : [];
                    $plans[] = $plan;
                }
                
                // Contatos ativos
                $contactsStmt = $pdo->query('SELECT * FROM landing_page_contacts WHERE ativo = 1 ORDER BY ordem, tipo');
                $contacts = $contactsStmt->fetchAll();
                
                return Response::json([
                    'config' => $configs,
                    'plans' => $plans,
                    'contacts' => $contacts
                ]);
            } catch (\Throwable $e) {
                return Response::json(['error' => $e->getMessage()], 500);
            }
        });
        
        // Verificar se existe usuário admin e relatar detalhes de segurança
        $router->get('/api/admin/tenants/{tenant}/verify-admin', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return new Response(422, ['error' => 'tenant requerido']);
            try {
                $pdo = TenantConnectionManager::forTenant($tenantId);
                // Detectar coluna enabled, se existir
                $enabledExists = false;
                try {
                    $stmt = $pdo->query("SHOW COLUMNS FROM users LIKE 'enabled'");
                    $enabledExists = (bool)$stmt->fetch();
                } catch (\Throwable $e) {}

                // Localizar usuários com papel admin
                $sql = "SELECT u.id, u.email, u.name, u.password_hash, u.created_at, " .
                       ($enabledExists ? "u.enabled" : "NULL AS enabled") .
                       " FROM users u\n" .
                       "JOIN user_roles ur ON ur.user_id = u.id\n" .
                       "JOIN roles r ON r.id = ur.role_id\n" .
                       "WHERE r.name = 'admin'";
                $rows = $pdo->query($sql)->fetchAll();

                $exists = count($rows) > 0;
                $users = [];
                foreach ($rows as $row) {
                    // Verificar algoritmo do hash (bcrypt geralmente inicia com $2y$ ou $2a$)
                    $hash = (string)($row['password_hash'] ?? '');
                    $bcrypt = str_starts_with($hash, '$2y$') || str_starts_with($hash, '$2a$') || str_starts_with($hash, '$2b$');
                    // Contar permissões efetivas do papel admin
                    $permCount = 0;
                    try {
                        $permCount = (int)$pdo->query("SELECT COUNT(*) AS c FROM role_permissions rp JOIN roles r ON r.id = rp.role_id WHERE r.name = 'admin'")->fetch()['c'] ?? 0;
                    } catch (\Throwable $e) {}
                    $users[] = [
                        'id' => (int)$row['id'],
                        'email' => (string)$row['email'],
                        'name' => (string)$row['name'],
                        'password_hash_bcrypt' => $bcrypt,
                        'created_at' => (string)$row['created_at'],
                        'enabled' => $enabledExists ? (bool)$row['enabled'] : null,
                        'admin_permissions_count' => $permCount,
                    ];
                }

                return Response::json([
                    'ok' => true,
                    'exists' => $exists,
                    'enabled_column_present' => $enabledExists,
                    'users' => $users,
                ]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });

        // Reset de senha do usuário admin do tenant
        $router->post('/api/admin/tenants/{tenant}/reset-admin-password', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return Response::json(['error' => 'tenant requerido'], 422);
            $email = trim((string)($r->body['email'] ?? ''));
            $newPassword = trim((string)($r->body['new_password'] ?? ''));
            try {
                $pdo = TenantConnectionManager::forTenant($tenantId);
                if ($email !== '') {
                    $stmt = $pdo->prepare("SELECT u.id, u.email FROM users u JOIN user_roles ur ON ur.user_id = u.id JOIN roles r ON r.id = ur.role_id WHERE r.name = 'admin' AND u.email = ? LIMIT 1");
                    $stmt->execute([$email]);
                } else {
                    $stmt = $pdo->query("SELECT u.id, u.email FROM users u JOIN user_roles ur ON ur.user_id = u.id JOIN roles r ON r.id = ur.role_id WHERE r.name = 'admin' ORDER BY u.id ASC LIMIT 1");
                }
                $user = $stmt->fetch();
                if (!$user) return Response::json(['error' => 'usuário admin não encontrado'], 404);
                if ($newPassword === '') { $newPassword = self::generateStrongPassword(16); }
                $policyError = self::validatePasswordPolicy($newPassword, (string)($user['email'] ?? ''));
                if ($policyError) return Response::json(['error' => $policyError], 422);
                $hash = password_hash($newPassword, PASSWORD_DEFAULT);
                $upd = $pdo->prepare('UPDATE users SET password_hash = ? WHERE id = ?');
                $upd->execute([$hash, (int)$user['id']]);
                // opcional: log em auditoria administrativa
                try {
                    $adminDb = AdminDb::db();
                    $meta = json_encode(['tenant' => $tenantId, 'email' => (string)$user['email']]);
                    $al = $adminDb->prepare('INSERT INTO admin_audit_logs (action, resource, resource_id, metadata, ip_address) VALUES (?, ?, ?, ?, ?)');
                    $al->execute(['tenant_password_reset', 'tenant', null, $meta, $r->getClientIp()]);
                } catch (\Throwable $e) { /* silencioso */ }
                return Response::json(['ok' => true, 'email' => (string)$user['email'], 'new_password' => $newPassword]);
            } catch (\Throwable $e) {
                return Response::json(['error' => $e->getMessage()], 400);
            }
        });

        // Listar últimos logs de autenticação (login_success/login_failed)
        $router->get('/api/admin/tenants/{tenant}/audit-auth', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return new Response(422, ['error' => 'tenant requerido']);
            try {
                $pdo = TenantConnectionManager::forTenant($tenantId);
                $limit = isset($r->query['limit']) ? (int)$r->query['limit'] : 20;
                $limit = max(1, min(100, $limit));
                $stmt = $pdo->prepare("SELECT id, user_id, action, resource, resource_id, metadata, created_at FROM audit_logs WHERE resource = 'auth' ORDER BY created_at DESC LIMIT " . $limit);
                $stmt->execute();
                $rows = $stmt->fetchAll();
                foreach ($rows as &$row) {
                    $row['metadata'] = $row['metadata'] ? json_decode($row['metadata'], true) : null;
                }
                return Response::json(['ok' => true, 'data' => $rows]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });

        // Semear dados de exemplo (clientes e produtos) para um tenant
        $router->post('/api/admin/tenants/{tenant}/seed-demo', function (Request $r) {
            $auth = $this->checkAdmin($r);
            if ($auth instanceof Response) return $auth;
            $params = $r->get('params', []);
            $tenantId = (string)($params[0] ?? '');
            if ($tenantId === '') return new Response(422, ['error' => 'tenant requerido']);
            try {
                $pdo = TenantConnectionManager::forTenant($tenantId);
                $created = ['clients' => 0, 'products' => 0];

                // Clientes demo
                $clients = [
                    ['nome' => 'Cliente Demo A', 'tipo' => 'PF', 'cpf' => null, 'cnpj' => null],
                    ['nome' => 'Cliente Demo B', 'tipo' => 'PJ', 'cpf' => null, 'cnpj' => '12345678000195']
                ];
                foreach ($clients as $c) {
                    $check = $pdo->prepare('SELECT id FROM clients WHERE nome = ? LIMIT 1');
                    $check->execute([$c['nome']]);
                    if (!$check->fetch()) {
                        $ins = $pdo->prepare('INSERT INTO clients (nome, tipo, cpf, cnpj) VALUES (?,?,?,?)');
                        $ins->execute([$c['nome'], $c['tipo'], $c['cpf'], $c['cnpj']]);
                        $created['clients']++;
                    }
                }

                // Produtos demo
                $products = [
                    ['codigo' => 'SKU-DEMO-1', 'ean' => '7891234567001', 'ncm' => '19059090', 'cest' => null, 'cfop' => '5102', 'preco' => 9.90],
                    ['codigo' => 'SKU-DEMO-2', 'ean' => '7891234567002', 'ncm' => '22021000', 'cest' => null, 'cfop' => '5102', 'preco' => 29.90],
                ];
                foreach ($products as $p) {
                    $check = $pdo->prepare('SELECT id FROM products WHERE codigo = ? LIMIT 1');
                    $check->execute([$p['codigo']]);
                    if (!$check->fetch()) {
                        $ins = $pdo->prepare('INSERT INTO products (codigo, ean, ncm, cest, cfop, preco) VALUES (?,?,?,?,?,?)');
                        $ins->execute([$p['codigo'], $p['ean'], $p['ncm'], $p['cest'], $p['cfop'], $p['preco']]);
                        $created['products']++;
                    }
                }

                return Response::json(['ok' => true, 'created' => $created]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => $e->getMessage()]);
            }
        });
    }

    /**
     * Garante que as tabelas da landing page existem
     */
    private function ensureLandingPageTables($pdo): void {
        try {
            $driver = '';
            try { $driver = (string)$pdo->getAttribute(\PDO::ATTR_DRIVER_NAME); } catch (\Throwable $e) {}

            if ($driver === 'sqlite') {
                // Fallback: criar tabelas com sintaxe compatível com SQLite
                try { $pdo->exec('CREATE TABLE IF NOT EXISTS landing_page_config (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    chave TEXT NOT NULL UNIQUE,
                    valor TEXT NULL,
                    tipo TEXT NOT NULL DEFAULT "text",
                    categoria TEXT NULL,
                    descricao TEXT NULL,
                    created_at TEXT DEFAULT (datetime("now")),
                    updated_at TEXT DEFAULT (datetime("now"))
                )'); } catch (\Throwable $e) { error_log('SQLite: erro ao criar landing_page_config: ' . $e->getMessage()); }

                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpc_categoria ON landing_page_config (categoria)'); } catch (\Throwable $e) {}
                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpc_chave ON landing_page_config (chave)'); } catch (\Throwable $e) {}

                try { $pdo->exec('CREATE TABLE IF NOT EXISTS landing_page_plans (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    plan_id INTEGER NULL,
                    nome TEXT NOT NULL,
                    descricao TEXT NULL,
                    preco REAL NOT NULL DEFAULT 0.0,
                    ciclo TEXT NOT NULL DEFAULT "mensal",
                    destaque INTEGER NOT NULL DEFAULT 0,
                    ativo INTEGER NOT NULL DEFAULT 1,
                    features TEXT NULL,
                    ordem INTEGER NOT NULL DEFAULT 0,
                    created_at TEXT DEFAULT (datetime("now")),
                    updated_at TEXT DEFAULT (datetime("now"))
                )'); } catch (\Throwable $e) { error_log('SQLite: erro ao criar landing_page_plans: ' . $e->getMessage()); }

                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpp_plan_id ON landing_page_plans (plan_id)'); } catch (\Throwable $e) {}
                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpp_destaque ON landing_page_plans (destaque)'); } catch (\Throwable $e) {}
                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpp_ativo ON landing_page_plans (ativo)'); } catch (\Throwable $e) {}
                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpp_ordem ON landing_page_plans (ordem)'); } catch (\Throwable $e) {}

                try { $pdo->exec('CREATE TABLE IF NOT EXISTS landing_page_contacts (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    tipo TEXT NOT NULL,
                    valor TEXT NOT NULL,
                    label TEXT NULL,
                    icone TEXT NULL,
                    ordem INTEGER NOT NULL DEFAULT 0,
                    ativo INTEGER NOT NULL DEFAULT 1,
                    created_at TEXT DEFAULT (datetime("now")),
                    updated_at TEXT DEFAULT (datetime("now"))
                )'); } catch (\Throwable $e) { error_log('SQLite: erro ao criar landing_page_contacts: ' . $e->getMessage()); }

                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpc_tipo ON landing_page_contacts (tipo)'); } catch (\Throwable $e) {}
                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpc_ativo ON landing_page_contacts (ativo)'); } catch (\Throwable $e) {}
                try { $pdo->exec('CREATE INDEX IF NOT EXISTS idx_lpc_ordem ON landing_page_contacts (ordem)'); } catch (\Throwable $e) {}
            } else {
                // MySQL: usar arquivo de migration existente
                $migrationFile = __DIR__ . '/migrations/003_landing_page_config.sql';
                if (file_exists($migrationFile)) {
                    $sql = file_get_contents($migrationFile);
                    // Executar apenas CREATE TABLE (ignorar INSERTs se já existirem)
                    $statements = explode(';', $sql);
                    foreach ($statements as $stmt) {
                        $stmt = trim($stmt);
                        if (empty($stmt) || stripos($stmt, 'INSERT') === 0) continue;
                        try {
                            $pdo->exec($stmt);
                        } catch (\Throwable $e) {
                            // Tabela pode já existir
                            if (stripos($e->getMessage(), 'already exists') === false && 
                                stripos($e->getMessage(), 'Duplicate') === false) {
                                error_log("Erro ao executar migration: " . $e->getMessage());
                            }
                        }
                    }
                }
            }
        } catch (\Throwable $e) {
            error_log("Erro ao garantir tabelas da landing page: " . $e->getMessage());
        }
    }

    public function name(): string { return 'Admin'; }
    public function version(): string { return '0.1.0'; }
    public function dependencies(): array { return []; }
    private static function generateStrongPassword(int $length = 16): string {
        $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}<>?';
        $pw = '';
        for ($i = 0; $i < $length; $i++) { $pw .= $chars[random_int(0, strlen($chars) - 1)]; }
        return $pw;
    }
    private static function validatePasswordPolicy(string $password, string $email): ?string {
        if (strlen($password) < 12) return 'Password must be at least 12 characters';
        $classes = 0;
        if (preg_match('/[A-Z]/', $password)) $classes++;
        if (preg_match('/[a-z]/', $password)) $classes++;
        if (preg_match('/[0-9]/', $password)) $classes++;
        if (preg_match('/[^A-Za-z0-9]/', $password)) $classes++;
        if ($classes < 3) return 'Password must include uppercase, lowercase, numbers and special characters';
        if ($email) {
            $local = explode('@', $email)[0];
            if ($local && stripos($password, $local) !== false) return 'Password must not contain parts of the email';
        }
        return null;
    }
}