<?php
/**
 * Sistema de autenticación
 * Gestiona inicio de sesión, registro y control de sesión.
 */ 
 
if (session_status() === PHP_SESSION_NONE) {
    session_start();
}

require_once 'config.php';
require_once __DIR__ . '/database_schema.php';
// Respaldo en caso de que sendEmail no esté disponible (p. ej. problemas de include).
if (!function_exists('sendEmail')) {
    function sendEmail($to, $subject, $htmlBody, $replyTo = null) {
        $fromEmail = defined('MAIL_FROM') ? MAIL_FROM : ini_get('sendmail_from');
        $fromName  = defined('MAIL_FROM_NAME') ? MAIL_FROM_NAME : 'No-Reply';
        $headers = [];
        $headers[] = 'MIME-Version: 1.0';
        $headers[] = 'Content-type: text/html; charset=UTF-8';
        if ($fromEmail) {
            $headers[] = 'From: ' . sprintf('"%s" <%s>', addslashes($fromName), $fromEmail);
        }
        if ($replyTo) {
            $headers[] = 'Reply-To: ' . $replyTo;
        }
        $headers[] = 'X-Mailer: PHP/' . phpversion();
        $headersStr = implode("\r\n", $headers);
        return @mail($to, $subject, $htmlBody, $headersStr);
    }
}

class Auth {
    private $db;
     
    public function __construct($db) {
        $this->db = $db;
    }

    private function ensureVerificationSchema() {
        static $done = false;
        if ($done) {
            return;
        }
        try {
            ensureDatabaseSchema($this->db);
            $done = true;
        } catch (Throwable $e) {
            error_log('No se pudo asegurar el esquema de verificacion: ' . $e->getMessage());
        }
    }

    private function startUserSession($row) {
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_regenerate_id(true);
        }

        $_SESSION['user_id'] = $row['id'];
        $_SESSION['username'] = $row['usuario_de_logeo'];
        $_SESSION['email'] = $row['email'];
        $_SESSION['full_name'] = $row['nombre_completo'];
        $_SESSION['logged_in'] = true;
        $_SESSION['login_time'] = time();
        unset($_SESSION['pending_verification_user_id'], $_SESSION['pending_verification_email'], $_SESSION['pending_verification_name']);

        $updateStmt = $this->db->prepare("UPDATE usuarios SET ultima_entrada_registrada = NOW() WHERE id = ?");
        if ($updateStmt) {
            $updateStmt->bind_param('i', $row['id']);
            $updateStmt->execute();
            $updateStmt->close();
        }

        $this->logActivity($row['id'], 'login', 'Usuario inicio sesion');

        return [
            'success' => true,
            'message' => 'Login exitoso',
            'user' => [
                'id' => $row['id'],
                'username' => $row['usuario_de_logeo'],
                'full_name' => $row['nombre_completo']
            ]
        ];
    }

    private function setPendingVerificationSession($row) {
        $_SESSION['pending_verification_user_id'] = (int)$row['id'];
        $_SESSION['pending_verification_email'] = $row['email'];
        $_SESSION['pending_verification_name'] = $row['nombre_completo'] ?: $row['usuario_de_logeo'];
    }

    private function completePendingLoginIfMatches($userId) {
        if (empty($_SESSION['pending_verification_user_id']) || (int)$_SESSION['pending_verification_user_id'] !== (int)$userId) {
            return false;
        }

        $stmt = $this->db->prepare("
            SELECT id, usuario_de_logeo, email, nombre_completo, estado_de_usuario
            FROM usuarios
            WHERE id = ? AND estado_de_usuario = 'activo'
            LIMIT 1
        ");
        if (!$stmt) {
            return false;
        }
        $stmt->bind_param('i', $userId);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if (!$row) {
            return false;
        }

        $this->startUserSession($row);
        return true;
    }

    public function createEmailVerificationChallenge($userId, $email, $displayName, $deliveryMethod = 'both') {
        $this->ensureVerificationSchema();

        $deliveryMethod = in_array($deliveryMethod, ['code', 'link', 'both'], true) ? $deliveryMethod : 'both';
        $code = str_pad((string)random_int(0, 999999), 6, '0', STR_PAD_LEFT);
        $token = bin2hex(random_bytes(32));
        $expiresAt = (new DateTime('+30 minutes'))->format('Y-m-d H:i:s');
        $verifyLink = rtrim(BASE_URL, '/') . '/verify_account?token=' . urlencode($token);

        $stmt = $this->db->prepare("
            INSERT INTO verificacion_de_usuarios (id_de_usuario, codigo, token, enlace_verificacion, expira_el, creado_el)
            VALUES (?, ?, ?, ?, ?, NOW())
        ");
        if (!$stmt) {
            return ['success' => false, 'message' => 'No se pudo preparar la verificacion'];
        }

        $stmt->bind_param('issss', $userId, $code, $token, $verifyLink, $expiresAt);
        $ok = $stmt->execute();
        $stmt->close();
        if (!$ok) {
            return ['success' => false, 'message' => 'No se pudo guardar la verificacion'];
        }

        $safeName = htmlspecialchars($displayName ?: $email, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $subject = 'Verifica tu cuenta - Firmador de PDFs CODEX';
        $html = '<div style="font-family:Segoe UI,Tahoma,Arial,sans-serif;font-size:14px;color:#111;">'
              . '<h2 style="color:#29648D;margin:0 0 12px;">Hola ' . $safeName . ',</h2>'
              . '<p>Necesitamos verificar tu correo para activar el acceso a la aplicacion.</p>';

        if ($deliveryMethod === 'link' || $deliveryMethod === 'both') {
            $html .= '<p><a href="' . $verifyLink . '" style="display:inline-block;padding:12px 18px;background:#29648D;color:#fff;text-decoration:none;border-radius:6px;">Verificar correo</a></p>'
                  . '<p>Si el boton no funciona, copia y pega este enlace en tu navegador:</p>'
                  . '<p style="word-break:break-all;"><a href="' . $verifyLink . '">' . $verifyLink . '</a></p>';
        }

        if ($deliveryMethod === 'code' || $deliveryMethod === 'both') {
            $html .= '<p>Tambien puedes ingresar este codigo manualmente:</p>'
                  . '<p style="font-size:24px;letter-spacing:3px;margin:12px 0;"><strong>' . $code . '</strong></p>';
        }

        $html .= '<p>La verificacion expira en 30 minutos.</p>'
              . '<hr style="border:none;border-top:1px solid #e5e7eb;margin:16px 0;" />'
              . '<p style="color:#555;margin:0;">Firmador de PDFs CODEX</p>'
              . '</div>';

        try {
            $emailSent = sendEmail($email, $subject, $html);
        } catch (Throwable $e) {
            error_log('Email verification send failed: ' . $e->getMessage());
            $emailSent = false;
        }

        if (!$emailSent) {
            return ['success' => false, 'message' => 'No se pudo enviar el correo de verificacion'];
        }

        return [
            'success' => true,
            'message' => 'Correo de verificacion enviado',
            'code' => $code,
            'link' => $verifyLink
        ];
    }

    public function sendVerificationByEmail($email, $deliveryMethod = 'both') {
        $email = trim((string)$email);
        if ($email === '') {
            return ['success' => false, 'message' => 'Email requerido'];
        }

        $stmt = $this->db->prepare("
            SELECT id, email, nombre_completo, usuario_de_logeo, estado_de_usuario
            FROM usuarios
            WHERE email = ?
            LIMIT 1
        ");
        if (!$stmt) {
            return ['success' => false, 'message' => 'No se pudo preparar la consulta'];
        }
        $stmt->bind_param('s', $email);
        $stmt->execute();
        $user = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if (!$user) {
            return ['success' => true, 'message' => 'Si el correo existe, enviaremos instrucciones de verificacion.'];
        }
        if ($user['estado_de_usuario'] === 'activo') {
            return ['success' => true, 'message' => 'La cuenta ya esta verificada.'];
        }

        return $this->createEmailVerificationChallenge(
            (int)$user['id'],
            $user['email'],
            $user['nombre_completo'] ?: $user['usuario_de_logeo'],
            $deliveryMethod
        );
    }
    
    /**  
     * Registra un usuario nuevo.
     */
    public function register($username, $email, $password, $fullName, $confirmPassword = null, $docType = null, $docNumber = null, $country = 'Colombia', $verificationMethod = 'both') {
        // Valida los datos mínimos.
        if (empty($username) || empty($email) || empty($password) || empty($fullName)) {
            return ['success' => false, 'message' => 'Todos los campos son obligatorios'];
        }
        if (empty($docType) || empty($docNumber)) {
            return ['success' => false, 'message' => 'Tipo y número de documento son obligatorios'];
        }
        
        /// Validación opcional de confirmación de contraseña.
        if ($confirmPassword !== null && $password !== $confirmPassword) {
            return ['success' => false, 'message' => 'Las contraseñas no coinciden'];
        }
        
        if (strlen($password) < 8) {
            return ['success' => false, 'message' => 'La contraseña debe tener al menos 8 caracteres'];
        }
        
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return ['success' => false, 'message' => 'Email inválido'];
        }
        
        // Verifica si el usuario ya existe.
        $stmt = $this->db->prepare("SELECT id FROM usuarios WHERE usuario_de_logeo = ? OR email = ?");
        $stmt->bind_param('ss', $username, $email);
        $stmt->execute();
        $result = $stmt->get_result();
        
        if ($result->num_rows > 0) {
            return ['success' => false, 'message' => 'El usuario o email ya existe'];
        }
        
        /// Genera el hash de la contraseña.
        $passwordHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
        
        $docType = strtoupper(substr(trim((string)$docType), 0, 20));
        $docNumber = substr(preg_replace('/\s+/', '', (string)$docNumber), 0, 50);
        $country = $country ? substr(trim((string)$country), 0, 50) : 'Colombia';

        // Inserta el usuario.
        $stmt = $this->db->prepare("
            INSERT INTO usuarios (usuario_de_logeo, email, contrasena_encryptada, nombre_completo, estado_de_usuario, creado_el, tipo_documento, numero_documento, pais)
            VALUES (?, ?, ?, ?, 'inactivo', NOW(), ?, ?, ?)
        ");
        
        $stmt->bind_param('sssssss', $username, $email, $passwordHash, $fullName, $docType, $docNumber, $country);
        
        if ($stmt->execute()) {
            $userId = $stmt->insert_id;
            $verification = $this->createEmailVerificationChallenge($userId, $email, $fullName ?: $username, $verificationMethod);
            $msg = 'Usuario registrado. Revisa tu correo para verificar la cuenta.';
            if (empty($verification['success'])) {
                $msg = 'Usuario registrado. ' . ($verification['message'] ?? 'No se pudo enviar la verificacion.');
            }

            return ['success' => true, 'message' => $msg];
            // Genera código de verificación (6 dígitos) y caducidad (30 minutos).
            $code = str_pad((string)random_int(0, 999999), 6, '0', STR_PAD_LEFT);
            $expiresAt = (new DateTime('+30 minutes'))->format('Y-m-d H:i:s');
            // Guarda el código de verificación.
            $okInsertCode = false;
            try {
                $vstmt = $this->db->prepare("
                    INSERT INTO verificacion_de_usuarios (id_de_usuario, codigo, expira_el, creado_el)
                    VALUES (?, ?, ?, NOW())
                ");
                $vstmt->bind_param('iss', $userId, $code, $expiresAt);
                $vstmt->execute();
                $okInsertCode = true;
                $vstmt->close();
            } catch (Throwable $e) {
                error_log('No se pudo guardar el código de verificación: ' . $e->getMessage());
            }
            // Envia el correo de verificacion
            $subject = 'Verifica tu cuenta - Firmador de PDFs CODEX';
            $safeName = htmlspecialchars($fullName ?: $username, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
            $verifyUrl = rtrim(BASE_URL, '/') . '/verify_account';
            $loginUrl = $verifyUrl;
            $html = '<div style="font-family:Segoe UI,Tahoma,Arial,sans-serif;font-size:14px;color:#111;">'
                  . '<h2 style="color:#29648D;margin:0 0 12px;">Hola ' . $safeName . ',</h2>'
                  . '<p>Tu cuenta ha sido creada exitosamente.</p>'
                  . '<p style="font-size:24px;letter-spacing:3px;margin:12px 0;"><strong>' . $code . '</strong></p>'
                  . '<p>Puedes iniciar sesión aquí: <a href="' . $loginUrl . '">' . $loginUrl . '</a></p>'
                  . '<hr style="border:none;border-top:1px solid #e5e7eb;margin:16px 0;" />'
                  . '<p style="color:#555;margin:0;">Firmador de PDFs CODEX</p>'
                  . '</div>';

            $emailSent = false;
            try { 
                $emailSent = sendEmail($email, $subject, $html);
            } catch (Throwable $e) {
                // Swallow email errors; proceed with successful registration 
                error_log('Email send failed on register: ' . $e->getMessage());
            }

            $msg = 'Usuario registrado. Revisa tu correo para el código de verificación.';
            if (!$okInsertCode) {
                $msg = 'Usuario registrado. No se pudo generar el código de verificación, contacte soporte.';
            } elseif (!$emailSent) {
                $msg = 'Usuario registrado. No se pudo enviar el correo de verificación.';
            }

            return ['success' => true, 'message' => $msg];
        } else {
            return ['success' => false, 'message' => 'Error al registrar usuario'];
        }
    }
    
    /**
     * Inicia sesion de usuario
     */
    public function login($username, $password) {
        if (empty($username) || empty($password)) {
            return ['success' => false, 'message' => 'Usuario y contraseña requeridos'];
        }
        
        /// Obtiene el usuario 
        $stmt = $this->db->prepare("
            SELECT id, usuario_de_logeo, email, contrasena_encryptada, nombre_completo, estado_de_usuario
            FROM usuarios
            WHERE (usuario_de_logeo = ? OR email = ?)
        ");
        
        $stmt->bind_param('ss', $username, $username);
        $stmt->execute();
        $result = $stmt->get_result();
        
        if ($row = $result->fetch_assoc()) {
            // Verifica la contraseña
            if (password_verify($password, $row['contrasena_encryptada'])) {
                if ($row['estado_de_usuario'] !== 'activo') {
                    $this->setPendingVerificationSession($row);
                    $verification = $this->createEmailVerificationChallenge((int)$row['id'], $row['email'], $row['nombre_completo'] ?: $row['usuario_de_logeo'], 'both');
                    $message = 'Cuenta no verificada. Te enviamos un enlace y un codigo de verificacion.';
                    if (empty($verification['success'])) {
                        $message = 'Cuenta no verificada. ' . ($verification['message'] ?? 'No se pudo enviar la verificacion.');
                    }
                    return [
                        'success' => false,
                        'verification_required' => true,
                        'message' => $message,
                        'email' => $row['email']
                    ];
                }
                // Crea la sesión y variables de sesión
                return $this->startUserSession($row);
                $_SESSION['user_id'] = $row['id'];
                $_SESSION['username'] = $row['usuario_de_logeo'];
                $_SESSION['email'] = $row['email'];
                $_SESSION['full_name'] = $row['nombre_completo'];
                $_SESSION['logged_in'] = true;
                $_SESSION['login_time'] = time();
                
                // Actualiza el ultimo acceso
                $updateStmt = $this->db->prepare("UPDATE usuarios SET ultima_entrada_registrada = NOW() WHERE id = ?");
                $updateStmt->bind_param('i', $row['id']);
                $updateStmt->execute();
                
                // Registra el inicio de sesión
                $this->logActivity($row['id'], 'login', 'Usuario inició sesión');

                // Retorna éxito de inicio de sesión junto con datos básicos del usuario
                return [
                    'success' => true,
                    'message' => 'Login exitoso',
                    'user' => [
                        'id' => $row['id'],
                        'username' => $row['usuario_de_logeo'],
                        'full_name' => $row['nombre_completo']
                    ]
                ];
            }
        }
        
        return ['success' => false, 'message' => 'Usuario o contraseña incorrectos'];
    }
    
    /**
     * Cierra sesión
     */
    public function logout() {
        if (isset($_SESSION['user_id'])) {
            $this->logActivity($_SESSION['user_id'], 'logout', 'Usuario cerró sesión');
        }
        
        session_unset();
        session_destroy();
        return ['success' => true, 'message' => 'Sesión cerrada'];
    }
    
    /**
     * Comprueba si el usuario sigue autenticado
     */
    public function isLoggedIn() {
        if (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true) {
            // Verifica expiraci?n de sesi?n (30 minutos)
            if (isset($_SESSION['login_time']) && (time() - $_SESSION['login_time'] > 1800)) {
                $this->logout();
                return false;
            }
            
            // Actualiza el tiempo de actividad
            $_SESSION['login_time'] = time();
            return true;
        }
        return false;
    }
    
    /** 
     * Obtiene el usuario actual
     */
    public function getCurrentUser() {
        if ($this->isLoggedIn()) {
            return [
                'id' => $_SESSION['user_id'],
                'username' => $_SESSION['username'],
                'email' => $_SESSION['email'],
                'full_name' => $_SESSION['full_name']
            ];
        }
        return null;
    }

    /**
     * Obtiene datos ampliados del perfil del usuario.
     */
    public function getUserProfile($userId) {
        $stmt = $this->db->prepare("
            SELECT 
                id,
                usuario_de_logeo AS username,
                email,
                nombre_completo AS full_name,
                tipo_documento AS doc_type,
                numero_documento AS doc_number,
                pais AS country,
                estado_de_usuario AS status,
                kyc_estado,
                kyc_verificado_el,
                kyc_motivo_resultado
            FROM usuarios
            WHERE id = ?
            LIMIT 1
        ");
        if (!$stmt) {
            return null;
        }
        // Ejecuta la consulta de perfil
        $stmt->bind_param('i', $userId);
        $stmt->execute();
        $res = $stmt->get_result();
        $row = $res->fetch_assoc();
        $stmt->close();
        return $row ?: null;
    }

    /**
     * Actualiza datos básicos del perfil.
     */
    public function updateProfile($userId, $fullName, $username, $email, $docType, $docNumber, $country = 'Colombia') {
        // Sanitiza y valida entradas
        $fullName = trim((string)$fullName);
        $username = trim((string)$username);
        $email = trim((string)$email);
        $docType = strtoupper(substr(trim((string)$docType), 0, 20));
        $docNumber = substr(preg_replace('/\\s+/', '', (string)$docNumber), 0, 50);
        $country = $country ? substr(trim((string)$country), 0, 50) : 'Colombia';

        // Validaciones básicas de campos obligatorios
        if ($fullName === '' || $username === '' || $email === '' || $docType === '' || $docNumber === '') {
            return ['success' => false, 'message' => 'Todos los campos son obligatorios'];
        }
        // Validaciones específicas de formato de email
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return ['success' => false, 'message' => 'Email inv\u00e1lido'];
        }
        // Validación de tipo de documento
        $allowedDocTypes = ['CC', 'CE', 'LC', 'PA', 'NIT', 'OTRO'];
        if (!in_array($docType, $allowedDocTypes, true)) {
            return ['success' => false, 'message' => 'Tipo de documento inv\u00e1lido'];
        }
        // Verifica duplicados de usuario o email
        $check = $this->db->prepare("
            SELECT id FROM usuarios 
            WHERE (usuario_de_logeo = ? OR email = ?) AND id <> ?
            LIMIT 1
        ");
        // Ejecuta la verificación de duplicados
        if ($check) {
            $check->bind_param('ssi', $username, $email, $userId);
            $check->execute();
            $dup = $check->get_result()->fetch_assoc();
            $check->close();
            if ($dup) {
                return ['success' => false, 'message' => 'Usuario o email ya en uso'];
            }
        }
        // Actualiza los datos del usuario
        $stmt = $this->db->prepare("
            UPDATE usuarios
            SET usuario_de_logeo = ?, email = ?, nombre_completo = ?, tipo_documento = ?, numero_documento = ?, pais = ?
            WHERE id = ?
        ");
        // Ejecuta la actualización
        if (!$stmt) {
            return ['success' => false, 'message' => 'No se pudo preparar la actualización'];
        }
        // Vincula valiabre y ejecuta sql
        $stmt->bind_param('ssssssi', $username, $email, $fullName, $docType, $docNumber, $country, $userId);
        $ok = $stmt->execute();
        $stmt->close();

        // Si fue exitoso, actualiza la sesión
        if ($ok) {
            // Mantiene la sesión en sync con los datos actualizados.
            $_SESSION['username'] = $username;
            $_SESSION['email'] = $email;
            $_SESSION['full_name'] = $fullName;
            return ['success' => true, 'message' => 'Perfil actualizado'];
        }
        // Si hubo error en la actualización
        return ['success' => false, 'message' => 'No se pudo actualizar el perfil'];
    }
    
    /**
     * Exige autenticación (para páginas protegidas)
     */
    public function requireAuth() {
        if (!$this->isLoggedIn()) {
            header('Location: login');
            exit;
        }
    }
    
    /**
     * Registra actividad del usuario
     */
    private function logActivity($userId, $action, $description) {
        $ip = $_SERVER['REMOTE_ADDR'];
        $userAgent = $_SERVER['HTTP_USER_AGENT'];
        // Inserta el registro de actividad
        $stmt = $this->db->prepare("
            INSERT INTO actividad_del_usuario (id_de_usuario, accion_de_usuario, descripcion, direccion_ip, user_agent, creado_el)
            VALUES (?, ?, ?, ?, ?, NOW())
        ");
        // Vincula parametros y ejecuta sql
        $stmt->bind_param('issss', $userId, $action, $description, $ip, $userAgent);
        $stmt->execute();
    }

    /**
     * Verifica la cuenta con email + código
     */
    public function verifyAccountByToken($token) {
        $this->ensureVerificationSchema();
        $token = trim((string)$token);
        if ($token === '') {
            return ['success' => false, 'message' => 'Token requerido'];
        }

        $stmt = $this->db->prepare("
            SELECT
                v.id,
                v.expira_el,
                v.consumido_el,
                u.id AS user_id,
                u.estado_de_usuario
            FROM verificacion_de_usuarios v
            INNER JOIN usuarios u ON u.id = v.id_de_usuario
            WHERE v.token = ?
            ORDER BY v.id DESC
            LIMIT 1
        ");
        if (!$stmt) {
            return ['success' => false, 'message' => 'No se pudo preparar la verificacion'];
        }
        $stmt->bind_param('s', $token);
        $stmt->execute();
        $row = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if (!$row) {
            return ['success' => false, 'message' => 'Enlace de verificacion invalido'];
        }
        if (!empty($row['consumido_el']) && $row['estado_de_usuario'] === 'activo') {
            $loggedIn = $this->completePendingLoginIfMatches((int)$row['user_id']);
            return ['success' => true, 'message' => 'Cuenta ya verificada.', 'logged_in' => $loggedIn];
        }
        if (!empty($row['consumido_el'])) {
            return ['success' => false, 'message' => 'Enlace ya utilizado'];
        }
        if (strtotime($row['expira_el']) < time()) {
            return ['success' => false, 'message' => 'Enlace expirado'];
        }

        return $this->activateAccountVerification((int)$row['user_id'], (int)$row['id']);
    }

    private function activateAccountVerification($userId, $verificationId) {
        $this->db->begin_transaction();
        try {
            $ustmt = $this->db->prepare("UPDATE usuarios SET estado_de_usuario = 'activo' WHERE id = ?");
            $ustmt->bind_param('i', $userId);
            $ustmt->execute();

            $cstmt = $this->db->prepare("UPDATE verificacion_de_usuarios SET consumido_el = NOW() WHERE id = ?");
            $cstmt->bind_param('i', $verificationId);
            $cstmt->execute();

            $this->db->commit();
        } catch (Throwable $e) {
            $this->db->rollback();
            return ['success' => false, 'message' => 'No se pudo activar la cuenta'];
        }

        $this->logActivity($userId, 'verify_account', 'Usuario verifico su cuenta');
        $loggedIn = $this->completePendingLoginIfMatches($userId);

        return [
            'success' => true,
            'message' => $loggedIn ? 'Cuenta verificada. Acceso concedido.' : 'Cuenta verificada. Ahora puede iniciar sesion.',
            'logged_in' => $loggedIn
        ];
    }

    public function verifyAccount($email, $code) {
        $this->ensureVerificationSchema();
        // Sanitiza entradas
        $email = trim((string)$email);
        $code = trim((string)$code);
        if ($email === '' || $code === '') {
            return ['success' => false, 'message' => 'Email y código son requeridos'];
        }

        // Busca al usuario
        $stmt = $this->db->prepare("SELECT id, estado_de_usuario FROM usuarios WHERE email = ?");
        $stmt->bind_param('s', $email);
        $stmt->execute();
        $user = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        // Verifica si el usuario existe
        if (!$user) {
            return ['success' => false, 'message' => 'Usuario no encontrado'];
        }
        $userId = (int)$user['id'];

        // $vstmt es un estamento sql para obtener el código de verificación
        $vstmt = $this->db->prepare(
            "SELECT id, expira_el, consumido_el FROM verificacion_de_usuarios WHERE id_de_usuario = ? AND codigo = ? ORDER BY id DESC LIMIT 1"
        );
        // Vincula parametros y ejecuta sql
        $vstmt->bind_param('is', $userId, $code);
        $vstmt->execute();
        $vres = $vstmt->get_result(); // $vres guarda el resultado
        $vrow = $vres->fetch_assoc(); // $vrow guarda la fila del resultado que esta en la variable $vres
        $vstmt->close();

        // Verifica validez del código
        if (!$vrow) {
            return ['success' => false, 'message' => 'Código inválido'];
        }
        if (!empty($vrow['consumido_el'])) {
            return ['success' => false, 'message' => 'Código ya utilizado'];
        }
        if (strtotime($vrow['expira_el']) < time()) {
            return ['success' => false, 'message' => 'Código expirado'];
        }

        return $this->activateAccountVerification($userId, (int)$vrow['id']);
        // Activate user and mark code consumed
        $this->db->begin_transaction();
        try {
            $ustmt = $this->db->prepare("UPDATE usuarios SET estado_de_usuario = 'activo' WHERE id = ?");
            $ustmt->bind_param('i', $userId);
            $ustmt->execute();

            $cstmt = $this->db->prepare("UPDATE verificacion_de_usuarios SET consumido_el = NOW() WHERE id = ?");
            $cstmt->bind_param('i', $vrow['id']);
            $cstmt->execute();

            $this->db->commit();
        } catch (Throwable $e) {
            $this->db->rollback();
            return ['success' => false, 'message' => 'No se pudo activar la cuenta'];
        }
        
        $this->logActivity($userId, 'verify_account', 'Usuario verificó su cuenta'); // Registra la actividad de verificación
        return ['success' => true, 'message' => 'Cuenta verificada. Ahora puede iniciar sesión.'];
    }

    /**
     * Cambia la contraseña
     */
    public function changePassword($userId, $oldPassword, $newPassword) {
        // Strlen revisa longitud de cadena, la cual debe ser al menos 8 caracteres
        if (strlen($newPassword) < 8) {
            return ['success' => false, 'message' => 'La nueva contraseña debe tener al menos 8 caracteres'];
        }
        
        // Verifica la contraseña anterior
        $stmt = $this->db->prepare("SELECT contrasena_encryptada FROM usuarios WHERE id = ?");
        $stmt->bind_param('i', $userId);
        $stmt->execute();
        $result = $stmt->get_result();
        
        // fetch_assoc obtiene la fila como un array asociativo
        if ($row = $result->fetch_assoc()) {
            if (password_verify($oldPassword, $row['contrasena_encryptada'])) {
                $newHash = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => 12]);
                
                $updateStmt = $this->db->prepare("UPDATE usuarios SET contrasena_encryptada = ? WHERE id = ?");
                $updateStmt->bind_param('si', $newHash, $userId);
                   
                if ($updateStmt->execute()) {
                    $this->logActivity($userId, 'password_change', 'Usuario cambió su contraseña');
                    return ['success' => true, 'message' => 'Contraseña actualizada'];
                }
            } else {
                return ['success' => false, 'message' => 'Contraseña actual incorrecta'];
            }
        }
        
        return ['success' => false, 'message' => 'Error al cambiar contraseña'];
    }

    /**
     * Genera un token de restablecimiento de contraseña y envía correo.
     */
    public function requestPasswordReset($identifier) { //El identificador puede ser email o usuario
        $identifier = trim((string)$identifier);
        if ($identifier === '') {
            return ['success' => false, 'message' => 'Usuario o email requerido'];
        }
        // Busca al usuario por email o nombre de usuario, limitando a 1 resultado
        $stmt = $this->db->prepare("
            SELECT id, email, nombre_completo, usuario_de_logeo, estado_de_usuario
            FROM usuarios
            WHERE email = ? OR usuario_de_logeo = ?
            LIMIT 1
        ");
        $stmt->bind_param('ss', $identifier, $identifier);
        $stmt->execute();
        $user = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        // Respuesta neutra para evitar enumeración
        $genericMsg = 'Si la cuenta existe, enviaremos instrucciones a su correo.';
        if (!$user) {
            return ['success' => true, 'message' => $genericMsg];
        }
        // En caso de que exista el usuario se procede a enviar el correo
        $userId = (int)$user['id'];

        // Invalida tokens abiertos previos
        try {
            // Registramos en la base de datos que el usuario va a solicitar un restablecimiento de contraseña
            $clearStmt = $this->db->prepare("UPDATE restablecimientos_de_contrasenas SET consumido_el = NOW() WHERE id_de_usuario = ? AND consumido_el IS NULL");
            $clearStmt->bind_param('i', $userId);
            $clearStmt->execute();
            $clearStmt->close();
        } catch (Throwable $e) {
            error_log('No se pudieron limpiar tokens previos: ' . $e->getMessage());
        }
        // Genera un nuevo token y su fecha de expiración (1 hora)
        $token = bin2hex(random_bytes(32));
        $expiresAt = (new DateTime('+1 hour'))->format('Y-m-d H:i:s');

        // Inserta el nuevo token en la base de datos
        $insertStmt = $this->db->prepare("
            INSERT INTO restablecimientos_de_contrasenas (id_de_usuario, token, expira_el, creado_el)
            VALUES (?, ?, ?, NOW())
        ");
        $insertStmt->bind_param('iss', $userId, $token, $expiresAt);
        $ok = $insertStmt->execute();
        $insertStmt->close();

        // En caso de fallo al insertar el token
        if (!$ok) {
            return ['success' => false, 'message' => 'No se pudo generar el enlace de recuperación'];
        }

        // Prepara y envía el correo de restablecimiento
        $resetUrl = rtrim(BASE_URL, '/') . '/reset_password?token=' . urlencode($token);
        $safeName = htmlspecialchars($user['nombre_completo'] ?: $user['usuario_de_logeo'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
        $subject = 'Restablece tu contraseña - Firmador de PDFs CODEX';
        $html = '<div style="font-family:Segoe UI,Tahoma,Arial,sans-serif;font-size:14px;color:#111;">'
              . '<h2 style="color:#29648D;margin:0 0 12px;">Hola ' . $safeName . ',</h2>'
              . '<p>Recibimos una solicitud para restablecer tu contraseña.</p>'
              . '<p><a href="' . $resetUrl . '" style="display:inline-block;padding:12px 18px;background:#29648D;color:#fff;text-decoration:none;border-radius:6px;">Restablecer contraseña</a></p>'
              . '<p>Si el botón no funciona, copia y pega este enlace en tu navegador:</p>'
              . '<p style="word-break:break-all;"><a href="' . $resetUrl . '">' . $resetUrl . '</a></p>'
              . '<p>El enlace expira en 1 hora. Si no solicitaste este cambio, ignora este correo.</p>'
              . '<hr style="border:none;border-top:1px solid #e5e7eb;margin:16px 0;" />'
              . '<p style="color:#555;margin:0;">Firmador de PDFs CODEX</p>'
              . '</div>';

        try {
            // Envía el correo
            sendEmail($user['email'], $subject, $html);
        } catch (Throwable $e) {
            error_log('Fallo al enviar correo de restablecimiento: ' . $e->getMessage());
        }
        // Registra la actividad de solicitud de restablecimiento
        $this->logActivity($userId, 'password_reset_request', 'Usuario solicitó restablecer su contraseña');
        return ['success' => true, 'message' => $genericMsg];
    }

    /**
     * Restablece la contraseña usando un token válido.
     */
    public function resetPasswordWithToken($token, $newPassword, $confirmPassword = null) {
        $token = trim((string)$token);
        if ($token === '') {
            return ['success' => false, 'message' => 'Token requerido'];
        }
        if (strlen($newPassword) < 8) {
            return ['success' => false, 'message' => 'La nueva contraseñaa debe tener al menos 8 caracteres'];
        }
        if ($confirmPassword !== null && $newPassword !== $confirmPassword) {
            return ['success' => false, 'message' => 'Las contraseñas no coinciden'];
        }

        // Busca el token en la base de datos para verificar su validez
        $stmt = $this->db->prepare("
            SELECT pr.id, pr.id_de_usuario, pr.expira_el, pr.consumido_el
            FROM restablecimientos_de_contrasenas pr
            WHERE pr.token = ?
            ORDER BY pr.id DESC
            LIMIT 1
        ");

        // Vincula parametros y ejecuta sql
        $stmt->bind_param('s', $token);
        $stmt->execute();
        $reset = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        // Verifica validez del token
        if (!$reset) {
            return ['success' => false, 'message' => 'Token inválido'];
        }
        if (!empty($reset['consumido_el'])) {
            return ['success' => false, 'message' => 'Token ya utilizado'];
        }
        if (strtotime($reset['expira_el']) < time()) {
            return ['success' => false, 'message' => 'Token expirado'];
        }

        $userId = (int)$reset['id_de_usuario'];

        // Actualiza la contraseña y marca el token como consumido
        $this->db->begin_transaction(); // begin_transaction inicia una transacción en la base de datos
        try {
            $newHash = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => 12]);

            $up = $this->db->prepare("UPDATE usuarios SET contrasena_encryptada = ? WHERE id = ?");
            $up->bind_param('si', $newHash, $userId);
            $up->execute();

            $mark = $this->db->prepare("UPDATE restablecimientos_de_contrasenas SET consumido_el = NOW() WHERE id = ?");
            $mark->bind_param('i', $reset['id']);
            $mark->execute();

            // Cierra cualquier token pendiente adicional
            $markAll = $this->db->prepare("UPDATE restablecimientos_de_contrasenas SET consumido_el = NOW() WHERE id_de_usuario = ? AND consumido_el IS NULL AND id <> ?");
            $markAll->bind_param('ii', $userId, $reset['id']);
            $markAll->execute();

            $this->db->commit();
        } catch (Throwable $e) {
            $this->db->rollback();
            return ['success' => false, 'message' => 'No se pudo actualizar la contraseña'];
        }

        $this->logActivity($userId, 'password_reset', 'Usuario restableció su contraseña con token');
        return ['success' => true, 'message' => 'Contraseña actualizada. Ahora puede iniciar sesión.'];
    }
}

/**
 * Devuelve una instancia de Auth a todas las clases que la requieran.
 */
function getAuth() {
    $db = getDatabaseConnection();
    return new Auth($db);
}
?>
