<?php
namespace Modules\Auth;

use Gestoreasy\Core\Module\ModuleInterface;
use Gestoreasy\Core\Routing\Router;
use Gestoreasy\Core\Http\Request;
use Gestoreasy\Core\Http\Response;
use Gestoreasy\Core\Tenant\ConnectionManager;
use Gestoreasy\Core\Tenant\TenantContext;
use Gestoreasy\Core\Auth\Jwt;
use Gestoreasy\Core\Config\Config;
use Gestoreasy\Core\Audit\AuditLogger;
use Gestoreasy\Core\Auth\AuthMiddleware;
use Gestoreasy\Core\Auth\Rbac;
use Gestoreasy\Core\View\View;
use Gestoreasy\Core\Auth\UserContext;

class Module implements ModuleInterface {
    public function register(Router $router): void {
        // HTML Login Page
        $router->get('/login', function (Request $r) {
            return View::renderWithLayout('login', [
                'title' => 'Login',
                'hideMenus' => true
            ]);
        });

        // Página de recuperação de senha
        $router->get('/forgot-password', function (Request $r) {
            return View::renderWithLayout('forgot_password', [
                'title' => 'Recuperar senha',
                'hideMenus' => true
            ]);
        });

        // Login rápido para ambiente local: emite JWT e grava cookie auth_token
        // Atenção: apenas disponível quando APP_ENV=local
        $router->get('/login/dev', function (Request $r) {
            $env = (string)Config::get('APP_ENV', 'local');
            if ($env !== 'local') {
                return new Response(403, ['error' => 'Dev login disponível apenas em ambiente local']);
            }
            $tenant = \Gestoreasy\Core\Tenant\TenantContext::getTenantId();
            $payload = [
                'sub' => 1,
                'email' => 'admin@local',
                'name' => 'Admin',
                'tenant' => $tenant,
                'roles' => ['admin'],
                'exp' => time() + 3600,
            ];
            $token = Jwt::sign($payload);
            $redirect = isset($r->query['redirect']) && is_string($r->query['redirect']) ? $r->query['redirect'] : '/';
            // Definir cookie para navegação HTML
            // Cookies: um HttpOnly para servidor e um acessível ao JS para Authorization header nas chamadas fetch
            $cookies = [
                'auth_token=' . $token . '; Path=/; HttpOnly; SameSite=Lax',
                'auth_token_js=' . $token . '; Path=/; SameSite=Lax',
                'x-tenant-id=' . $tenant . '; Path=/; SameSite=Lax'
            ];
            return new Response(302, '', ['Location' => $redirect, 'Set-Cookie' => $cookies]);
        });
        
        $router->post('/api/auth/login', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $email = $r->body['email'] ?? '';
            $password = $r->body['password'] ?? '';
            
            // Verificar proteção contra força bruta
            $ip = $r->getClientIp();
            if (\Gestoreasy\Core\Security\BruteForceProtection::isBlocked($ip, 'tenant_login')) {
                $remaining = \Gestoreasy\Core\Security\BruteForceProtection::getRemainingLockoutTime($ip, 'tenant_login');
                return new Response(429, ['error' => 'Muitas tentativas falhadas. Tente novamente em ' . ceil($remaining / 60) . ' minutos.']);
            }
            
            $stmt = $pdo->prepare('SELECT id, email, password_hash, name FROM users WHERE email = ? LIMIT 1');
            $stmt->execute([$email]);
            $rows = $stmt->fetchAll();
            // Garantir liberação do cursor antes de executar próxima query
            try { $stmt->closeCursor(); } catch (\Throwable $ignore) {}
            $user = $rows[0] ?? null;
            if (!$user || !password_verify($password, $user['password_hash'])) {
                // Registrar tentativa falhada
                \Gestoreasy\Core\Security\BruteForceProtection::recordFailedAttempt($ip, 'tenant_login', $email);
                
                AuditLogger::logLogin(0, false, ['email' => $email]);
                return new Response(401, ['error' => 'Credenciais inválidas']);
            }
            
            // Limpar tentativas após login bem-sucedido
            \Gestoreasy\Core\Security\BruteForceProtection::clearAttempts($ip, 'tenant_login');
            $rolesStmt = $pdo->prepare('SELECT r.name FROM roles r JOIN user_roles ur ON ur.role_id = r.id WHERE ur.user_id = ?');
            $rolesStmt->execute([$user['id']]);
            $roleRows = $rolesStmt->fetchAll();
            try { $rolesStmt->closeCursor(); } catch (\Throwable $ignore) {}
            $roles = array_map(fn($r) => $r['name'], $roleRows);

            // Checagem de horários de acesso (bypass para admin)
            $isAdminByRole = in_array('admin', $roles);
            if (!$isAdminByRole) {
                try {
                    $qs = $pdo->prepare('SELECT day_of_week, start_minute, end_minute, allow FROM user_access_schedules WHERE user_id = ?');
                    $qs->execute([(int)$user['id']]);
                    $rules = $qs->fetchAll();
                    if ($rules && count($rules) > 0) {
                        $now = time();
                        $dow = (int)date('w', $now);
                        $hm = (int)date('H', $now) * 60 + (int)date('i', $now);
                        $allowed = false;
                        foreach ($rules as $r) {
                            if ((int)$r['allow'] !== 1) continue;
                            if ((int)$r['day_of_week'] !== $dow) continue;
                            $s = (int)$r['start_minute'];
                            $e = (int)$r['end_minute'];
                            if ($hm >= $s && $hm < $e) { $allowed = true; break; }
                        }
                        if (!$allowed) {
                            AuditLogger::logLogin((int)$user['id'], false, ['email' => $email, 'reason' => 'ACCESS_SCHEDULE_BLOCKED']);
                            return new Response(403, ['error' => 'Horário de acesso restrito']);
                        }
                    }
                } catch (\Throwable $e) {
                    // Em caso de erro, não bloquear login, mas registrar
                    AuditLogger::logLogin((int)$user['id'], false, ['email' => $email, 'reason' => 'ACCESS_SCHEDULE_ERROR']);
                }
            }
            $tenant = \Gestoreasy\Core\Tenant\TenantContext::getTenantId();
            $payload = [
                'sub' => (string)$user['id'],
                'email' => $user['email'],
                'name' => $user['name'],
                'tenant' => $tenant,
                'roles' => $roles,
            ];
            $token = Jwt::sign($payload);
            $refreshTtl = (int)Config::get('JWT_REFRESH_TTL_SECONDS', 1209600);
            $refresh = Jwt::sign($payload, $refreshTtl);
            AuditLogger::logLogin((int)$user['id'], true, ['email' => $email]);
            return Response::json(['access_token' => $token, 'refresh_token' => $refresh]);
        });

        $router->post('/api/auth/refresh', function (Request $r) {
            $refresh = $r->body['refresh_token'] ?? '';
            $payload = Jwt::verify($refresh);
            if (!$payload) return new Response(401, ['error' => 'Refresh inválido']);
            $token = Jwt::sign($payload);
            return Response::json(['access_token' => $token]);
        });

        // Current authenticated user payload
        $router->get('/api/auth/me', function (Request $r) {
            $user = UserContext::get($r) ?: [];
            // Anexar permissões efetivas do usuário para controle de acesso no frontend
            try {
                $userId = (int)($user['sub'] ?? 0);
                if ($userId > 0) {
                    $pdo = ConnectionManager::forTenant();
                    $stmt = $pdo->prepare('SELECT p.name FROM permissions p JOIN role_permissions rp ON rp.permission_id = p.id JOIN user_roles ur ON ur.role_id = rp.role_id WHERE ur.user_id = ?');
                    $stmt->execute([$userId]);
                    $perms = array_map(fn($r) => $r['name'], $stmt->fetchAll());
                    $user['permissions'] = $perms;
                } else {
                    $user['permissions'] = [];
                }
            } catch (\Throwable $e) {
                $user['permissions'] = $user['permissions'] ?? [];
            }
            error_log('[AuthAPI] /api/auth/me responded for user sub=' . ($user['sub'] ?? 'null') . ' tenant=' . (\Gestoreasy\Core\Tenant\TenantContext::getTenantId() ?? 'null'));
            return Response::json(['user' => $user]);
        }, [AuthMiddleware::requireAuthCookieOrHeader()]);

        // RBAC management endpoints (admin only)
        $router->get('/api/auth/roles', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $rows = $pdo->query('SELECT id, name FROM roles ORDER BY name')->fetchAll();
            return Response::json(['data' => $rows]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->get('/api/auth/permissions', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $rows = $pdo->query('SELECT id, name FROM permissions ORDER BY name')->fetchAll();
            return Response::json(['data' => $rows]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->post('/api/auth/roles', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $name = trim((string)($r->body['name'] ?? ''));
            if ($name === '') return new Response(422, ['error' => 'name requerido']);
            $stmt = $pdo->prepare('INSERT IGNORE INTO roles (name) VALUES (?)');
            $stmt->execute([$name]);
            return Response::json(['ok' => true], 201);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        $router->post('/api/auth/permissions', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $name = trim((string)($r->body['name'] ?? ''));
            if ($name === '') return new Response(422, ['error' => 'name requerido']);
            $stmt = $pdo->prepare('INSERT IGNORE INTO permissions (name) VALUES (?)');
            $stmt->execute([$name]);
            return Response::json(['ok' => true], 201);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Registro seguro de superadministrador (persistente, hash de segredo)
        $router->post('/api/auth/superadmin/register', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $email = trim((string)($r->body['email'] ?? ''));
            $secret = (string)($r->body['secret'] ?? '');
            if ($email === '') return new Response(422, ['error' => 'email requerido']);
            // Se nenhum segredo for informado, gera um aleatório seguro
            if ($secret === '') { $secret = bin2hex(random_bytes(16)); }
            $hash = password_hash($secret, PASSWORD_BCRYPT);
            $stmt = $pdo->prepare('INSERT INTO superadmins (email, secret_hash, created_at, updated_at) VALUES (?, ?, NOW(), NOW()) ON DUPLICATE KEY UPDATE secret_hash = VALUES(secret_hash), updated_at = NOW()');
            $stmt->execute([$email, $hash]);
            // Não retornamos o segredo, apenas indicamos sucesso
            return Response::json(['ok' => true, 'email' => $email]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Status do superadministrador (para validação de configuração)
        $router->get('/api/auth/superadmin/status', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $email = isset($r->query['email']) ? trim((string)$r->query['email']) : '';
            if ($email === '') return new Response(422, ['error' => 'email requerido']);
            $stmt = $pdo->prepare('SELECT email, created_at, updated_at FROM superadmins WHERE email = ? LIMIT 1');
            $stmt->execute([$email]);
            $row = $stmt->fetch();
            if (!$row) return new Response(404, ['error' => 'superadmin não registrado']);
            return Response::json(['data' => $row]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        $router->post('/api/auth/role-permissions', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $role = trim((string)($r->body['role'] ?? ''));
            $perm = trim((string)($r->body['permission'] ?? ''));
            if ($role === '' || $perm === '') return new Response(422, ['error' => 'role e permission requeridos']);
            $stmt = $pdo->prepare("SELECT id FROM roles WHERE name = ? LIMIT 1");
            $stmt->execute([$role]);
            $roleId = $stmt->fetch()['id'] ?? null;
            $stmt = $pdo->prepare("SELECT id FROM permissions WHERE name = ? LIMIT 1");
            $stmt->execute([$perm]);
            $permId = $stmt->fetch()['id'] ?? null;
            if (!$roleId || !$permId) return new Response(404, ['error' => 'role/permission não encontrados']);
            $stmt = $pdo->prepare('INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?, ?)');
            $stmt->execute([$roleId, $permId]);
            return Response::json(['ok' => true], 201);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Listar permissões de um papel
        $router->get('/api/auth/role-permissions', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $role = isset($r->query['role']) ? trim((string)$r->query['role']) : '';
            if ($role === '') return new Response(422, ['error' => 'role requerido']);
            $stmt = $pdo->prepare("SELECT id FROM roles WHERE name = ? LIMIT 1");
            $stmt->execute([$role]);
            $roleId = $stmt->fetch()['id'] ?? null;
            if (!$roleId) return new Response(404, ['error' => 'role não encontrado']);
            $rows = $pdo->prepare('SELECT p.name FROM role_permissions rp JOIN permissions p ON p.id = rp.permission_id WHERE rp.role_id = ? ORDER BY p.name');
            $rows->execute([(int)$roleId]);
            return Response::json(['data' => array_map(fn($r) => $r['name'], $rows->fetchAll())]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Atualizar conjunto de permissões de um papel (add/remove)
        $router->put('/api/auth/role-permissions', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $role = trim((string)($r->body['role'] ?? ''));
            $perms = is_array($r->body['permissions'] ?? null) ? $r->body['permissions'] : null;
            if ($role === '' || $perms === null) return new Response(422, ['error' => 'role e permissions requeridos']);
            $stmt = $pdo->prepare("SELECT id FROM roles WHERE name = ? LIMIT 1");
            $stmt->execute([$role]);
            $roleId = $stmt->fetch()['id'] ?? null;
            if (!$roleId) return new Response(404, ['error' => 'role não encontrado']);
            // Mapear ids das permissões informadas
            $placeholders = implode(',', array_fill(0, max(count($perms),1), '?'));
            $stmtAll = $pdo->prepare('SELECT id, name FROM permissions');
            $stmtAll->execute();
            $permMap = [];
            foreach ($stmtAll->fetchAll() as $row) { $permMap[$row['name']] = (int)$row['id']; }
            $desiredIds = array_values(array_filter(array_map(fn($p)=>$permMap[$p]??null, $perms), fn($v)=>$v!==null));
            // Buscar atuais
            $curStmt = $pdo->prepare('SELECT permission_id FROM role_permissions WHERE role_id = ?');
            $curStmt->execute([(int)$roleId]);
            $currentIds = array_map(fn($r)=> (int)$r['permission_id'], $curStmt->fetchAll());
            $toAdd = array_values(array_diff($desiredIds, $currentIds));
            $toDel = array_values(array_diff($currentIds, $desiredIds));
            if ($toAdd) {
                $ins = $pdo->prepare('INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?, ?)');
                foreach ($toAdd as $pid) { $ins->execute([(int)$roleId, (int)$pid]); }
            }
            if ($toDel) {
                $del = $pdo->prepare('DELETE FROM role_permissions WHERE role_id = ? AND permission_id = ?');
                foreach ($toDel as $pid) { $del->execute([(int)$roleId, (int)$pid]); }
            }
            return Response::json(['ok' => true, 'added' => count($toAdd), 'removed' => count($toDel)]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        $router->post('/api/auth/user-roles', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $userId = (int)($r->body['user_id'] ?? 0);
            $role = trim((string)($r->body['role'] ?? ''));
            if ($userId <= 0 || $role === '') return new Response(422, ['error' => 'user_id e role requeridos']);
            $stmt = $pdo->prepare("SELECT id FROM roles WHERE name = ? LIMIT 1");
            $stmt->execute([$role]);
            $roleId = $stmt->fetch()['id'] ?? null;
            if (!$roleId) return new Response(404, ['error' => 'role não encontrado']);
            $stmt = $pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)');
            $stmt->execute([$userId, $roleId]);
            return Response::json(['ok' => true], 201);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Listar roles atribuídos a um usuário
        $router->get('/api/auth/user-roles', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $userId = isset($r->query['user_id']) ? (int)$r->query['user_id'] : 0;
            if ($userId <= 0) return new Response(422, ['error' => 'user_id requerido']);
            $stmt = $pdo->prepare('SELECT r.name FROM user_roles ur JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = ? ORDER BY r.name');
            $stmt->execute([$userId]);
            return Response::json(['data' => array_map(fn($r)=>$r['name'], $stmt->fetchAll())]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Atualizar conjunto de roles de um usuário (add/remove)
        $router->put('/api/auth/user-roles', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $userId = (int)($r->body['user_id'] ?? 0);
            $roles = is_array($r->body['roles'] ?? null) ? $r->body['roles'] : null;
            if ($userId <= 0 || $roles === null) return new Response(422, ['error' => 'user_id e roles requeridos']);
            $rows = $pdo->query('SELECT id, name FROM roles')->fetchAll();
            $map = []; foreach ($rows as $row) { $map[$row['name']] = (int)$row['id']; }
            $desiredIds = array_values(array_filter(array_map(fn($nm)=>$map[$nm]??null, $roles), fn($v)=>$v!==null));
            $curStmt = $pdo->prepare('SELECT role_id FROM user_roles WHERE user_id = ?');
            $curStmt->execute([$userId]);
            $currentIds = array_map(fn($r)=> (int)$r['role_id'], $curStmt->fetchAll());
            $toAdd = array_values(array_diff($desiredIds, $currentIds));
            $toDel = array_values(array_diff($currentIds, $desiredIds));
            if ($toAdd) { $ins = $pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)'); foreach ($toAdd as $rid) { $ins->execute([$userId, (int)$rid]); } }
            if ($toDel) { $del = $pdo->prepare('DELETE FROM user_roles WHERE user_id = ? AND role_id = ?'); foreach ($toDel as $rid) { $del->execute([$userId, (int)$rid]); } }
            return Response::json(['ok' => true, 'added' => count($toAdd), 'removed' => count($toDel)]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // CRUD de usuários (admin/rbac.manage)
        $router->get('/api/auth/users', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $q = trim((string)($r->query['q'] ?? ''));
            $only = (string)($r->query['status'] ?? ''); // 'ativos' | 'inativos' | ''
            $sort = trim((string)($r->query['sort'] ?? 'id'));
            $dir = strtoupper(trim((string)($r->query['dir'] ?? 'DESC')));
            $page = max(1, (int)($r->query['page'] ?? 1));
            $limit = max(1, min(200, (int)($r->query['limit'] ?? 20)));
            $offset = ($page - 1) * $limit;
            $where = [];
            $params = [];
            // Detectar coluna enabled
            $enabledExists = false;
            try { $col = $pdo->query("SHOW COLUMNS FROM users LIKE 'enabled'"); $enabledExists = (bool)$col->fetch(); } catch (\Throwable $e) {}
            if ($q !== '') { $where[] = '(name LIKE ? OR email LIKE ?)'; $params[] = "%$q%"; $params[] = "%$q%"; }
            if ($enabledExists) {
                if ($only === 'ativos') { $where[] = 'enabled = 1'; }
                if ($only === 'inativos') { $where[] = 'enabled = 0'; }
            }
            $sqlWhere = $where ? ('WHERE ' . implode(' AND ', $where)) : '';
            $sel = $enabledExists ? 'enabled' : 'NULL AS enabled';
            // Ordenação segura
            $sortMap = [ 'id' => 'u.id', 'name' => 'u.name', 'email' => 'u.email', 'created_at' => 'u.created_at' ];
            if ($enabledExists) { $sortMap['enabled'] = 'u.enabled'; }
            $sortCol = $sortMap[$sort] ?? 'u.id';
            $dirSql = ($dir === 'ASC') ? 'ASC' : 'DESC';
            // Total
            $stmtTotal = $pdo->prepare("SELECT COUNT(1) AS c FROM users u $sqlWhere");
            $stmtTotal->execute($params);
            $total = (int)($stmtTotal->fetch()['c'] ?? 0);
            // Incluir papéis agregados por usuário, preservando filtros com paginação
            $stmt = $pdo->prepare("SELECT u.id, u.name, u.email, $sel, u.created_at,
                (SELECT GROUP_CONCAT(r.name ORDER BY r.name SEPARATOR ',')
                 FROM user_roles ur JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = u.id) AS roles
                FROM users u $sqlWhere ORDER BY $sortCol $dirSql LIMIT :limit OFFSET :offset");
            foreach ($params as $i => $v) { $stmt->bindValue($i+1, $v); }
            $stmt->bindValue(':limit', $limit, \PDO::PARAM_INT);
            $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
            $stmt->execute();
            return Response::json(['data' => $stmt->fetchAll(), 'page' => $page, 'limit' => $limit, 'total' => $total]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->get('/api/auth/users/{id}', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $id = (int)($r->get('params', [])[0] ?? 0);
            if ($id <= 0) return new Response(422, ['error' => 'ID inválido']);
            $enabledExists = false;
            try { $col = $pdo->query("SHOW COLUMNS FROM users LIKE 'enabled'"); $enabledExists = (bool)$col->fetch(); } catch (\Throwable $e) {}
            $sel = $enabledExists ? 'enabled' : 'NULL AS enabled';
            $stmt = $pdo->prepare("SELECT id, name, email, $sel, created_at FROM users WHERE id = ?");
            $stmt->execute([$id]);
            $row = $stmt->fetch();
            if (!$row) return new Response(404, ['error' => 'Usuário não encontrado']);
            // Roles atuais
            $roles = $pdo->prepare('SELECT r.name FROM user_roles ur JOIN roles r ON r.id = ur.role_id WHERE ur.user_id = ? ORDER BY r.name');
            $roles->execute([$id]);
            $row['roles'] = array_map(fn($r)=>$r['name'], $roles->fetchAll());
            return Response::json(['data' => $row]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->post('/api/auth/users', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $name = trim((string)($r->body['name'] ?? ''));
            $email = trim((string)($r->body['email'] ?? ''));
            $password = (string)($r->body['password'] ?? '');
            $enabled = isset($r->body['enabled']) ? (int)!!$r->body['enabled'] : 1;
            $roles = is_array($r->body['roles'] ?? null) ? $r->body['roles'] : [];
            if ($name === '' || $email === '' || $password === '') return new Response(422, ['error' => 'name, email e password são obrigatórios']);
            // Requer pelo menos um papel
            if (!$roles || count(array_filter($roles, fn($r)=>trim((string)$r) !== '')) === 0) {
                return new Response(422, ['error' => 'roles são obrigatórios']);
            }
            // Verificar e-mail único
            $exists = $pdo->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
            $exists->execute([$email]);
            if ($exists->fetch()) return new Response(409, ['error' => 'E-mail já cadastrado']);
            // Mapear roles válidos
            $rows = $pdo->query('SELECT id, name FROM roles')->fetchAll();
            $map = []; foreach ($rows as $row) { $map[$row['name']] = (int)$row['id']; }
            $validRoleIds = array_values(array_filter(array_map(fn($nm)=> $map[$nm] ?? null, $roles), fn($v)=> $v !== null));
            if (count($validRoleIds) === 0) {
                return new Response(422, ['error' => 'roles inválidos']);
            }
            // Criar usuário e atribuir roles
            $hash = password_hash($password, PASSWORD_DEFAULT);
            // Detectar coluna enabled
            $enabledExists = false;
            try {
                $col = $pdo->query("SHOW COLUMNS FROM users LIKE 'enabled'");
                $enabledExists = (bool)$col->fetch();
            } catch (\Throwable $e) {}
            if ($enabledExists) {
                $stmt = $pdo->prepare('INSERT INTO users (name, email, password_hash, enabled, created_at) VALUES (?, ?, ?, ?, NOW())');
                $stmt->execute([$name, $email, $hash, $enabled]);
            } else {
                $stmt = $pdo->prepare('INSERT INTO users (name, email, password_hash, created_at) VALUES (?, ?, ?, NOW())');
                $stmt->execute([$name, $email, $hash]);
            }
            $userId = (int)$pdo->lastInsertId();
            $ins = $pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)');
            foreach ($validRoleIds as $rid) { $ins->execute([$userId, (int)$rid]); }
            return Response::json(['ok' => true, 'id' => $userId], 201);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->put('/api/auth/users/{id}', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $id = (int)($r->get('params', [])[0] ?? 0);
            if ($id <= 0) return new Response(422, ['error' => 'ID inválido']);
            $name = isset($r->body['name']) ? trim((string)$r->body['name']) : null;
            $email = isset($r->body['email']) ? trim((string)$r->body['email']) : null;
            $password = isset($r->body['password']) ? (string)$r->body['password'] : null;
            $enabled = isset($r->body['enabled']) ? (int)!!$r->body['enabled'] : null;
            $roles = is_array($r->body['roles'] ?? null) ? $r->body['roles'] : null;
            $sets = [];
            $params = [];
            if ($name !== null) { $sets[] = 'name = ?'; $params[] = $name; }
            if ($email !== null) { 
                // Checar duplicidade de email
                $exists = $pdo->prepare('SELECT id FROM users WHERE email = ? AND id <> ? LIMIT 1');
                $exists->execute([$email, $id]);
                if ($exists->fetch()) return new Response(409, ['error' => 'E-mail já cadastrado por outro usuário']);
                $sets[] = 'email = ?'; $params[] = $email; 
            }
            if ($password !== null) { $sets[] = 'password_hash = ?'; $params[] = password_hash($password, PASSWORD_DEFAULT); }
            if ($enabled !== null) { $sets[] = 'enabled = ?'; $params[] = $enabled; }
            if ($sets) {
                $params[] = $id;
                $stmt = $pdo->prepare('UPDATE users SET ' . implode(', ', $sets) . ' WHERE id = ?');
                $stmt->execute($params);
            }
            // Atualizar roles, se fornecidos
            if ($roles !== null) {
                $rows = $pdo->query('SELECT id, name FROM roles')->fetchAll();
                $map = []; foreach ($rows as $row) { $map[$row['name']] = (int)$row['id']; }
                $desiredIds = array_values(array_filter(array_map(fn($nm)=>$map[$nm]??null, $roles), fn($v)=>$v!==null));
                $curStmt = $pdo->prepare('SELECT role_id FROM user_roles WHERE user_id = ?');
                $curStmt->execute([$id]);
                $currentIds = array_map(fn($r)=> (int)$r['role_id'], $curStmt->fetchAll());
                $toAdd = array_values(array_diff($desiredIds, $currentIds));
                $toDel = array_values(array_diff($currentIds, $desiredIds));
                if ($toAdd) { $ins = $pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)'); foreach ($toAdd as $rid) { $ins->execute([$id, (int)$rid]); } }
                if ($toDel) { $del = $pdo->prepare('DELETE FROM user_roles WHERE user_id = ? AND role_id = ?'); foreach ($toDel as $rid) { $del->execute([$id, (int)$rid]); } }
            }
            return Response::json(['ok' => true]);
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->delete('/api/auth/users/{id}', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $id = (int)($r->get('params', [])[0] ?? 0);
            if ($id <= 0) return new Response(422, ['error' => 'ID inválido']);
            try {
                $pdo->beginTransaction();
                // Remover roles associados
                $delRoles = $pdo->prepare('DELETE FROM user_roles WHERE user_id = ?');
                $delRoles->execute([$id]);
                // Remover usuário
                $delUser = $pdo->prepare('DELETE FROM users WHERE id = ?');
                $delUser->execute([$id]);
                $pdo->commit();
            } catch (\Throwable $e) {
                if ($pdo->inTransaction()) $pdo->rollBack();
                return new Response(409, ['error' => 'Não foi possível excluir usuário']);
            }
            return new Response(204, '');
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        if ((Config::get('APP_ENV', 'local') === 'local')) {
            $router->post('/api/auth/seed-admin', function (Request $r) {
                $pdo = ConnectionManager::forTenant();
                $email = $r->body['email'] ?? 'admin@local';
                $name = $r->body['name'] ?? 'Admin';
                $pass = $r->body['password'] ?? 'admin123';
                $hash = password_hash($pass, PASSWORD_BCRYPT);
                // Criar usuário admin se não existir
                $existing = $pdo->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
                $existing->execute([$email]);
                $row = $existing->fetch();
                if ($row && isset($row['id'])) {
                    $userId = (int)$row['id'];
                } else {
                    $pdo->prepare('INSERT INTO users (email, name, password_hash) VALUES (?,?,?)')->execute([$email, $name, $hash]);
                    $userId = (int)$pdo->lastInsertId();
                }
                $pdo->exec("INSERT IGNORE INTO roles (name) VALUES ('admin')");
                $roleId = (int)$pdo->query("SELECT id FROM roles WHERE name = 'admin' LIMIT 1")->fetch()['id'];
                $stmt = $pdo->prepare('INSERT IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)');
                $stmt->execute([$userId, $roleId]);

                // Seed baseline permissions and attach to admin
                $perms = [
                    'clientes.read','clientes.create','clientes.update','clientes.delete',
                    'produtos.read','produtos.create','produtos.update','produtos.delete',
                    'compras.read','compras.create','compras.update','compras.delete',
                    'cotacoes.read','cotacoes.create','cotacoes.update','cotacoes.delete',
                    'estoque.read','estoque.update',
                    'wms.read','wms.create','wms.update','wms.delete',
                    'vendas.read','vendas.create','vendas.update','vendas.delete',
                    'pdv.read',
                    // Acesso a módulos e configurações
                    'modules.access','settings.access',
                    // Cadastros: permissões por entidade
                    'cadastros.fornecedores.read','cadastros.fornecedores.create','cadastros.fornecedores.update','cadastros.fornecedores.delete',
                    'cadastros.empresas.read','cadastros.empresas.create','cadastros.empresas.update','cadastros.empresas.delete',
                    'cadastros.transportadoras.read','cadastros.transportadoras.create','cadastros.transportadoras.update','cadastros.transportadoras.delete',
                    'cadastros.usuarios.read','cadastros.usuarios.create','cadastros.usuarios.update','cadastros.usuarios.delete',
                    'cadastros.vendedores.read','cadastros.vendedores.create','cadastros.vendedores.update','cadastros.vendedores.delete',
                    'cadastros.tecnicos.read','cadastros.tecnicos.create','cadastros.tecnicos.update','cadastros.tecnicos.delete',
                    'rbac.manage'
                ];
                $pi = $pdo->prepare('INSERT IGNORE INTO permissions (name) VALUES (?)');
                foreach ($perms as $p) { $pi->execute([$p]); }
                $rp = $pdo->prepare('INSERT IGNORE INTO role_permissions (role_id, permission_id) VALUES (?, ?)');
                $rows = $pdo->query('SELECT id, name FROM permissions')->fetchAll();
                $map = [];
                foreach ($rows as $row) { $map[$row['name']] = (int)$row['id']; }
                foreach ($perms as $p) { if (isset($map[$p])) { $rp->execute([$roleId, $map[$p]]); } }
                return Response::json(['ok' => true]);
            });

            // Semear papéis padrão esperados pela UI de RBAC
            $router->post('/api/auth/seed-default-roles', function (Request $r) {
                $pdo = ConnectionManager::forTenant();
                $defaults = ['ADMIN','VENDEDOR','GERENTE','SUPORTE TI','SUPERVISOR'];
                $ins = $pdo->prepare('INSERT IGNORE INTO roles (name) VALUES (?)');
                foreach ($defaults as $name) { $ins->execute([$name]); }
                // Garantir o papel clássico 'admin' também
                $pdo->exec("INSERT IGNORE INTO roles (name) VALUES ('admin')");
                $rows = $pdo->query('SELECT id, name FROM roles ORDER BY name')->fetchAll();
                return Response::json(['ok' => true, 'count' => count($rows), 'data' => $rows]);
            });
        }

        // Audit trail (admin only)
        $router->get('/api/audit/{resource}', function (Request $r) {
            $params = $r->get('params', []);
            $resource = (string)($params[0] ?? '');
            $resourceId = isset($r->query['id']) ? (int)$r->query['id'] : null;
            $limit = isset($r->query['limit']) ? (int)$r->query['limit'] : 50;
            $logs = AuditLogger::getAuditTrail($resource, $resourceId, $limit);
            return Response::json(['data' => $logs]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Prune audit logs (admin only)
        $router->post('/api/audit/prune', function (Request $r) {
            $days = isset($r->body['days']) ? (int)$r->body['days'] : (isset($r->query['days']) ? (int)$r->query['days'] : 90);
            $deleted = AuditLogger::pruneOlderThan(max(1, $days));
            return Response::json(['ok' => true, 'deleted' => $deleted]);
        }, [AuthMiddleware::requireAuth(), Rbac::requireRoles(['admin'])]);

        // Horários de acesso por usuário (admin/rbac.manage)
        $router->get('/api/auth/access-schedules', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $userId = isset($r->query['user_id']) ? (int)$r->query['user_id'] : 0;
            if ($userId <= 0) return new Response(422, ['error' => 'user_id requerido']);
            try {
                $stmt = $pdo->prepare('SELECT id, day_of_week, start_minute, end_minute, allow FROM user_access_schedules WHERE user_id = ? ORDER BY day_of_week, start_minute');
                $stmt->execute([$userId]);
                $rows = $stmt->fetchAll();
                // Converter para formato amigável
                $toTime = function($m){ $h = (int)floor($m/60); $mm = $m % 60; return sprintf('%02d:%02d', $h, $mm); };
                $data = array_map(function($r) use ($toTime){
                    return [
                        'id' => (int)$r['id'],
                        'day' => (int)$r['day_of_week'],
                        'start' => $toTime((int)$r['start_minute']),
                        'end' => $toTime((int)$r['end_minute']),
                        'allow' => (int)$r['allow'] === 1
                    ];
                }, $rows);
                return Response::json(['data' => $data]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => 'Falha ao consultar horários']);
            }
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->put('/api/auth/access-schedules', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $userId = (int)($r->body['user_id'] ?? 0);
            $rules = is_array($r->body['rules'] ?? null) ? $r->body['rules'] : null;
            if ($userId <= 0 || $rules === null) return new Response(422, ['error' => 'user_id e rules requeridos']);
            // Validar usuário existente
            $chk = $pdo->prepare('SELECT id FROM users WHERE id = ? LIMIT 1');
            $chk->execute([$userId]);
            if (!$chk->fetch()) return new Response(404, ['error' => 'Usuário não encontrado']);
            $parseTime = function($s){
                // Aceita HH:MM
                if (!is_string($s)) return null;
                $parts = explode(':', $s);
                if (count($parts) < 2) return null;
                $h = (int)$parts[0]; $m = (int)$parts[1];
                if ($h < 0 || $h > 23 || $m < 0 || $m > 59) return null;
                return $h*60 + $m;
            };
            try {
                $pdo->beginTransaction();
                // Substituir conjunto completo do usuário
                $del = $pdo->prepare('DELETE FROM user_access_schedules WHERE user_id = ?');
                $del->execute([$userId]);
                $ins = $pdo->prepare('INSERT INTO user_access_schedules (user_id, day_of_week, start_minute, end_minute, allow, created_at, updated_at) VALUES (?, ?, ?, ?, ?, NOW(), NOW())');
                $count = 0;
                foreach ($rules as $rule) {
                    $day = (int)($rule['day'] ?? -1);
                    $start = $parseTime($rule['start'] ?? '');
                    $end = $parseTime($rule['end'] ?? '');
                    $allow = isset($rule['allow']) ? (int)!!$rule['allow'] : 1;
                    if ($day < 0 || $day > 6 || $start === null || $end === null) continue;
                    if ($start < 0 || $end <= 0 || $start >= 1440 || $end > 1440) continue;
                    if ($end <= $start) continue; // intervalo inválido
                    $ins->execute([$userId, $day, $start, $end, $allow]);
                    $count++;
                }
                $pdo->commit();
                return Response::json(['ok' => true, 'count' => $count]);
            } catch (\Throwable $e) {
                if ($pdo->inTransaction()) $pdo->rollBack();
                return new Response(500, ['error' => 'Falha ao salvar horários']);
            }
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);

        $router->get('/api/auth/access-schedules/status', function (Request $r) {
            $pdo = ConnectionManager::forTenant();
            $userId = isset($r->query['user_id']) ? (int)$r->query['user_id'] : 0;
            if ($userId <= 0) return new Response(422, ['error' => 'user_id requerido']);
            try {
                $stmt = $pdo->prepare('SELECT day_of_week, start_minute, end_minute, allow FROM user_access_schedules WHERE user_id = ?');
                $stmt->execute([$userId]);
                $rules = $stmt->fetchAll();
                $now = time();
                $dow = (int)date('w', $now); // 0..6
                $hm = (int)date('H', $now) * 60 + (int)date('i', $now);
                $relevant = array_filter($rules, fn($r)=> (int)$r['day_of_week'] === $dow && (int)$r['allow'] === 1);
                $allowed = true;
                if ($rules && count($rules) > 0) {
                    $allowed = false;
                    foreach ($relevant as $r) {
                        $s = (int)$r['start_minute']; $e = (int)$r['end_minute'];
                        if ($hm >= $s && $hm < $e) { $allowed = true; break; }
                    }
                }
                return Response::json(['allowed' => $allowed, 'now_dow' => $dow, 'now_minutes' => $hm]);
            } catch (\Throwable $e) {
                return new Response(500, ['error' => 'Falha ao verificar status']);
            }
        }, [AuthMiddleware::requireAuth(), Rbac::requirePermissions(['rbac.manage'])]);
    }
    public function name(): string { return 'Auth'; }
    public function version(): string { return '0.1.0'; }
    public function dependencies(): array { return []; }
}