import os
import re
import smtplib
import logging
import json
import base64
import mimetypes
import secrets
import uuid
import requests
from datetime import datetime, timedelta
from collections import defaultdict
import io
from io import BytesIO

from flask import (
    request, jsonify, send_from_directory, abort, render_template,
    redirect, url_for, flash, session, current_app, send_file, Response
)
from flask_login import (
    login_required, login_user, logout_user, current_user
)
from sqlalchemy import func, or_
from sqlalchemy.exc import SQLAlchemyError

from config import (
    UPLOAD_FOLDER, SMTP_SERVER, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD,
    SMTP_USE_TLS, SMTP_USE_SSL, ROLE_STORAGE_LIMITS
)
from elasticsearch import Elasticsearch
from pygments import highlight
from pygments.formatters import HtmlFormatter
from pygments.lexers import (
    guess_lexer, guess_lexer_for_filename, get_lexer_by_name, get_all_lexers
)
from pygments.styles import get_all_styles
from pygments.util import ClassNotFound
from markdown import markdown
from PIL import Image, ExifTags
from PIL.ExifTags import TAGS
from pymediainfo import MediaInfo
from rapidfuzz import process, fuzz
from src.auth import generate_token, jwt_required
from src.models import db, Paste, User, Favorite, shared_pastes
from werkzeug.security import check_password_hash
from werkzeug.utils import secure_filename
import magic
import jwt
from guesslang import Guess
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import smtplib
import pytesseract
import subprocess
import tempfile
from flask import Flask, request, Response, stream_with_context, jsonify
from pygments.lexers import get_all_lexers

#es = Elasticsearch(hosts=["http://elasticsearch:9200"])
es = Elasticsearch(hosts=["http://elasticsearch:9200"], timeout=30, max_retries=10, retry_on_timeout=True)


# Configuración de DeepSeek-Coder
DEEPSEEK_API_URL = "https://api.deepseek.com/beta/v1/chat/completions"


DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")  # Reemplaza con tu clave de API

HEADERS = {
    "Authorization": f"Bearer {DEEPSEEK_API_KEY}",
    "Content-Type": "application/json"
}

def get_client_ip():
    return request.headers.get("X-Forwarded-For", request.remote_addr).split(',')[0].strip()


def get_readable_languages():
    """
    Retorna una lista de lenguajes con sus nombres amigables (ej. 'Python', 'JavaScript') 
    usando el primer alias disponible como `value` y el nombre formal como `label`.
    """
    languages = []
    for lexer in get_all_lexers():
        name, aliases, _, _ = lexer
        if aliases:  # Ignorar lexers sin alias
            languages.append((aliases[0], name))
    # Ordenar alfabéticamente por nombre amigable
    return sorted(languages, key=lambda x: x[1].lower())


def stream_response(payload):
    """
    Stream the response from DeepSeek-Chat to the frontend in real-time.
    Extracts only the generated text from each chunk.
    """
    try:
        with requests.post(DEEPSEEK_API_URL, headers=HEADERS, json=payload, stream=True, timeout=120) as response:
            response.raise_for_status()

            for line in response.iter_lines():
                if line:
                    decoded_line = line.decode("utf-8").strip()
                    
                    # Ignorar mensajes de keep-alive o fin de transmisión
                    if decoded_line == "data: [DONE]":
                        break

                    if decoded_line.startswith("data:"):
                        try:
                            json_data = json.loads(decoded_line[5:].strip())  # Eliminar "data: " del inicio
                            if "choices" in json_data and len(json_data["choices"]) > 0:
                                chunk_text = json_data["choices"][0].get("delta", {}).get("content", "")
                                if chunk_text:
                                    yield chunk_text  # Enviar solo el código generado
                        except json.JSONDecodeError:
                            continue  # Ignorar líneas malformadas
    except requests.exceptions.RequestException as e:
        yield f"Error connecting to DeepSeek: {str(e)}"


def summarize_text(content):
    """ Envía el contenido del paste a la API de Hugging Face para obtener un resumen. """
    payload = {
        "inputs": content[:1024],  # Limitamos a 1024 caracteres
        "parameters": {
            "max_length": 200,  # Definir max_length explícitamente
            "min_length": 50,   # Evitar resúmenes demasiado cortos
            "do_sample": False  # No generar texto aleatorio
        }
    }

    try:
        response = requests.post(HUGGINGFACE_API_URL, headers=HUGGINGFACE_HEADERS, json=payload)
        response.raise_for_status()

        result = response.json()

        logging.debug(f"Respuesta completa de Hugging Face: {result}")

        if "error" in result:
            return f"⚠️ Error en la API: {result['error']}"

        if not isinstance(result, list) or "generated_text" not in result[0]:
            return "⚠️ Respuesta inesperada de la API. Intenta más tarde."

        return result[0]["generated_text"]

    except requests.exceptions.HTTPError as e:
        return f"❌ Error HTTP: {e.response.status_code} - {e.response.text}"
    except requests.exceptions.RequestException as e:
        return f"⚠️ Error inesperado: {str(e)}"


def extract_pdf_text(file_content):
    with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_pdf:
        tmp_pdf.write(file_content)
        tmp_pdf.flush()
        pdf_path = tmp_pdf.name

    cmd = ["pdftotext", pdf_path, "-"]  # "-" => salida a stdout
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, check=True)
        return result.stdout
    except subprocess.CalledProcessError as e:
        logging.error(f"❌ Error con pdftotext: {e}")
        return ""
    finally:
        # Opcional: borrar el archivo temporal
        import os
        os.remove(pdf_path)

def index_paste(paste):
    try:
        file_path = os.path.join("/app/uploads", paste.filename)
        content = ""

        if paste.content_type == "application/pdf":
            logging.info(f"📄 Intentando extraer texto del PDF: {paste.filename}")
            with open(file_path, "rb") as f:
                file_content = f.read()
            content = extract_pdf_text(file_content)
            if not content.strip():
                logging.warning(f"⚠️ No se extrajo texto del PDF {paste.filename}.")

        elif paste.content_type.startswith("image/"):
            logging.info(f"🖼 Procesando imagen con OCR: {paste.filename}")
            with open(file_path, "rb") as f:
                img_data = f.read()
            image = Image.open(io.BytesIO(img_data))
            try:
                content = pytesseract.image_to_string(image)
                logging.info(f"🖼 Texto extraído de la imagen (primeros 300 chars): {content[:300]}")
            except Exception as e:
                logging.error(f"❌ Error al procesar OCR: {e}")
                content = ""

        else:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()

        shared_with_ids = [str(user.id) for user in paste.shared_with]

        doc = {
            "id": str(paste.id),
            "title": paste.title or paste.filename,
            "content": content,
            "owner_id": str(paste.owner_id),
            "created_at": paste.created_at.isoformat(),
            "private": paste.private,
            "shared_with": shared_with_ids
        }

        es.index(index="pastes", id=paste.id, document=doc)
        logging.info(f"✅ Paste {paste.id} indexado correctamente en Elasticsearch.")

    except Exception as e:
        current_app.logger.error(f"Error indexando paste {paste.id}: {e}")

def convert_to_degrees(value):
    """ Convierte coordenadas GPS en formato EXIF a grados decimales """
    d, m, s = value
    return float(d) + (float(m) / 60.0) + (float(s) / 3600.0)

def get_image_metadata(file_path):
    """ Extrae y formatea los metadatos EXIF de una imagen, incluyendo GPS. """
    try:
        img = Image.open(file_path)
        exif_data = img._getexif()  # Obtener datos EXIF
        if not exif_data:
            return [{"Info": "No EXIF metadata found in image file"}]

        metadata = []
        gps_data = {}

        for tag, value in exif_data.items():
            decoded_tag = ExifTags.TAGS.get(tag, tag)  # Decodificar etiqueta EXIF

            if decoded_tag == "GPSInfo":
                # Extraer GPSInfo correctamente
                gps_info_raw = exif_data.get(tag, {})
                if isinstance(gps_info_raw, dict):
                    for gps_tag, gps_value in gps_info_raw.items():
                        gps_decoded = ExifTags.GPSTAGS.get(gps_tag, gps_tag)
                        gps_data[gps_decoded] = gps_value

                    # Convertir coordenadas GPS a formato decimal
                    if "GPSLatitude" in gps_data and "GPSLongitude" in gps_data:
                        lat = convert_to_degrees(gps_data["GPSLatitude"])
                        lon = convert_to_degrees(gps_data["GPSLongitude"])
                        lat_ref = gps_data.get("GPSLatitudeRef", "N")
                        lon_ref = gps_data.get("GPSLongitudeRef", "E")

                        if lat_ref != "N":
                            lat = -lat
                        if lon_ref != "E":
                            lon = -lon

                        gps_data["Decimal Coordinates"] = f"{lat}, {lon}"

                else:
                    gps_data["Error"] = "GPSInfo format incorrect, could not decode"

            else:
                metadata.append({decoded_tag: str(value)})

        # Si hay datos GPS, los añadimos a los metadatos
        if gps_data:
            metadata.append({"GPS Data": gps_data})

        return metadata if metadata else [{"Info": "No metadata found in image file"}]

    except Exception as e:
        print(f"[ERROR] No se pudo leer EXIF de {file_path}: {e}")
        return [{"Error": f"Failed to read EXIF: {str(e)}"}]

def has_gps_data(file_path):
    """Verifica si una imagen tiene datos GPS en los metadatos EXIF."""
    try:
        img = Image.open(file_path)
        exif_data = img.getexif()

        if not exif_data:
            return False  # No hay EXIF

        gps_info_tag = 34853  # Código del tag GPSInfo
        if gps_info_tag in exif_data:
            gps_info = exif_data[gps_info_tag]
            return bool(gps_info)  # Devuelve True si hay info GPS

        return False  # No se encontraron datos GPS
    except Exception as e:
        print(f"[ERROR] No se pudo leer EXIF de {file_path}: {e}")
        return False

def get_pygments_language_mappings():
    """Genera un mapeo entre Pygments y Monaco Editor, incluyendo extensiones de archivo."""
    all_lexers = list(get_all_lexers())

    language_mappings = {}
    extension_mappings = {}

    for name, aliases, filenames, mimetypes in all_lexers:
        if aliases:
            main_alias = aliases[0]  # Tomamos el primer alias como principal
            language_mappings[main_alias] = main_alias  # Asumimos que Monaco reconoce el mismo nombre

            # Extraer extensiones de archivo
            for filename in filenames:
                if filename.startswith("*."):
                    ext = filename[2:]  # Removemos "*."
                    extension_mappings[main_alias] = ext

    return language_mappings, extension_mappings

def register_error_handlers(app):
    @app.errorhandler(404)
    def not_found_error(error):
        return render_template("errors/404.html"), 404

    @app.errorhandler(500)
    def internal_error(error):
        return render_template("errors/500.html"), 500

    @app.errorhandler(403)
    def forbidden_error(error):
        return render_template("errors/403.html"), 403

def delete_expired_pastes():
    """Elimina los pastes expirados de la base de datos y Elasticsearch."""
    now = datetime.utcnow()

    # Obtener pastes expirados
    expired_pastes = Paste.query.filter(Paste.expires_at < now).all()

    if not expired_pastes:
        print("[INFO] No expired pastes found.")
        return

    for paste in expired_pastes:
        try:
            # Eliminar de Elasticsearch
            es.delete(index="pastes", id=paste.id, ignore=[404])
            print(f"[INFO] Deleted paste {paste.id} from Elasticsearch.")
        except Exception as e:
            print(f"[ERROR] Failed to delete paste {paste.id} from Elasticsearch: {e}")

        # Eliminar de la base de datos
        delete_paste_from_index(paste)
        db.session.delete(paste)
    
    db.session.commit()
    print(f"[INFO] Deleted {len(expired_pastes)} expired pastes from the database.")


def calculate_storage_used(user_id):
    """
    Calcula el almacenamiento utilizado por el usuario.

    :param user_id: ID del usuario.
    :return: Almacenamiento utilizado en MB.
    """
    total = db.session.query(db.func.sum(Paste.size)).filter_by(user_id=user_id).scalar()
    total = total if total else 0
    mb = total / (1024 ** 2)  # Convertir bytes a MB
    return mb



def get_shared_pastes(user, paste_filters=None, page=1, per_page=10):
    """
    Obtiene los pastes compartidos con el usuario especificado, aplicando filtros si es necesario.

    :param user: Objeto de usuario actual.
    :param paste_filters: Lista de filtros adicionales para aplicar a la consulta.
    :param page: Número de página para la paginación.
    :param per_page: Número de elementos por página.
    :return: Objeto de paginación con los pastes compartidos.
    """
    query = Paste.query.join(shared_pastes).filter(shared_pastes.c.user_id == user.id)

    if paste_filters:
        query = query.filter(*paste_filters)

    query = query.order_by(Paste.created_at.desc())

    pagination = query.paginate(page=page, per_page=per_page, error_out=False)

    return pagination


def delete_paste_from_index(paste):
    try:
        es.delete(index='pastes', id=paste.id)
        print(f"Deleted paste {paste.id} from index.")
    except Exception as e:
        print(f"Error deleting paste {paste.id} from index: {e}")

def calculate_stats(user_id=None, start_date=None, end_date=None):
    query = Paste.query

    if user_id is not None:
        query = query.filter_by(user_id=user_id)

    if start_date:
        if isinstance(start_date, str):
            start_date = datetime.strptime(start_date, '%Y-%m-%d')
        query = query.filter(Paste.created_at >= start_date)

    if end_date:
        if isinstance(end_date, str):
            end_date = datetime.strptime(end_date, '%Y-%m-%d').replace(hour=23, minute=59, second=59)
        query = query.filter(Paste.created_at <= end_date)

    pastes = query.all()

    stats = {
        "total_pastes": len(pastes),
        "total_size": sum(paste.size for paste in pastes),
        "total_text_pastes": query.filter(Paste.content_type.like('text/%')).count(),
        "total_file_pastes": query.filter(Paste.content_type.notlike('text/%')).count(),
        "total_media_pastes": query.filter(
            or_(
                Paste.content_type.like('image/%'),
                Paste.content_type.like('video/%'),
                Paste.content_type.like('audio/%')
            )
        ).count(),
        "total_compressed_pastes": query.filter(
            Paste.content_type.in_([
                "application/zip", "application/x-tar", "application/gzip",
                "application/x-bzip2", "application/x-7z-compressed", "application/x-rar-compressed"
            ])
        ).count(),
        "languages": list(set(paste.language for paste in pastes if paste.language)),
        "counts_text": [],
        "counts_file": [],
        "counts_media": [],
        "counts_compressed": [],
        "pastes": pastes  # Lista de pastes
    }

    # Agrupar estadísticas por lenguaje y tipo
    language_groups = query.with_entities(Paste.language, Paste.content_type).all()
    language_counts = {}
    for lang, ctype in language_groups:
        if lang not in language_counts:
            language_counts[lang] = {"text": 0, "file": 0, "media": 0}
        if ctype.startswith("text/"):
            language_counts[lang]["text"] += 1
        elif ctype.startswith(("image/", "video/", "audio/")):
            language_counts[lang]["media"] += 1
        else:
            language_counts[lang]["file"] += 1

    stats["counts_text"] = [language_counts[lang]["text"] for lang in stats["languages"]]
    stats["counts_file"] = [language_counts[lang]["file"] for lang in stats["languages"]]
    stats["counts_media"] = [language_counts[lang]["media"] for lang in stats["languages"]]

    return stats

def get_current_user():
    user_id = session.get('user_id')
    if not user_id:
        return None
    # Supongamos que tienes un modelo User para buscar el usuario
    return User.query.get(user_id)

logging.basicConfig(
    level=logging.DEBUG,  # Cambiar a INFO si no quieres mensajes de depuración
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        logging.StreamHandler()  # Envía los logs a la consola
    ]
)

# Define la función highlight_code
def highlight_code(content, language=None, filename=None):
    if language:
        try:
            lexer = get_lexer_by_name(language)
        except:
            lexer = guess_lexer(content)
    else:
        if filename:
            lexer = guess_lexer_for_filename(filename, content)
        else:
            lexer = guess_lexer(content)

    formatter = HtmlFormatter(linenos=True, cssclass="highlight")
    html_code = highlight(content, lexer, formatter)
    
    return html_code

MEDIA_MIME_TYPES = (
    'image/',
    'video/',
    'audio/',
    'application/pdf',
)

# Tipos MIME basados en texto que no comienzan con 'text/'
TEXT_BASED_APPLICATION_MIME_TYPES = (
    'application/json',
    'application/javascript',
    'application/xml',
    'application/xhtml+xml',
    'application/sql',
    'text/xml',
    # Agrega otros tipos basados en texto según tus necesidades
)

LANGUAGE_TO_EXTENSION = {
    "python": "py",
    "javascript": "js",
    "java": "java",
    "csharp": "cs",
    "cpp": "cpp",
    "ruby": "rb",
    "go": "go",
    "html": "html",
    "css": "css",
    "php": "php",
    "swift": "swift",
    "kotlin": "kt",
    "rust": "rs",
    "typescript": "ts",
    "bash": "sh",
    "plaintext": "txt",
    "sql": "sql",
    "json": "json",
    "yaml": "yaml",
    "xml": "xml"
}

# Mapeo personalizado de extensiones a lenguajes
EXTENSION_TO_LANGUAGE = {
    ".sh": "bash",
    ".bash": "bash",
    ".py": "python",
    ".json": "json",
    ".js": "javascript",
    ".ts": "typescript",
    ".sql": "sql",
    ".html": "html",
    ".css": "css",
    ".java": "java",
    ".c": "c",
    ".cpp": "cpp",
    ".rb": "ruby",
    ".go": "go",
    ".png": "image",
    ".jpg": "image",
    ".jpeg": "image",
    ".gif": "image",
    ".mp4": "video",
    ".avi": "video",
    ".mp3": "audio",
    ".webm": "video",
    ".mov": "video",
    ".mkv": "video",
    ".pdf": "pdf",
    ",log": "plaintext",
    ".md": "markdown",
    ".yaml": "yaml",
    ".toml": "toml",
    ".rs": "rust",
    ".kt": "kotlin",
    ".php": "php",
    ".cs": "csharp",
    ".xml": "xml",
    ".7z": "compressed",
    ".zip": "compressed",
    ".tar": "compressed",
    ".gz": "compressed",
    ".bz2": "compressed",
    ".rar": "compressed",
    ".tgz": "compressed",
    ".xz": "compressed",
    ".zst": "compressed",
    ".lz4": "compressed",
    ".lzma": "compressed",
    ".lzo": "compressed",
    ".z": "compressed",
    ".arj": "compressed",
    ".cab": "compressed"
    # Agrega más mapeos según tus necesidades
}

FILENAME_TO_LANGUAGE = {
    "PKGBUILD": "bash",
    # Puedes añadir más archivos específicos aquí
    # "INSTALL": "bash",
    # "configure": "bash",
}


mime = magic.Magic(mime=True, uncompress=True)


# Definir MIME types y extensiones de archivos comprimidos
COMPRESSED_MIME_TYPES = {
    "application/zip": "compressed",
    "application/gzip": "compressed",
    "application/x-tar": "compressed",
    "application/x-bzip2": "compressed",
    "application/x-7z-compressed": "compressed"
}

COMPRESSED_EXTENSIONS = {
    ".zip": "compressed",
    ".gz": "compressed",
    ".tgz": "compressed",
    ".tar": "compressed",
    ".bz2": "compressed",
    ".7z": "compressed"
}


def detect_language(content, unique_filename=None, original_filename=None, detected_mime_type=None):
    MIME_TO_LANGUAGE = {
        "application/json": "json",
        "application/x-shellscript": "bash",
        "application/javascript": "javascript",
        "text/plain": None,
        "text/xml": "xml",
        "text/html": "html",
        "text/css": "css",
        "text/x-python": "python",
        "text/x-c": "c",
        "text/x-c++": "cpp",
        "text/x-java": "java",
        "text/x-php": "php",
        "text/x-shellscript": "bash",
        "application/sql": "sql",
        "application/x-yaml": "yaml",
        "application/x-toml": "toml",
        "text/x-markdown": "markdown",
        "text/markdown": "markdown",
        "text/x-lua": "lua",
        "text/x-rust": "rust",
        "application/x-perl": "perl",
        "text/x-ruby": "ruby",
    }

    # 1️⃣ Por extensión
    if unique_filename:
        ext = os.path.splitext(unique_filename)[1].lower()
        if ext in EXTENSION_TO_LANGUAGE:
            language_by_extension = EXTENSION_TO_LANGUAGE[ext]
            logging.info(f"📂 Detectado lenguaje por extensión '{ext}': '{language_by_extension}'")
            return language_by_extension

    # 2️⃣ Por nombre de archivo específico
    if original_filename:
        base_name = os.path.basename(original_filename)
        if base_name.upper() in FILENAME_TO_LANGUAGE:
            language = FILENAME_TO_LANGUAGE[base_name.upper()]
            logging.info(f"📄 Nombre de archivo '{base_name}' mapeado a lenguaje '{language}'")
            return language

    # 3️⃣ Por shebang (primera línea del contenido)
    if content:
        first_line = content.strip().splitlines()[0] if content.strip() else ''
        if first_line.startswith('#!'):
            shebang_map = {
                'python': 'python',
                'bash': 'bash',
                'sh': 'bash',
                'perl': 'perl',
                'ruby': 'ruby',
                'php': 'php',
                'node': 'javascript',
                'lua': 'lua',
            }
            for key, lang in shebang_map.items():
                if key in first_line:
                    logging.info(f"📌 Shebang detectado en primera línea: {first_line} → {lang}")
                    return lang

    # 4️⃣ Por MIME
    if detected_mime_type:
        detected_language = MIME_TO_LANGUAGE.get(detected_mime_type)
        if detected_language:
            logging.info(f"📝 MIME type '{detected_mime_type}' mapeado a lenguaje '{detected_language}'")
            return detected_language
        elif detected_mime_type == "text/plain":
            logging.info(f"📎 MIME type 'text/plain', usando Guesslang como fallback.")

    # 5️⃣ Por Guesslang (si el contenido es suficientemente largo)
    if content and len(content.strip()) > 30:
        try:
            guess = Guess()
            detected_language = guess.language_name(content)
            logging.info(f"🤖 Guesslang detectó: '{detected_language}'")
            return detected_language
        except Exception as e:
            logging.warning(f"⚠️ Guesslang falló: {e}")

    # 6️⃣ Por Pygments (fallback)
    if content:
        try:
            lexer = guess_lexer(content)
            language = lexer.aliases[0]
            logging.info(f"🎨 Pygments detectó el lenguaje como '{language}'")
            return language
        except ClassNotFound:
            logging.warning("⚠️ Pygments no pudo detectar el lenguaje.")

    # 7️⃣ Por defecto
    logging.warning("🚨 No se pudo determinar el lenguaje, asignando 'plaintext'.")
    return "plaintext"


def init_routes(app):
    @app.route('/file/<filename>', methods=['GET'])
    def get_file(filename):
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        file_path = os.path.join(UPLOAD_FOLDER, filename)

        if not os.path.exists(file_path):
            abort(404, description="file not found")

        mime_type = mimetypes.guess_type(file_path)[0] or 'application/octet-stream'
        return send_from_directory(UPLOAD_FOLDER, filename, mimetype=mime_type)


    @app.route('/update-theme', methods=['POST'])
    def update_theme():
        data = request.get_json()
        theme = data.get('theme')
        if theme not in ['light', 'dark']:
            return jsonify({'error': 'Invalid theme'}), 400
        session['theme'] = theme
        return jsonify({'message': 'Theme updated successfully'}), 200


    @app.route('/', methods=['GET'])
    def index():
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        base_url = request.host_url.rstrip('/')
        user_name = None

        # Supongamos que el usuario está guardado en la sesión con su ID
        if 'user_id' in session:
            user = User.query.get(session['user_id'])  # Recuperar el usuario de la base de datos
            user_name = user.username if user else None  # Obtener el nombre del usuario

        return render_template('index.html', base_url=base_url, user_name=user_name)


    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if request.method == 'POST':
            username = request.form.get('username')
            password = request.form.get('password')
            user = User.query.filter_by(username=username).first()

            if user and user.check_password(password):
                login_user(user)  # Autentica al usuario
                flash("Login successful!", "success")
                return redirect(url_for('user_dashboard'))

            flash("Invalid username or password", "danger")
            return redirect(url_for('login'))

        return render_template('login.html')

    @app.route('/paste/<int:id>/json', methods=['GET'])
    def get_paste_json(id):
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        paste = Paste.query.get(id)
        
        if not paste:
            return jsonify({"error": "Paste not found"}), 404

        # ✅ Restringir acceso si el paste es privado
        if paste.private:
            if not current_user.is_authenticated:
                logging.warning(f"🚫 Usuario no autenticado intentó acceder al paste privado {id} en JSON.")
                return jsonify({"error": "You do not have permission to view this paste"}), 403

            if current_user.id != paste.owner_id and current_user not in paste.shared_with:
                logging.warning(f"🚫 Usuario {current_user.id} no tiene acceso al paste privado {id} en JSON.")
                return jsonify({"error": "You do not have permission to view this paste"}), 403

        response_data = {
            "id": paste.id,
            "filename": paste.filename,
            "language": paste.language,
            "content_type": paste.content_type,
            "size": paste.size,
            "created_at": paste.created_at.strftime('%Y-%m-%d %H:%M:%S') if paste.created_at else None,
            "content": None
        }

        # ✅ Solo incluir contenido si es accesible
        if hasattr(paste, 'content') and paste.content:
            response_data["content"] = paste.content
        elif paste.filename:
            file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
            if os.path.exists(file_path):
                with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
                    response_data["content"] = f.read()

        return jsonify(response_data), 200


    @app.route('/paste/<int:id>/raw', methods=['GET'])
    def get_paste_raw(id):
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        paste = Paste.query.get(id)

        if not paste:
            return jsonify({"error": "Paste not found"}), 404

        # ✅ Restringir acceso si el paste es privado
        if paste.private:
            if not current_user.is_authenticated:
                logging.warning(f"🚫 Usuario no autenticado intentó acceder al paste privado {id} en RAW.")
                return jsonify({"error": "You do not have permission to view this paste"}), 403

            if current_user.id != paste.owner_id and current_user not in paste.shared_with:
                logging.warning(f"🚫 Usuario {current_user.id} no tiene acceso al paste privado {id} en RAW.")
                return jsonify({"error": "You do not have permission to view this paste"}), 403

        file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
        if not os.path.exists(file_path):
            return jsonify({"error": "File not found"}), 404

        # ✅ Leer el contenido del archivo y devolverlo como texto sin formato
        with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
            content = f.read()

        return content, 200, {'Content-Type': 'text/plain; charset=utf-8'}



    @app.route('/paste', methods=['POST'])
    @jwt_required
    def create_paste():
        """Crea un nuevo paste con detección automática de lenguaje, opciones de expiración y soporte para compartir."""
        file_obj = request.files.get('c')
        user_language = request.form.get('lang', '').strip().lower()
        expire = request.form.get('expire', 'yes').strip().lower()  # Normaliza el input
        share_with = request.form.getlist('share_with')  # Lista de usuarios con los que se comparte
        can_edit = request.form.get('can_edit', 'false').strip().lower() == 'true'  # Convertir a booleano
        private = request.form.get('private', 'false').strip().lower() == 'true'  # ✅ Nuevo: privacidad del paste

        logging.info(f"User provided language: {user_language}")

        if not file_obj:
            logging.warning("No file provided for the paste.")
            return jsonify({"error": "No content provided"}), 400

        filename = None
        content_type = None

        # Obtener usuario autenticado
        user = request.user

        # Leer el archivo antes de calcular el tamaño
        file_content = file_obj.read()
        file_size = len(file_content)
        file_obj.seek(0)  # Resetear el puntero del archivo

        # Validar límites de usuario
        if user.role == 'user':
            if file_size > 100 * 1024**2:  # 100 MB por paste
                logging.warning(f"User {user.username} tried to upload a file exceeding 100 MB.")
                return jsonify({"error": "File exceeds the 100 MB limit for your role"}), 403
            if user.storage_used + file_size > user.storage_limit:  # Límite de almacenamiento total
                logging.warning(f"User {user.username} exceeded their total storage limit.")
                return jsonify({"error": "You have exceeded your total storage limit"}), 403

        try:
            # Obtener la extensión del archivo
            original_filename = file_obj.filename
            file_extension = os.path.splitext(original_filename)[1].lower()

            # Generar un nombre único para el archivo
            unique_filename = f"{uuid.uuid4().hex}{file_extension}"
            file_path = os.path.join(UPLOAD_FOLDER, unique_filename)

            # Leer una muestra del archivo para analizar MIME
            file_sample = file_content[:8192]
            detected_mime_type = magic.Magic(mime=True).from_buffer(file_sample)

            logging.info(f"Detected MIME type: {detected_mime_type}")
            logging.info(f"Original filename: {original_filename}, Extension: {file_extension}, Size: {file_size} bytes")
            content_type = detected_mime_type

            # Guardar el archivo en el sistema de archivos
            with open(file_path, 'wb') as f:
                f.write(file_content)
            logging.info(f"Saved paste content to: {file_path}")

            # **Usar `detect_language()` para la detección de lenguaje**
            if not user_language:
                logging.info("No valid language provided, using automatic detection.")
                language = detect_language(
                    content=file_content.decode(errors="ignore"),
                    unique_filename=unique_filename,
                    original_filename=original_filename,
                    detected_mime_type=detected_mime_type
                )
            else:
                language = user_language
                logging.info(f"User specified language: {language}")

            filename = unique_filename

            # Actualizar el almacenamiento usado por el usuario
            user.storage_used += file_size
            db.session.commit()

        except Exception as e:
            logging.error(f"Exception occurred: {str(e)}")
            return jsonify({"error": f"Error processing the content: {str(e)}"}), 500

        # **Manejo de Expiración**
        expires_at = datetime.utcnow() + timedelta(days=1) if expire == 'yes' else None

        # **Crear registro del paste en la base de datos**
        paste = Paste(
            content_type=content_type,
            filename=filename,
            owner_id=user.id,
            user_id=user.id,
            language=language,
            size=file_size,
            expires_at=expires_at,
            private=private  # ✅ Guardamos la privacidad del paste
        )

        try:
            db.session.add(paste)
            db.session.commit()
            logging.info(f"Saved paste with ID: {paste.id}")

            # **Indexar contenido en Elasticsearch**
            index_paste(paste)

            # **Compartir paste con otros usuarios**
            shared_users = []
            if share_with:
                for username in share_with:
                    shared_user = User.query.filter_by(username=username).first()
                    if shared_user and shared_user.id != user.id:
                        db.session.execute(shared_pastes.insert().values(
                            paste_id=paste.id,
                            user_id=shared_user.id,
                            can_edit=can_edit
                        ))
                        shared_users.append(username)

                db.session.commit()
                logging.info(f"Paste {paste.id} shared with: {shared_users} (Editable: {can_edit})")

        except Exception as e:
            db.session.rollback()
            logging.error(f"Error saving paste: {str(e)}")
            return jsonify({"error": f"Error saving paste: {str(e)}"}), 500

        base_url = request.host_url.rstrip('/')
        paste_url = f"{base_url}/paste/{paste.id}"

        logging.info(f"Paste created successfully. ID: {paste.id}, URL: {paste_url}")
        return jsonify({
            "url": paste_url,
            "language": language,
            "expires_at": expires_at,
            "private": private,  # ✅ Enviar estado de privacidad en la respuesta
            "shared_with": shared_users,
            "can_edit": can_edit
        }), 201

            
    @app.route('/paste/<int:id>', methods=['GET'])
    def get_paste(id):
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        paste = Paste.query.get(id)

        if not paste:
            logging.warning(f"Paste ID {id} not found.")
            return render_template("errors/404.html", message="This paste has been deleted or has expired."), 404

        # ✅ Restringir acceso si el paste es privado
        if paste.private:
            if not current_user.is_authenticated:
                logging.warning("🚫 Usuario no autenticado intentando acceder a paste privado.")
                return render_template("errors/403.html"), 403

            if current_user.id != paste.owner_id and current_user not in paste.shared_with:
                logging.warning(f"🚫 Usuario {current_user.id} no tiene acceso al paste privado {id}.")
                return render_template("errors/403.html"), 403

        # ✅ Determinar si el usuario puede editar el paste
        can_edit = paste.has_edit_permission(current_user) if current_user.is_authenticated else False

        # ✅ Obtener ruta del archivo
        file_path = os.path.join(UPLOAD_FOLDER, paste.filename) if paste.filename else None
        has_gps = has_gps_data(file_path) if paste.content_type.startswith("image/") else False

        # ✅ Si el usuario es el dueño, obtener lista de usuarios con los que se ha compartido
        shared_with = []
        if current_user.is_authenticated and paste.owner_id == current_user.id:
            shared_with = db.session.query(User.username, shared_pastes.c.can_edit).join(
                shared_pastes, User.id == shared_pastes.c.user_id
            ).filter(shared_pastes.c.paste_id == paste.id).all()

        # ✅ Definir los tipos MIME
        BINARIO_MIME_TYPES = ('font/', 'model/')
        TEXT_BASED_APPLICATION_MIME_TYPES = (
            'application/json', 'application/javascript', 'application/xml', 
            'application/xhtml+xml', 'application/sql'
        )
        MEDIA_MIME_TYPES = ('image/', 'video/', 'audio/', 'application/pdf')

        # ✅ Funciones para verificar tipos MIME
        def is_binary(mime_type):
            return any(mime_type.startswith(prefix) for prefix in BINARIO_MIME_TYPES)

        def is_media(mime_type):
            return any(mime_type.startswith(prefix) for prefix in MEDIA_MIME_TYPES)

        # ✅ Determinar el tipo de contenido
        if paste.content_type.startswith('text/') or paste.content_type in TEXT_BASED_APPLICATION_MIME_TYPES:
            es_binario, es_media = False, False
        elif is_media(paste.content_type):
            es_binario, es_media = False, True
        elif is_binary(paste.content_type):
            es_binario, es_media = True, False
        else:
            es_binario, es_media = True, False  # Por defecto, tratarlo como binario

        logging.debug(f"Content Type: {paste.content_type} | Is binary: {es_binario} | Is media: {es_media}")

        # ✅ Si es binario, renderizar `binary_view.html`
        if es_binario:
            if not paste.filename or not os.path.exists(file_path):
                logging.error(f"File for paste ID {id} not found: {file_path}")
                return jsonify({"error": "File not found"}), 404

            try:
                owner = User.query.get(paste.owner_id)
                file_size = paste.size  # Asegurar que `size` está en el modelo `Paste`
                return render_template(
                    'binary_view.html', paste=paste, filename=paste.filename, mime_type=paste.content_type, 
                    owner=owner, size=file_size, can_edit=can_edit
                )
            except Exception as e:
                logging.error(f"Error rendering binary view for paste ID {id}: {e}")
                return jsonify({"error": "Error while processing the file"}), 500

        # ✅ Si es media, renderizar `media_view.html`
        elif es_media:
            if not paste.filename or not os.path.exists(file_path):
                logging.error(f"File for paste ID {id} not found: {file_path}")
                return jsonify({"error": "File not found"}), 404

            try:
                metadata = []
                if paste.content_type.startswith("image/"):
                    logging.info(f"Extracting metadata for image: {file_path}")
                    metadata = get_image_metadata(file_path) or [{"Info": "No metadata found in image file"}]
                elif paste.content_type.startswith(("video/", "audio/")):
                    logging.info(f"Extracting metadata for media: {file_path}")
                    media_info = MediaInfo.parse(file_path)
                    metadata = [{key: value for key, value in track.to_data().items() if value} for track in media_info.tracks] or [{"Info": "No metadata found in media file"}]

                return render_template(
                    'media_view.html', filename=paste.filename, mime_type=paste.content_type, 
                    metadata=metadata, paste_id=paste.id, can_edit=can_edit, has_gps=has_gps
                ), 200

            except Exception as e:
                logging.error(f"Error rendering media view for paste ID {id}: {e}")
                return jsonify({"error": "Error while processing the media file"}), 500

        # ✅ Si es texto, renderizar `text_paste.html`
        else:
            try:
                with open(file_path, 'r', encoding='utf-8', errors='replace') as f:
                    content = f.read()

                is_markdown = (
                    paste.filename and paste.filename.lower().endswith('.md') or
                    paste.language and paste.language.lower() in ['md', 'markdown'] or
                    paste.content_type == 'text/markdown'
                )

                if is_markdown:
                    md_html = markdown(content, extensions=['tables', 'fenced_code', 'codehilite'])
                    return render_template(
                        'text_paste.html', paste=paste, md_html_code=md_html, is_markdown=True, 
                        can_edit=can_edit, shared_with=shared_with
                    ), 200
                else:
                    html_code = highlight_code(content, language=paste.language, filename=paste.filename)
                    return render_template(
                        'text_paste.html', paste=paste, html_code=html_code, is_markdown=False, 
                        can_edit=can_edit, shared_with=shared_with
                    ), 200

            except Exception as e:
                logging.error(f"Error reading text file for paste ID {id}: {e}")
                return jsonify({"error": "Error processing text file"}), 500

   
    @app.route('/pastes', methods=['GET'])
    @jwt_required
    def list_pastes():
        try:
            # Obtener todos los pastes del usuario autenticado
            user = request.user
            pastes = Paste.query.filter_by(owner_id=user.id).all()

            if not pastes:
                return jsonify({"message": "No pastes found"}), 200

            base_url = request.host_url.rstrip('/')
            response_list = []
            for paste in pastes:
                response_list.append({
                    "id": paste.id,
                    "url": f"{base_url}/paste/{paste.id}",
                    "title": paste.title or paste.filename,
                    "type": paste.get_type(),  # Método definido en tu modelo Paste
                    "size": paste.size or 0,  # Devuelve 0 si el tamaño no está definido
                    "created_at": paste.created_at.strftime('%Y-%m-%d %H:%M:%S') if paste.created_at else None,
                    "is_favorite": paste in user.favorite_pastes  # Verifica si el paste es favorito
                })

            return jsonify(response_list), 200
        except Exception as e:
            logging.error(f"Error retrieving pastes: {e}")
            return jsonify({"error": "Error retrieving pastes"}), 500

    @app.route('/register', methods=['POST'])
    @jwt_required
    def register_user():
        authenticated_user = request.user

        if authenticated_user.username != 'admin':
            return jsonify({"error": "you have no access rights to register users"}), 403

        data = request.get_json()
        if not data:
            return jsonify({"error": "No data provided"}), 400

        username = data.get('username')
        password = data.get('password')

        if not username or not password:
            return jsonify({"error": "'username' and 'password' required"}), 400

        existing_user = User.query.filter_by(username=username).first()
        if existing_user:
            return jsonify({"error": "El nombre de usuario ya existe"}), 400

        try:
            new_user = User(username=username)
            new_user.set_password(password)
            db.session.add(new_user)
            db.session.commit()
            return jsonify({"message": f"Usuario '{username}' registry successful"}), 201
        except Exception as e:
            db.session.rollback()
            return jsonify({"error": f"Error registering user: {str(e)}"}), 500

    @app.route('/admin/login', methods=['GET', 'POST'])
    def admin_login():
        if request.method == 'POST':
            username = request.form.get('username')
            password = request.form.get('password')
            user = User.query.filter_by(username=username).first()

            if user and user.check_password(password) and username == 'admin':
                session['admin'] = username
                flash('Login successful', 'success')
                return redirect(url_for('admin_dashboard'))

            flash('Invalid credentials', 'danger')
        return render_template('admin_login.html')

    @app.route('/admin/logout')
    def admin_logout():
        session.pop('admin', None)
        flash('Logged out', 'info')
        return redirect(url_for('admin_login'))

    @app.route('/admin')
    def admin_dashboard():
        if 'admin' not in session:
            flash('Please log in to access the admin panel', 'warning')
            return redirect(url_for('admin_login'))

        users_count = User.query.count()
        pastes_count = Paste.query.count()
        return render_template('dashboard.html', users_count=users_count, pastes_count=pastes_count)

    @app.route('/admin/users')
    def admin_users():
        if 'admin' not in session:
            flash('Please log in to access the admin panel', 'warning')
            return redirect(url_for('admin_login'))

        users = User.query.all()
        return render_template('users.html', users=users)

    @app.route('/admin/users/add', methods=['GET', 'POST'])
    def admin_add_user():
        if 'admin' not in session:
            flash('Please log in to access the admin panel', 'warning')
            return redirect(url_for('admin_login'))

        if request.method == 'POST':
            username = request.form.get('username')
            password = request.form.get('password')
            role = request.form.get('role')  # Obtén el rol del formulario

            # Validar datos
            if not username or not password or not role:
                flash('All fields are required', 'danger')
                return redirect(url_for('admin_add_user'))

            # Validar si el rol es válido
            valid_roles = ['admin', 'advanced', 'user']
            if role not in valid_roles:
                flash('Invalid role selected.', 'danger')
                return redirect(url_for('admin_add_user'))

            # Crear usuario
            try:
                user = User(username=username, role=role, storage_limit=ROLE_STORAGE_LIMITS.get(role))
                user.set_password(password)
                db.session.add(user)
                db.session.commit()
                flash('User added successfully', 'success')
                return redirect(url_for('admin_users'))
            except Exception as e:
                db.session.rollback()
                flash(f'Error: {str(e)}', 'danger')
                return redirect(url_for('admin_add_user'))

        return render_template('add_user.html')

    @app.route('/admin/users/delete/<int:user_id>', methods=['POST'])
    def admin_delete_user(user_id):
        if 'admin' not in session:
            # Enviar JSON de error en lugar de redirigir
            return jsonify({"error": "Unauthorized"}), 401

        user = User.query.get(user_id)
        if not user:
            return jsonify({"error": "User not found"}), 404

        db.session.delete(user)
        db.session.commit()
        return jsonify({"message": "User deleted successfully"}), 200


    @app.route('/admin/pastes')
    def admin_pastes():
        if 'admin' not in session:
            flash('Please log in to access the admin panel', 'warning')
            return redirect(url_for('admin_login'))

        pastes = Paste.query.all()
        return render_template('pastes.html', pastes=pastes)

    @app.route('/admin/pastes/delete/<int:paste_id>', methods=['POST'])
    def admin_delete_paste(paste_id):
        if 'admin' not in session:
            flash('Please log in to access the admin panel', 'warning')
            return redirect(url_for('admin_login'))

        paste = Paste.query.get(paste_id)
        if not paste:
            flash('Paste not found', 'danger')
        else:
            delete_paste_from_index(paste)
            paste.shared_with.clear()
            db.session.delete(paste)
            db.session.commit()
            flash('Paste deleted successfully', 'success')
        return redirect(url_for('admin_pastes'))

    @app.route('/stats', methods=['GET'])
    def anonymous_stats():
        start_date = request.args.get('start_date')
        end_date = request.args.get('end_date')

        stats = calculate_stats(start_date=start_date, end_date=end_date)

        return render_template(
            'anonymous_stats.html',
            total_pastes=stats["total_pastes"],
            total_text_pastes=stats["total_text_pastes"],
            total_file_pastes=stats["total_file_pastes"],
            total_media_pastes=stats["total_media_pastes"],
            languages=stats["languages"],
            counts_text=stats["counts_text"],
            counts_file=stats["counts_file"],
            counts_media=stats["counts_media"],
        )


    @app.route('/api/docs')
    def redoc():
        return '''
        <!DOCTYPE html>
        <html>
        <head>
        <title>Pastebin API Docs</title>
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.css" />
        </head>
        <body>
        <redoc spec-url="/static/openapi.yaml"></redoc>
        <script src="https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js"></script>
        </body>
        </html>
        '''
    @app.route('/paste/<int:id>/download', methods=['GET'])
    def download_paste(id):
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        paste = Paste.query.get_or_404(id)

        # Comprobación de permisos para pastes privados
        if paste.private:
            if not current_user.is_authenticated or (
                current_user.id != paste.owner_id and current_user not in paste.shared_with
            ):
                flash('No tienes permiso para descargar este archivo.', 'danger')
                return redirect(url_for('get_paste', id=id))

        if not paste.filename:
            flash('Este paste no tiene un archivo asociado.', 'warning')
            return redirect(url_for('get_paste', id=id))

        file_path = os.path.join(app.config['UPLOAD_FOLDER'], paste.filename)
        if not os.path.exists(file_path):
            flash('El archivo no existe.', 'danger')
            return redirect(url_for('get_paste', id=id))

        # 🧠 Nombre limpio
        extension = paste.get_extension()
        download_name = f"paste_{paste.id}.{extension}"

        # 🧠 Corrección de mimetype si es sospechoso
        fallback_mime = {
            'zip': 'application/zip',
            'pdf': 'application/pdf',
            'jpg': 'image/jpeg',
            'jpeg': 'image/jpeg',
            'png': 'image/png',
            'gif': 'image/gif',
            'mp3': 'audio/mpeg',
            'mp4': 'video/mp4',
            'txt': 'text/plain',
            'json': 'application/json'
        }
        mimetype = paste.content_type or fallback_mime.get(extension, 'application/octet-stream')
        # fallback si content_type es incorrecto (ej: text/plain para .zip)

        # 📦 Leer archivo y servir como stream
        with open(file_path, 'rb') as f:
            file_data = BytesIO(f.read())

        return send_file(
            file_data,
            as_attachment=True,
            download_name=download_name,
            mimetype=mimetype
        )


    @app.route('/request-account', methods=['GET', 'POST'])
    def request_account():
        if request.method == 'POST':
            email = request.form.get('email')
            username = request.form.get('username')

            # Validate basic input
            if not email or not username:
                logging.warning("Validation failed: Missing email or username")
                flash("Email and username are required.", "danger")
                return redirect(url_for('request_account'))

            logging.info(f"Account request initiated. Username: {username}, Email: {email}")

            # Log the SMTP environment variables for debugging
            logging.debug(f"SMTP_SERVER={SMTP_SERVER}")
            logging.debug(f"SMTP_PORT={SMTP_PORT}")
            logging.debug(f"SMTP_USERNAME={SMTP_USERNAME}")
            logging.debug(f"SMTP_USE_SSL={SMTP_USE_SSL}, SMTP_USE_TLS={SMTP_USE_TLS}")

            # Send email
            try:
                msg = MIMEText(f"New account request:\n\nUsername: {username}\nEmail: {email}")
                msg['Subject'] = 'Account Request'
                msg['From'] = SMTP_USERNAME
                msg['To'] = SMTP_USERNAME  # Ajusta a tu dirección de correo receptora

                # Connect to the SMTP server
                if SMTP_USE_SSL:
                    with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
                        logging.debug("Using SMTP over SSL")
                        server.login(SMTP_USERNAME, SMTP_PASSWORD)
                        logging.info("SMTP login successful")
                        server.send_message(msg)
                else:
                    with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
                        if SMTP_USE_TLS:
                            logging.debug("Starting TLS session")
                            server.starttls()
                        server.login(SMTP_USERNAME, SMTP_PASSWORD)
                        logging.info("SMTP login successful")
                        server.send_message(msg)

                logging.info("Account request email sent successfully")
                flash("Your account request has been submitted successfully!", "success")
                return redirect(url_for('index'))

            except smtplib.SMTPException as smtp_error:
                logging.error(f"SMTP error: {smtp_error}")
                flash(f"SMTP error: {smtp_error}", "danger")
            except Exception as general_error:
                logging.error(f"An unexpected error occurred: {general_error}")
                flash(f"An error occurred: {general_error}", "danger")

            return redirect(url_for('request_account'))

        return render_template('request_account.html')

    @app.route('/user/details', methods=['GET'])
    @jwt_required
    def get_user_details():
        user = request.user  # Usuario autenticado
        if not user:
            return jsonify({"error": "User not found"}), 404

        # Calcular contadores
        pastes_count = Paste.query.filter_by(owner_id=user.id).count()
        favorites_count = user.favorite_pastes.count()
        shared_with_me_count = db.session.query(Paste).join(shared_pastes).filter(
            shared_pastes.c.user_id == user.id
        ).count()
        shared_with_others_count = db.session.query(Paste).join(shared_pastes).filter(
            Paste.owner_id == user.id,
            shared_pastes.c.user_id != user.id
        ).count()

        # Construir la respuesta
        response = {
            "id": user.id,
            "username": user.username,
            "role": user.role,
            "storage_used": user.storage_used,
            "storage_limit": user.storage_limit,
            "storage_remaining": max(user.storage_limit - user.storage_used, 0) if user.storage_limit != -1 else None,
            "theme_preference": user.theme_preference,
            # Suponiendo que tienes un campo user.created_at
            "created_at": user.created_at.isoformat() if hasattr(user, 'created_at') and user.created_at else None,
            "pastes_count": pastes_count,
            "favorites_count": favorites_count,
            "shared_with_me_count": shared_with_me_count,
            "shared_with_others_count": shared_with_others_count
        }

        return jsonify(response), 200


    @app.route('/admin/users/<int:user_id>/change_role', methods=['POST'])
    def change_user_role(user_id):
        if 'admin' not in session:
            flash('Please log in to access the admin panel', 'warning')
            return redirect(url_for('admin_login'))

        try:
            user = User.query.get(user_id)
            if not user:
                return jsonify({"error": "User not found"}), 404

            new_role = request.form.get('role')
            valid_roles = ['admin', 'advanced', 'user']
            if new_role not in valid_roles:
                return jsonify({"error": "Invalid role"}), 400

            # Actualizar rol y límite de almacenamiento
            user.role = new_role
            user.storage_limit = ROLE_STORAGE_LIMITS[new_role]
            db.session.commit()

            return jsonify({"message": f"User role updated to {new_role}"}), 200

        except Exception as e:
            db.session.rollback()
            return jsonify({"error": f"Failed to update role: {str(e)}"}), 500

    @app.route('/logout')
    @login_required
    def logout():
        logout_user()
        flash("You have been logged out.", "info")
        return redirect(url_for('login'))

    @app.route('/user-dashboard', methods=['GET'])
    @login_required
    def user_dashboard():
        user = current_user  # Usuario autenticado automáticamente

        # Obtener el número de página desde los parámetros de consulta, por defecto es 1
        page = request.args.get('page', 1, type=int)
        per_page = 10  # Número de pastes por página
        favorite_page = request.args.get('favorite_page', 1, type=int)

        private_page = request.args.get('private_page', 1, type=int)

        pastes = Paste.query.filter_by(owner_id=user.id).order_by(Paste.created_at.desc()).paginate(page=page, per_page=10)
        private_pastes = Paste.query.filter_by(owner_id=user.id, private=True).order_by(Paste.created_at.desc()).paginate(page=private_page, per_page=10)

        # Consultar los pastes del usuario con paginación
        pastes_query = Paste.query.filter_by(user_id=user.id).order_by(Paste.created_at.desc())
        pagination = pastes_query.paginate(page=page, per_page=per_page, error_out=False)
        pastes = pagination.items

        # Consultar los pastes favoritos del usuario con paginación
        favorite_query = Paste.query.filter(Paste.favorites.any(id=user.id))
        favorite_pagination = favorite_query.paginate(page=favorite_page, per_page=10, error_out=False)
        favorite_pastes = favorite_pagination.items

        # Consultar los pastes compartidos con el usuario
        shared_pastes_list = Paste.query.join(shared_pastes, (shared_pastes.c.paste_id == Paste.id))\
                                        .filter(shared_pastes.c.user_id == user.id)\
                                        .all()

        # Calcular estadísticas generales
        stats = calculate_stats(user.id)  # Asegúrate de que esta función devuelve un diccionario con las claves usadas a continuación

        # **Nuevo Código: Cálculo de Almacenamiento**

        # Obtener el rol del usuario
        user_role = user.role.lower() if user.role else 'user'  # Asegúrate de que el campo 'role' existe en el modelo User

        # Obtener el límite de almacenamiento basado en el rol
        storage_limits = current_app.config.get('ROLE_STORAGE_LIMITS', {
            'admin': -1,          # Ilimitado
            'advanced': 2 * 1024**3,  # 2GB en bytes
            'user': 1 * 1024**3,      # 1GB en bytes
        })

             # Obtener límite en bytes y convertir a MB
        storage_limit = storage_limits.get(user_role, 1 * 1024**3)  # 1GB en bytes
        storage_used = db.session.query(db.func.sum(Paste.size)).filter_by(user_id=user.id).scalar() or 0

        # Calcular almacenamiento disponible
        storage_available = storage_limit - storage_used if storage_limit != -1 else -1
        storage_available = max(storage_available, 0)  # Evita valores negativos

        # CONVERSIÓN A MB 
        storage_used_mb = round(storage_used / (1024**2), 2)
        storage_limit_mb = -1 if storage_limit == -1 else round(storage_limit / (1024**2), 2)
        storage_available_mb = -1 if storage_available == -1 else round(storage_available / (1024**2), 2)

        # DEBUG LOG PARA COMPROBAR QUE SE CONVIERTE BIEN
        app.logger.debug(f"Storage (MB) - Used: {storage_used_mb}, Limit: {storage_limit_mb}, Available: {storage_available_mb}")

        # Información del perfil del usuario (sin email ni bio)
        user_profile = {
            'username': user.username,
            'role': user.role.capitalize() if user.role else 'User',
        }

        return render_template(
            'user_dashboard.html',
            user=user,
            total_pastes=stats['total_pastes'],
            total_size=stats['total_size'],
            pastes=pastes,
            pagination=pagination,  # Pasar el objeto de paginación al template
            total_text_pastes=stats.get('total_text_pastes', 0),
            total_file_pastes=stats.get('total_file_pastes', 0),
            total_media_pastes=stats.get('total_media_pastes', 0),
            languages=stats.get('languages', []),
            counts_text=stats.get('counts_text', []),
            counts_file=stats.get('counts_file', []),
            counts_media=stats.get('counts_media', []),
            favorite_pastes=favorite_pastes,
            favorite_pagination=favorite_pagination,
            shared_pastes=shared_pastes_list,
            storage_used=storage_used_mb,
            storage_limit=storage_limit_mb,
            storage_available=storage_available_mb,
            user_profile=user_profile,
            private_pastes=private_pastes,
            LANGUAGE_TO_EXTENSION=LANGUAGE_TO_EXTENSION
        )


    @app.route('/paste/<int:id>', methods=['DELETE'])
    @jwt_required
    def delete_paste(id):
        paste = Paste.query.get(id)
        if not paste:
            return jsonify({"error": "Paste not found"}), 404

        # Permitir eliminación solo si es el propietario o un administrador
        if paste.owner_id != request.user.id and request.user.username != 'admin':
            return jsonify({"error": "You do not have permission to delete this paste"}), 403

        try:
            # Eliminar favoritos asociados
            paste.favorites.clear()

            # Eliminar el archivo si existe
            if paste.filename:
                file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
                if os.path.exists(file_path):
                    os.remove(file_path)

            delete_paste_from_index(paste)
            paste.shared_with.clear()
            db.session.delete(paste)
            db.session.commit()
            return jsonify({"message": "Paste deleted successfully"}), 200
        except Exception as e:
            db.session.rollback()
            return jsonify({"error": f"Error deleting paste: {str(e)}"}), 500


    @app.route('/change-password-form', methods=['GET'])
    @login_required
    def change_password_form():
        return render_template('change_password.html')


    @app.route('/change-password', methods=['POST'])
    @login_required
    def change_password():
        current_password = request.form.get('current_password')
        new_password = request.form.get('new_password')
        confirm_password = request.form.get('confirm_password')

        # Validar que los campos no estén vacíos
        if not current_password or not new_password or not confirm_password:
            flash("All fields are required.", "danger")
            return redirect(url_for('change_password_form'))

        # Verificar que las contraseñas coincidan
        if new_password != confirm_password:
            flash("New passwords do not match.", "danger")
            return redirect(url_for('change_password_form'))

        # Verificar contraseña actual
        if not current_user.check_password(current_password):  # Método check_password en el modelo User
            flash("Current password is incorrect.", "danger")
            return redirect(url_for('change_password_form'))

        # Actualizar la contraseña
        try:
            current_user.set_password(new_password)  # Método set_password en el modelo User
            db.session.commit()
            flash("Password updated successfully.", "success")
        except Exception as e:
            db.session.rollback()
            flash(f"An error occurred: {str(e)}", "danger")

        return redirect(url_for('user_dashboard'))

    @app.context_processor
    def inject_pygments_styles():
        styles = list(get_all_styles())
        current_theme = session.get('theme', 'light')  # Obtener el tema actual de la sesión
        default_pygments_style = 'monokai' if current_theme == 'dark' else 'default'
        
        # Obtener el nombre del usuario si está logueado
        user_name = None
        if 'user_id' in session:
            user = User.query.get(session['user_id'])
            user_name = user.username if user else None

        return dict(
            pygments_styles=styles,
            default_pygments_style=default_pygments_style,
            user_name=user_name
        )

    @app.route('/paste/<int:id>/download_page', methods=['GET'])
    def download_page(id):
        paste = Paste.query.get(id)
        if not paste:
            flash("Paste no encontrado", "danger")
            return redirect(url_for('index'))
        
        return render_template('download_page.html', paste=paste)

    @app.route('/paste/<int:id>/download_file', methods=['GET'])
    def download_file(id):
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        logging.info(f"Confirmed download request for paste ID: {id}")
        paste = Paste.query.get(id)
        if not paste:
            logging.warning(f"Paste ID {id} not found.")
            return jsonify({"error": "Paste not found"}), 404

        # ✅ Obtener la extensión del mapeo usando paste.language
        extension = LANGUAGE_TO_EXTENSION.get(paste.language.lower(), 'txt') if paste.language else 'txt'
        download_name = f"paste_{paste.id}.{extension}"
        logging.info(f"Using download filename: {download_name}")

        if paste.filename:  # 📂 Archivo físico guardado
            file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
            if not os.path.exists(file_path):
                logging.error(f"File {paste.filename} for paste ID {id} not found.")
                return jsonify({"error": "File not found"}), 404

            return send_from_directory(
                directory=UPLOAD_FOLDER,
                path=paste.filename,
                as_attachment=True,
                download_name=download_name,
                mimetype=paste.content_type
            )

        elif paste.content:  # 📝 Paste de texto
            file_stream = BytesIO()
            file_stream.write(paste.content.encode('utf-8'))
            file_stream.seek(0)

            return send_file(
                file_stream,
                as_attachment=True,
                download_name=download_name,
                mimetype='text/plain'
            )

        logging.error(f"Paste ID {id} has no associated content.")
        return jsonify({"error": "Paste has no associated content"}), 400



    @app.route('/paste/<int:id>/stream_download', methods=['GET'])
    def stream_download(id):
        ip = get_client_ip()
        current_app.logger.info(f"Vista del paste {id} desde IP: {ip}")
        paste = Paste.query.get(id)
        if not paste or not paste.filename:
            return jsonify({"error": "Paste or file not found"}), 404

        def generate():
            with open(os.path.join(UPLOAD_FOLDER, paste.filename), 'rb') as f:
                while True:
                    chunk = f.read(4096)
                    if not chunk:
                        break
                    yield chunk

        response = Response(generate(), mimetype=paste.content_type)
        response.headers.set('Content-Disposition', 'attachment', filename=paste.filename)
        return response

    @app.route('/user/paste/<int:id>/delete', methods=['POST'])
    @login_required
    def user_delete_paste(id):
        paste = Paste.query.get_or_404(id)

        # Verificar si el usuario es el propietario
        if paste.owner_id != current_user.id:
            return jsonify({"error": "You do not have permission to delete this paste"}), 403

        try:
            # Eliminar favoritos asociados
            paste.favorites.clear()

            # Eliminar el archivo asociado, si existe
            if paste.filename:
                file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
                if os.path.exists(file_path):
                    os.remove(file_path)

            # Eliminar el paste de la base de datos
            delete_paste_from_index(paste)
            db.session.delete(paste)
            db.session.commit()
            return jsonify({"message": "Paste deleted successfully"}), 200
        except Exception as e:
            db.session.rollback()
            return jsonify({"error": f"Error deleting paste: {str(e)}"}), 500

    @app.route('/user/<username>/stats', methods=['GET'])
    @login_required
    def user_stats(username):
        if current_user.username != username:
            abort(404)

        start_date_str = request.args.get('start_date')
        end_date_str = request.args.get('end_date')

        paste_filters = []
        try:
            if start_date_str:
                start_date = datetime.strptime(start_date_str, '%Y-%m-%d')
                paste_filters.append(Paste.created_at >= start_date)
            if end_date_str:
                end_date = datetime.strptime(end_date_str, '%Y-%m-%d').replace(hour=23, minute=59, second=59)
                paste_filters.append(Paste.created_at <= end_date)
        except ValueError as ve:
            app.logger.error(f"Error al parsear las fechas: {ve}")

        # Obtener pastes y favoritos
        page = request.args.get('page', 1, type=int)
        per_page = 10
        pastes_query = Paste.query.filter_by(user_id=current_user.id).order_by(Paste.created_at.desc())
        if paste_filters:
            pastes_query = pastes_query.filter(*paste_filters)
        pagination = pastes_query.paginate(page=page, per_page=per_page, error_out=False)

        favorite_query = Paste.query.filter(Paste.favorites.any(id=current_user.id))
        if paste_filters:
            favorite_query = favorite_query.filter(*paste_filters)
        favorite_pagination = favorite_query.paginate(page=1, per_page=10, error_out=False)

        # Calcular estadísticas
        stats = calculate_stats(current_user.id)

        # ✅ ✅ Si es AJAX (`?format=json`), solo devuelve datos del gráfico
        if request.args.get('format') == 'json':
            return jsonify({
                "languageLabels": stats['languages'],
                "textCounts": stats['counts_text'],
                "fileCounts": stats['counts_file'],
                "mediaCounts": stats['counts_media'],
                # ✅ Añadir las métricas con nombres correctos
                "totalPastes": stats['total_pastes'],
                "totalTextPastes": stats['total_text_pastes'],
                "totalFilePastes": stats['total_file_pastes'],
                "totalMediaPastes": stats['total_media_pastes']
            })

        # Obtener almacenamiento
        try:
            storage_used = calculate_storage_used(current_user.id)
            storage_limit = current_user.get_storage_limit() or -1
            storage_available = max(storage_limit - storage_used, 0) if storage_limit != -1 else -1

            storage_used_mb = round(storage_used / (1024**2), 2)
            storage_limit_mb = -1 if storage_limit == -1 else round(storage_limit / (1024**2), 2)
            storage_available_mb = -1 if storage_available == -1 else round(storage_available / (1024**2), 2)
        except Exception as e:
            app.logger.error(f"Error al calcular el almacenamiento: {e}")
            storage_used_mb = 0
            storage_limit_mb = -1
            storage_available_mb = -1

        shared_page = request.args.get('shared_page', 1, type=int)
        shared_pagination = get_shared_pastes(current_user, paste_filters=paste_filters, page=shared_page, per_page=per_page)

        # ✅ ✅ Si NO es AJAX, devuelve la página HTML normal (`render_template`)
        # ✅ Si es AJAX (`?format=json`), devuelve solo JSON (para gráficos y métricas)
        if request.args.get('format') == 'json':
            return jsonify({
                "languageLabels": stats['languages'],
                "textCounts": stats['counts_text'],
                "fileCounts": stats['counts_file'],
                "mediaCounts": stats['counts_media'],
                "totalPastes": stats['total_pastes'],
                "totalTextPastes": stats['total_text_pastes'],
                "totalFilePastes": stats['total_file_pastes'],
                "totalMediaPastes": stats['total_media_pastes']
            })

        return render_template(
            'user_dashboard.html',
            user=current_user,
            user_profile=current_user.role,
            total_pastes=stats['total_pastes'],
            total_size=stats['total_size'],
            total_text_pastes=stats['total_text_pastes'],
            total_file_pastes=stats['total_file_pastes'],
            total_media_pastes=stats['total_media_pastes'],
            languages=stats['languages'],
            counts_text=stats['counts_text'],
            counts_file=stats['counts_file'],
            counts_media=stats['counts_media'],
            pastes=pagination.items,
            pagination=pagination,
            favorite_pastes=favorite_pagination.items,
            favorite_pagination=favorite_pagination,
            shared_pastes=shared_pagination.items,
            shared_pagination=shared_pagination,
            start_date=start_date_str,
            end_date=end_date_str,
            storage_used=storage_used_mb,
            storage_limit=storage_limit_mb,
            storage_available=storage_available_mb,
        )


    @app.route('/api/token', methods=['POST'])
    def get_token():
        """
        Endpoint dedicado para generar un token JWT.
        """
        data = request.json
        if not data or 'username' not in data or 'password' not in data:
            return jsonify({"error": "Username and password are required"}), 400

        username = data['username']
        password = data['password']

        user = User.query.filter_by(username=username).first()
        if user and check_password_hash(user.password_hash, password):
            token = generate_token(username)
            return jsonify({"token": token}), 200

        return jsonify({"error": "Invalid username or password"}), 401

    @app.route('/paste/<int:id>/favorite', methods=['POST'])
    @login_required
    def add_to_favorites(id):
        user = current_user  # Usuario autenticado
        paste = Paste.query.get_or_404(id)

        # Verificar si ya es favorito
        if user in paste.favorites:
            # Sigues devolviendo 200, pero ahora con "success": True
            return jsonify({
                "success": True,
                "message": "Paste already in favorites"
            }), 200

        try:
            paste.favorites.append(user)
            db.session.commit()
            # ¡Aquí pones success: True!
            return jsonify({
                "success": True,
                "message": "Paste added to favorites"
            }), 201
        except Exception as e:
            db.session.rollback()
            return jsonify({"error": str(e), "success": False}), 500

    @app.route('/paste/<int:id>/unfavorite', methods=['POST'])
    @login_required
    def remove_from_favorites(id):
        user = current_user  # Usuario autenticado
        paste = Paste.query.get_or_404(id)

        if user not in paste.favorites:
            # Devuelve success: False
            return jsonify({"error": "Paste not in favorites", "success": False}), 404

        try:
            paste.favorites.remove(user)
            db.session.commit()
            return jsonify({
                "success": True,
                "message": "Paste removed from favorites"
            }), 200
        except Exception as e:
            db.session.rollback()
            return jsonify({"error": str(e), "success": False}), 500


    @app.route('/favorites', methods=['GET'])
    @login_required
    def list_favorites():
        user = current_user
        page = request.args.get('page', 1, type=int)
        per_page = 10
        favorite_query = Favorite.query.filter_by(user_id=user.id)
        pagination = favorite_query.paginate(page=page, per_page=per_page, error_out=False)

        favorites = [{
            "id": fav.paste.id,
            "title": fav.paste.filename,
            "type": fav.paste.get_type(),  # Tipo de archivo
            "created_at": fav.paste.created_at.strftime('%Y-%m-%d %H:%M:%S')  # Fecha de creación
        } for fav in pagination.items]

        return jsonify({
            "favorites": favorites,
            "pagination": {
                "page": pagination.page,
                "per_page": pagination.per_page,
                "total_pages": pagination.pages,
                "total_items": pagination.total,
                "has_next": pagination.has_next,
                "has_prev": pagination.has_prev,
                "next_page": pagination.next_num if pagination.has_next else None,
                "prev_page": pagination.prev_num if pagination.has_prev else None
            }
        })


    @app.route('/media/<int:id>', methods=['GET'])
    def serve_media(id):
        paste = Paste.query.get_or_404(id)

        # Ruta completa del archivo
        file_path = os.path.join(UPLOAD_FOLDER, paste.filename)

        if not os.path.exists(file_path):
            return "File not found", 404

        # Sirve el archivo directamente como contenido de video
        return send_file(file_path, mimetype=paste.content_type)
     

    @app.route('/create_paste_web', methods=['GET', 'POST'])
    @login_required
    def create_paste_web():
        if request.method == 'POST':
            paste_type = request.form.get('type')
            content = request.form.get('content', '').strip()
            file = request.files.get('file')
            expire = request.form.get('expire', 'yes').strip().lower()
            user_language = request.form.get('language', '').strip().lower()
            private = request.form.get('private', 'false').strip().lower() == 'true'  # ✅ Nuevo campo de privacidad
            share_with = request.form.getlist('share_with')  # ✅ Lista de usuarios para compartir

            filename = None
            language = None
            content_type = None
            size = 0

            if file:
                original_filename = secure_filename(file.filename)
                file_extension = os.path.splitext(original_filename)[1].lower().lstrip('.')
                filename = f"{uuid.uuid4().hex}.{file_extension}"
                file_path = os.path.join(UPLOAD_FOLDER, filename)

                file_content = file.read()
                size = len(file_content)
                file.seek(0)

                detected_mime_type = magic.Magic(mime=True).from_buffer(file_content[:8192])
                content_type = detected_mime_type

                with open(file_path, 'wb') as f:
                    f.write(file_content)

                language = detect_language(content=file_content.decode(errors="ignore"), unique_filename=filename)

            elif paste_type == 'Text':
                if not user_language:
                    language = detect_language(content=content)
                else:
                    language = user_language

                filename = f"{uuid.uuid4().hex}.txt"
                file_path = os.path.join(UPLOAD_FOLDER, filename)

                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(content)

                content_type = 'text/plain'
                size = len(content)

            expires_at = datetime.utcnow() + timedelta(days=1) if expire == 'yes' else None

            # ✅ Crear el nuevo paste con privacidad y expiración
            paste = Paste(
                content_type=content_type,
                filename=filename,
                owner_id=current_user.id,
                user_id=current_user.id,
                language=language,
                size=size,
                expires_at=expires_at,
                private=private  # ✅ Guardamos la privacidad del paste
            )

            db.session.add(paste)
            db.session.commit()

            # ✅ Compartir el paste con otros usuarios si está configurado
            shared_users = []
            if share_with:
                for username in share_with:
                    shared_user = User.query.filter_by(username=username).first()
                    if shared_user and shared_user.id != current_user.id:
                        db.session.execute(shared_pastes.insert().values(
                            paste_id=paste.id,
                            user_id=shared_user.id
                        ))
                        shared_users.append(username)

                db.session.commit()

            index_paste(paste)

            flash("Paste created successfully!", "success")
            return redirect(url_for('get_paste', id=paste.id))

        pygments_languages = get_readable_languages()
        return render_template('create_paste_web.html', pygments_languages=pygments_languages)

    def new_mail_signal(display_name, new_count, *args):
        # Extraer los valores de las estructuras
        mailbox = display_name[0] if isinstance(display_name, dbus.Struct) else display_name
        count = new_count[0] if isinstance(new_count, dbus.Struct) else new_count

        print(f"Nuevo correo en: {mailbox}. Correos nuevos: {count}")

    def main():
        # Configurar el loop de D-Bus
        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

        # Conectarse al bus de sesión
        bus = dbus.SessionBus()

        # Suscribirse a la señal de nuevo correo
        bus.add_signal_receiver(
            new_mail_signal,
            dbus_interface="org.gnome.evolution.mail.dbus.Signal",
            signal_name="Newmail",
            path="/org/gnome/evolution/mail/newmail"
        )

        # Ejecutar el loop principal
        loop = GLib.MainLoop()
        loop.run()

    if __name__ == "__main__":
        main()

    @app.route('/paste/<int:paste_id>')
    @login_required
    def view_paste(paste_id):
        paste = Paste.query.get_or_404(paste_id)
        # Verificar que el usuario es el propietario o tiene permisos para ver el paste
        if paste.owner_id != current_user.id:
            flash('No tienes permiso para ver este paste.', 'danger')
            return redirect(url_for('user_dashboard'))
        return render_template('view_paste.html', paste=paste)

    @app.route('/pastes/search', methods=['GET'])
    @jwt_required
    def search_pastes():
        """Búsqueda de pastes en Elasticsearch considerando pastes públicos y compartidos."""
        try:
            query = request.args.get('q', '').strip()
            if not query:
                return jsonify({"error": "Search query is required"}), 400

            user = request.user
            if not user:
                return jsonify({"error": "User not authenticated"}), 403

            body = {
                "query": {
                    "bool": {
                        "must": [{"match": {"content": query}}],
                        "filter": [
                            {"bool": {
                                "should": [
                                    {"term": {"owner_id": user.id}},
                                    {"terms": {"id": [paste.id for paste in user.shared_pastes]}},
                                    {"term": {"private": False}}
                                ]
                            }}
                        ]
                    }
                }
            }
            results = es.search(index="pastes", body=body)
            hits = results["hits"]["hits"]
            
            base_url = request.host_url.rstrip('/')
            response = [{
                "id": hit["_id"],
                "url": f"{base_url}/paste/{hit['_id']}",
                "content": hit["_source"].get("content", "")
            } for hit in hits]
            
            return jsonify(response), 200

        except Exception as e:
            current_app.logger.error(f"Error searching pastes: {e}")
            return jsonify({"error": "Error searching pastes"}), 500


    @app.route('/pastes/search_web', methods=['GET'])
    @login_required
    def search_pastes_web():
        """Búsqueda de pastes desde la web con filtros adicionales."""
        try:
            query = request.args.get('q', '').strip()
            content_type = request.args.get('content_type', '').strip()
            language = request.args.get('language', '').strip()

            user = current_user
            if not user.is_authenticated:
                return redirect(url_for('login'))

            must_clauses = []
            if query:
                must_clauses.append({"match": {"content": query}})
            if content_type:
                must_clauses.append({"term": {"content_type": content_type}})
            if language:
                must_clauses.append({"term": {"language": language}})

            body = {
                "query": {
                    "bool": {
                        "must": must_clauses,
                        "filter": [
                            {"bool": {
                                "should": [
                                    {"term": {"owner_id": user.id}},
                                    {"terms": {"id": [paste.id for paste in user.shared_pastes]}},
                                    {"term": {"private": False}}
                                ]
                            }}
                        ]
                    }
                }
            }
            results = es.search(index="pastes", body=body)
            hits = results["hits"]["hits"]
            
            pastes = [
                {
                    "id": hit["_id"],
                    "url": f"{request.host_url.rstrip('/')}/paste/{hit['_id']}",
                    "content_type": hit["_source"].get("content_type", ""),
                    "language": hit["_source"].get("language", ""),
                }
                for hit in hits
            ]
            
            return render_template('search_results.html', pastes=pastes, query=query, content_type=content_type, language=language)

        except Exception as e:
            current_app.logger.error(f"Error in web search: {e}")
            return render_template('error.html', message="Search failed"), 500

    @app.route('/api/paste/<int:id>/favorite', methods=['POST'])
    @jwt_required
    def api_add_to_favorites(id):
        try:
            # Usuario autenticado
            user = request.user
            paste = Paste.query.get_or_404(id)

            # Verificar si ya es favorito
            if paste in user.favorite_pastes:
                return jsonify({"message": "Paste already in favorites"}), 200

            # Añadir a favoritos
            user.favorite_pastes.append(paste)
            db.session.commit()
            return jsonify({"message": "Paste added to favorites"}), 201

        except Exception as e:
            logging.error(f"Error adding paste to favorites: {e}")
            db.session.rollback()
            return jsonify({"error": str(e)}), 500
    @app.route('/api/paste/<int:id>/unfavorite', methods=['POST'])
    @jwt_required
    def api_remove_from_favorites(id):
        try:
            # Usuario autenticado
            user = request.user
            paste = Paste.query.get_or_404(id)

            # Verificar si no está en favoritos
            if paste not in user.favorite_pastes:
                return jsonify({"message": "Paste is not in favorites"}), 200

            # Quitar de favoritos
            user.favorite_pastes.remove(paste)
            db.session.commit()
            return jsonify({"message": "Paste removed from favorites"}), 200

        except Exception as e:
            logging.error(f"Error removing paste from favorites: {e}")
            db.session.rollback()
            return jsonify({"error": str(e)}), 500

    @app.route('/api/paste/<int:id>/download', methods=['GET'])
    @jwt_required
    def api_download_paste_file(id):
        user = request.user  # Usuario autenticado mediante JWT
        paste = Paste.query.get_or_404(id)

        # Verificar permisos
        if paste.owner_id != user.id:
            return jsonify({"error": "You do not have permission to download this file"}), 403

        # Verificar que el paste tiene un archivo asociado
        if not paste.filename:
            return jsonify({"error": "This paste does not have an associated file"}), 400

        # Construir la ruta completa del archivo
        file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], paste.filename)

        # Verificar que el archivo exista
        if not os.path.exists(file_path):
            return jsonify({"error": "The file does not exist"}), 404

        # Determinar el tipo MIME del archivo
        mime_type = paste.content_type or mimetypes.guess_type(file_path)[0] or 'application/octet-stream'

        # Enviar el archivo con el nombre original y tipo MIME
        return send_file(
            file_path,
            as_attachment=True,
            download_name=paste.filename,  # Flask >= 2.0
            mimetype=mime_type
        )

    @app.route('/api/favorites', methods=['GET'])
    @jwt_required
    def api_list_favorites():
        try:
            # Obtener el usuario autenticado
            user = request.user

            # Obtener todos los favoritos del usuario
            favorite_pastes = user.favorite_pastes  # Relación definida en el modelo User

            if not favorite_pastes:
                return jsonify({"message": "No favorites found"}), 200

            base_url = request.host_url.rstrip('/')
            response_list = []
            for paste in favorite_pastes:
                response_list.append({
                    "id": paste.id,
                    "url": f"{base_url}/paste/{paste.id}",
                    "title": paste.title or paste.filename,
                    "type": paste.get_type(),  # Método definido en tu modelo Paste
                    "size": paste.size or 0,  # Devuelve 0 si el tamaño no está definido
                    "created_at": paste.created_at.strftime('%Y-%m-%d %H:%M:%S') if paste.created_at else None
                })

            return jsonify(response_list), 200
        except Exception as e:
            logging.error(f"Error retrieving favorites: {e}")
            return jsonify({"error": "Error retrieving favorites"}), 500

    @app.route('/paste/<int:id>/toggle_editable', methods=['POST'])
    @login_required
    def toggle_editable(id):
        paste = Paste.query.get_or_404(id)
        
        # Verificar que el usuario es el propietario
        if paste.owner_id != current_user.id:
            return jsonify({"error": "No tienes permiso para modificar este paste"}), 403
        
        try:
            # Toggle el estado de editable
            paste.editable = not paste.editable
            db.session.commit()
            
            status = "enabled" if paste.editable else "disabled"
            message = f"Editable {status} successfully."
            return jsonify({"success": True, "editable": paste.editable, "message": message}), 200
        
        except Exception as e:
            db.session.rollback()
            logging.error(f"Error toggling editable: {e}")
            return jsonify({"success": False, "error": "Error al actualizar el estado editable"}), 500

    @app.route('/paste/<int:id>/edit', methods=['GET', 'POST'])
    @login_required
    def edit_paste_web(id):
        paste = Paste.query.get_or_404(id)

        # Verificar permisos de edición
        if not paste.has_edit_permission(current_user):
            flash("No tienes permiso para editar este paste.", "danger")
            return redirect(url_for('get_paste', id=id))

        file_path = os.path.join(UPLOAD_FOLDER, paste.filename) if paste.filename else None

        # Manejar GET y POST
        if request.method == 'POST':
            new_content = request.form.get('content')
            if not new_content:
                flash("El contenido no puede estar vacío.", "danger")
                return redirect(url_for('edit_paste_web', id=id))

            try:
                # 1. Eliminar el paste del índice anterior en Elasticsearch
                delete_paste_from_index(paste)

                # 2. Guardar el contenido actualizado en el archivo
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(new_content)

                # Actualizar la información del paste en la base de datos
                paste.last_edited_at = datetime.utcnow()
                db.session.commit()

                # 3. Indexar nuevamente el paste actualizado en Elasticsearch
                index_paste(paste)

                flash("Paste actualizado correctamente.", "success")
                return redirect(url_for('get_paste', id=id))
            except Exception as e:
                db.session.rollback()
                flash(f"Ocurrió un error al guardar el paste: {str(e)}", "danger")
                return redirect(url_for('edit_paste_web', id=id))

        # Cargar contenido actual para la edición
        current_content = ""
        if file_path and os.path.exists(file_path):
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    current_content = f.read()
            except Exception as e:
                flash(f"Error al leer el contenido del paste: {str(e)}", "danger")

        return render_template('edit_paste.html', paste=paste, current_content=current_content)


    @app.route('/paste/<int:id>/share', methods=['POST'])
    @login_required
    def share_paste(id):
        data = request.json
        username = data.get('username')
        can_edit = data.get('can_edit', False)

        # Buscar al usuario con el que se compartirá el paste
        user = User.query.filter_by(username=username).first()
        if not user:
            return jsonify({"error": "User not found"}), 404

        # Buscar el paste
        paste = Paste.query.get_or_404(id)
        if paste.owner_id != current_user.id:
            return jsonify({"error": "You do not own this paste"}), 403

        try:
            # Verificar si ya existe el registro
            existing_entry = db.session.query(shared_pastes).filter_by(
                paste_id=paste.id,
                user_id=user.id
            ).first()

            if existing_entry:
                # Si el registro existe, actualizar el valor de `can_edit`
                db.session.execute(
                    shared_pastes.update()
                    .where(
                        (shared_pastes.c.paste_id == paste.id) &
                        (shared_pastes.c.user_id == user.id)
                    )
                    .values(can_edit=can_edit)
                )
            else:
                # Si el registro no existe, insertar uno nuevo
                stmt = shared_pastes.insert().values(
                    paste_id=paste.id,
                    user_id=user.id,
                    can_edit=can_edit
                )
                db.session.execute(stmt)

            db.session.commit()
            return jsonify({"message": f"Paste shared with {username}, can_edit: {can_edit}."}), 200

        except Exception as e:
            db.session.rollback()
            return jsonify({"error": f"Error sharing paste: {str(e)}"}), 500


    @app.route('/paste/<int:id>/check-permissions', methods=['GET'])
    @login_required
    def check_permissions(id):
        paste = Paste.query.get_or_404(id)
        if paste.owner_id == current_user.id:
            return jsonify({'can_edit': True}), 200

        shared_paste = SharedPaste.query.filter_by(paste_id=paste.id, user_id=current_user.id).first()
        if shared_paste and shared_paste.can_edit:
            return jsonify({'can_edit': True}), 200

        return jsonify({'can_edit': False}), 403

    @app.route('/api/shared_with_others', methods=['GET'])
    @jwt_required
    def shared_with_others():
        """
        Devuelve los pastes compartidos por el usuario autenticado con otros usuarios.
        """
        try:
            user = request.user  # Usuario autenticado

            # Obtener los pastes compartidos por el usuario actual con otros
            shared_pastes_query = db.session.query(Paste).join(shared_pastes).filter(
                shared_pastes.c.user_id != user.id,  # Excluir al propietario como destinatario
                Paste.owner_id == user.id           # El usuario es el propietario del paste
            ).all()

            # Construir respuesta con información de usuarios (usando joins para obtener el username)
            pastes = []
            for paste in shared_pastes_query:
                shared_users = db.session.query(User.username, shared_pastes.c.can_edit).join(
                    shared_pastes, shared_pastes.c.user_id == User.id
                ).filter(
                    shared_pastes.c.paste_id == paste.id,
                    shared_pastes.c.user_id != user.id  # Excluir al propietario
                ).all()

                shared_with_list = [
                    {"username": shared_user.username, "can_edit": shared_user.can_edit}
                    for shared_user in shared_users
                ]

                pastes.append({
                    "id": paste.id,
                    "title": paste.title or "Untitled",
                    "created_at": paste.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                    "shared_with": shared_with_list
                })

            return jsonify({"shared_with_others": pastes}), 200
        except Exception as e:
            logging.error(f"Error retrieving shared_with_others: {e}")
            return jsonify({"error": "Error retrieving shared_with_others"}), 500


    @app.route('/api/shared_with_me', methods=['GET'])
    @jwt_required
    def shared_with_me():
        """
        Devuelve los pastes compartidos con el usuario autenticado.
        """
        try:
            user = request.user  # Usuario autenticado

            # Obtener los pastes compartidos con el usuario actual
            shared_pastes_query = db.session.query(Paste).join(shared_pastes).filter(
                shared_pastes.c.user_id == user.id  # Pastes compartidos con el usuario
            ).all()

            # Construir respuesta
            pastes = [
                {
                    "id": paste.id,
                    "title": paste.title or "Untitled",
                    "created_at": paste.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                    "owner": paste.owner.username,
                    "can_edit": db.session.query(shared_pastes).filter_by(
                        paste_id=paste.id, user_id=user.id
                    ).first().can_edit
                }
                for paste in shared_pastes_query
            ]

            return jsonify({"shared_with_me": pastes}), 200
        except Exception as e:
            logging.error(f"Error retrieving shared_with_me: {e}")
            return jsonify({"error": "Error retrieving shared_with_me"}), 500


    @app.route('/api/paste/<int:paste_id>/share', methods=['POST'])
    @jwt_required # Asegúrate de usar el decorador correcto
    def add_share_paste(paste_id):
        try:
            # Obtener el usuario actual
            current_user = request.user
            if not current_user:
                return jsonify({"error": "User not found in request"}), 401

            # Verificar que el paste pertenece al usuario autenticado
            paste = Paste.query.filter_by(id=paste_id, owner_id=current_user.id).first()
            if not paste:
                return jsonify({"error": "Paste not found or you do not own it"}), 404

            # Obtener datos del cuerpo de la solicitud
            data = request.get_json()
            if not data:
                return jsonify({"error": "Invalid JSON data"}), 400

            username = data.get("username")
            can_edit = data.get("can_edit", False)

            if not username:
                return jsonify({"error": "Username is required"}), 400

            # Prevenir que el usuario comparta el paste consigo mismo
            if username.lower() == current_user.username.lower():
                return jsonify({"error": "You cannot share a paste with yourself"}), 400

            # Verificar que el usuario destinatario existe (sin sensibilidad a mayúsculas)
            recipient = User.query.filter(func.lower(User.username) == username.lower()).first()
            if not recipient:
                return jsonify({"error": f"User '{username}' not found"}), 404

            # Verificar si ya se ha compartido el paste con este usuario
            existing_share = db.session.query(shared_pastes).filter(
                shared_pastes.c.paste_id == paste_id,
                shared_pastes.c.user_id == recipient.id
            ).first()

            if existing_share:
                return jsonify({"error": f"Paste is already shared with '{username}'"}), 400

            # Asegurarse de que can_edit es un booleano
            if not isinstance(can_edit, bool):
                return jsonify({"error": "can_edit must be a boolean value"}), 400

            # Añadir el registro en shared_pastes
            db.session.execute(shared_pastes.insert().values(
                paste_id=paste_id,
                user_id=recipient.id,
                can_edit=can_edit
            ))
            db.session.commit()

            # Registrar la acción para auditoría
            app.logger.info(f"User '{current_user.username}' shared paste ID {paste_id} with '{recipient.username}' with can_edit={can_edit}")

            return jsonify({"message": f"Paste shared successfully with '{username}'", "can_edit": can_edit}), 200

        except SQLAlchemyError as e:
            db.session.rollback()
            app.logger.error(f"Database error while sharing paste: {e}")
            return jsonify({"error": "Database error", "details": str(e)}), 500
        except Exception as e:
            app.logger.error(f"Unexpected error while sharing paste: {e}")
            return jsonify({"error": "An unexpected error occurred", "details": str(e)}), 500
   

    @app.route('/api/paste/<int:paste_id>/unshare', methods=['POST'])
    @jwt_required
    def unshare_paste(paste_id):
        try:
            # Verificar que el usuario autenticado está configurado correctamente
            current_user = request.user
            if not current_user:
                return jsonify({"error": "User not found in request"}), 401

            # Verificar que el paste pertenece al usuario autenticado
            paste = Paste.query.filter_by(id=paste_id, owner_id=current_user.id).first()
            if not paste:
                return jsonify({"error": "Paste not found or you do not own it"}), 404

            # Obtener datos del cuerpo de la solicitud
            data = request.get_json()
            username = data.get("username")

            if not username:
                return jsonify({"error": "Username is required"}), 400

            # Verificar que el usuario destinatario existe
            recipient = User.query.filter_by(username=username).first()
            if not recipient:
                return jsonify({"error": f"User {username} not found"}), 404

            # Verificar si el paste está compartido con este usuario
            existing_share = db.session.query(shared_pastes).filter(
                shared_pastes.c.paste_id == paste_id,
                shared_pastes.c.user_id == recipient.id
            ).scalar()

            if not existing_share:
                return jsonify({"error": f"Paste is not shared with {username}"}), 400

            # Eliminar el registro en shared_pastes
            db.session.execute(
                shared_pastes.delete().where(
                    (shared_pastes.c.paste_id == paste_id) &
                    (shared_pastes.c.user_id == recipient.id)
                )
            )
            db.session.commit()

            return jsonify({"message": f"Paste unshared successfully from {username}"}), 200
        except Exception as e:
            return jsonify({"error": "An unexpected error occurred", "details": str(e)}), 500

    @app.route('/paste/<int:paste_id>/unshare', methods=['POST'])
    @login_required
    def unshare_paste_web(paste_id):
        """
        Versión con sesión/logueo normal para 'descompartir' un paste.
        """
        try:
            # Tomar el username del formulario o del JSON (como prefieras)
            # Si lo envías desde un fetch con JSON, usas request.get_json()
            # Si lo envías desde un formulario <form>, usas request.form
            data = request.get_json() or {}
            username = data.get("username")

            if not username:
                return jsonify({"error": "Username is required"}), 400

            # Verificar que el paste pertenece al usuario logueado
            paste = Paste.query.filter_by(id=paste_id, owner_id=current_user.id).first()
            if not paste:
                return jsonify({"error": "Paste not found or you do not own it"}), 404

            # Verificar que el usuario con el que se compartió existe
            recipient = User.query.filter_by(username=username).first()
            if not recipient:
                return jsonify({"error": f"User {username} not found"}), 404

            # Verificar si está compartido
            existing_share = db.session.query(shared_pastes).filter(
                (shared_pastes.c.paste_id == paste_id),
                (shared_pastes.c.user_id == recipient.id)
            ).scalar()

            if not existing_share:
                return jsonify({"error": f"Paste is not shared with {username}"}), 400

            # Eliminar el registro de shared_pastes
            db.session.execute(
                shared_pastes.delete().where(
                    (shared_pastes.c.paste_id == paste_id) &
                    (shared_pastes.c.user_id == recipient.id)
                )
            )
            db.session.commit()

            return jsonify({"message": f"Paste unshared from {username} successfully"}), 200

        except Exception as e:
            db.session.rollback()
            return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500

    @app.route('/contact', methods=['GET', 'POST'])
    def contact():
        if request.method == 'POST':
            email = request.form.get('email')
            subject = request.form.get('subject') or "Contact Form"
            message = request.form.get('message')

            # Validar campos básicos
            if not email or not message:
                flash("Email and message are required.", "danger")
                return redirect(url_for('contact'))

            # Lógica de envío de correo (idéntica a la que ya tienes en request_account)
            try:
                # Construir el mensaje
                msg_body = f"Message from Contact Form:\n\nEmail: {email}\nSubject: {subject}\nMessage: {message}"
                msg = MIMEText(msg_body)
                msg['Subject'] = subject
                msg['From'] = SMTP_USERNAME  # El remitente que tienes configurado
                msg['To'] = SMTP_USERNAME    # El correo que recibirá el mensaje

                # Conexión SMTP (mismo patrón que request_account)
                if SMTP_USE_SSL:
                    with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) as server:
                        server.login(SMTP_USERNAME, SMTP_PASSWORD)
                        server.send_message(msg)
                else:
                    with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
                        if SMTP_USE_TLS:
                            server.starttls()
                        server.login(SMTP_USERNAME, SMTP_PASSWORD)
                        server.send_message(msg)

                flash("Your message has been sent!", "success")
                return redirect(url_for('contact'))

            except smtplib.SMTPException as smtp_error:
                flash(f"SMTP error: {smtp_error}", "danger")
                return redirect(url_for('contact'))
            except Exception as e:
                flash(f"An error occurred: {e}", "danger")
                return redirect(url_for('contact'))

        # Si es GET, renderizar plantilla con el formulario
        return render_template('contact.html')

    @app.route('/api/paste/<int:id>', methods=['PUT'])
    @jwt_required
    def update_paste_file(id):
        paste = Paste.query.get_or_404(id)
        user = request.user  # Usuario autenticado

        # Verificar permisos
        if not paste.has_edit_permission(user):
            return jsonify({"error": "No permission to edit this paste."}), 403

        data = request.json or {}
        new_content = data.get('content')
        if not new_content:
            return jsonify({"error": "Missing 'content' in JSON"}), 400

        file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
        if not os.path.exists(file_path):
            return jsonify({"error": "File not found"}), 404

        try:
            # 1. Eliminar de Elasticsearch (opcional)
            delete_paste_from_index(paste)

            # 2. Guardar contenido en el archivo
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(new_content)

            # 3. Actualizar la fecha de edición
            paste.last_edited_at = datetime.utcnow()
            db.session.commit()

            # 4. Indexar de nuevo
            index_paste(paste)

            return jsonify({"message": "Paste updated successfully"}), 200

        except Exception as e:
            db.session.rollback()
            return jsonify({"error": str(e)}), 500

    @app.route('/api/users', methods=['GET'])
    @jwt_required
    def list_users():
        try:
            users = User.query.with_entities(User.username).all()
            usernames = [user.username for user in users]
            return jsonify({"users": usernames}), 200
        except Exception as e:
            app.logger.error(f"Error retrieving users: {e}")
            return jsonify({"error": "An unexpected error occurred."}), 500


    @app.route('/api/users/search', methods=['GET'])
    @login_required  # Asegura que solo usuarios autenticados puedan acceder
    def search_users():
        query = request.args.get('q', '').strip()
        logging.info(f"Search query received: {query}")
        if not query:
            return jsonify([])  # Retorna una lista vacía si la consulta está vacía

        # Obtener todos los nombres de usuario desde la base de datos
        all_users = [user.username for user in User.query.all()]
        logging.info(f"Total users fetched: {len(all_users)}")

        # Realizar una búsqueda difusa utilizando RapidFuzz
        matches = process.extract(query, all_users, scorer=fuzz.WRatio, limit=10)
        logging.info(f"Matches found: {matches}")

        # Definir un umbral para filtrar resultados poco relevantes
        threshold = 60  # Puedes ajustar este valor según tus necesidades

        # Filtrar los resultados que superen el umbral
        matched_users = [username for username, score, _ in matches if score >= threshold]
        logging.info(f"Matched users after threshold: {matched_users}")

        # Crear una lista de diccionarios para retornar
        user_list = [{'username': username} for username in matched_users]

        return jsonify(user_list)

    @app.route('/paste/<int:paste_id>/save-edited', methods=['POST'])
    @login_required
    def save_edited_image(paste_id):
        paste = Paste.query.get(paste_id)
        if not paste or paste.owner_id != current_user.id:
            return jsonify({"error": "Unauthorized"}), 403

        data = request.get_json()
        if not data or "image" not in data:
            return jsonify({"error": "Invalid data"}), 400

        image_data = data["image"].split(",")[1]  # Extraer datos base64
        file_path = os.path.join(UPLOAD_FOLDER, paste.filename)

        with open(file_path, "wb") as f:
            f.write(base64.b64decode(image_data))

        return jsonify({"message": "Image updated successfully!"}), 200

    @app.route('/paste/<int:paste_id>/remove_gps', methods=['POST'])
    @login_required
    def remove_gps_metadata(paste_id):
        paste = Paste.query.get(paste_id)
        if not paste or not paste.filename:
            return jsonify({"error": "Paste not found"}), 404

        if paste.owner_id != current_user.id:
            return jsonify({"error": "You do not have permission to modify this file"}), 403

        file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
        if not os.path.exists(file_path):
            return jsonify({"error": "File not found"}), 404

        try:
            img = Image.open(file_path)

            # Obtener los metadatos EXIF
            exif_data = img._getexif()
            orientation_tag = None

            if exif_data:
                for tag, value in exif_data.items():
                    tag_name = ExifTags.TAGS.get(tag, tag)
                    if tag_name == "Orientation":
                        orientation_tag = value
                        break

            # Corregir la orientación antes de eliminar EXIF
            if orientation_tag:
                if orientation_tag == 3:
                    img = img.rotate(180, expand=True)
                elif orientation_tag == 6:
                    img = img.rotate(270, expand=True)
                elif orientation_tag == 8:
                    img = img.rotate(90, expand=True)

            # Convertir imagen a RGB para evitar problemas de formato
            img = img.convert("RGB")

            # Crear una nueva imagen sin metadatos
            temp_file_path = file_path.replace(".jpg", "_clean.jpg")
            img.save(temp_file_path, format="JPEG")

            # Reemplazar la imagen original con la nueva sin EXIF
            os.replace(temp_file_path, file_path)

            print(f"[INFO] Se eliminaron los metadatos EXIF y se corrigió la orientación de {file_path}")

            return jsonify({"success": True}), 200
        except Exception as e:
            print(f"[ERROR] No se pudo eliminar EXIF de {file_path}: {e}")
            return jsonify({"error": f"Error removing GPS: {e}"}), 500


    @app.route('/api/removegps', methods=['POST'])
    @jwt_required
    def api_remove_gps_metadata():
        data = request.json
        paste_id = data.get("paste_id")

        if not paste_id:
            return jsonify({"error": "Missing paste_id"}), 400

        paste = Paste.query.get(paste_id)
        if not paste or not paste.filename:
            return jsonify({"error": "Paste not found"}), 404

        # Verificar si el usuario autenticado es el propietario del archivo
        if paste.owner_id != request.user.id:
            return jsonify({"error": "You do not have permission to modify this file"}), 403

        file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
        if not os.path.exists(file_path):
            return jsonify({"error": "File not found"}), 404

        try:
            img = Image.open(file_path)

            # Leer los metadatos EXIF y corregir la orientación
            exif_data = img._getexif()
            orientation_tag = None

            if exif_data:
                for tag, value in exif_data.items():
                    tag_name = ExifTags.TAGS.get(tag, tag)
                    if tag_name == "Orientation":
                        orientation_tag = value
                        break

            if orientation_tag:
                if orientation_tag == 3:
                    img = img.rotate(180, expand=True)
                elif orientation_tag == 6:
                    img = img.rotate(270, expand=True)
                elif orientation_tag == 8:
                    img = img.rotate(90, expand=True)

            # Convertir a RGB para evitar problemas de formato
            img = img.convert("RGB")

            # Guardar la imagen sin EXIF en un archivo temporal
            temp_file_path = file_path.replace(".jpg", "_clean.jpg")
            img.save(temp_file_path, format="JPEG")

            # Reemplazar la imagen original con la nueva sin metadatos
            os.replace(temp_file_path, file_path)

            print(f"[INFO] API: Metadatos EXIF eliminados de {file_path}")

            return jsonify({"success": True}), 200
        except Exception as e:
            print(f"[ERROR] API: No se pudo eliminar EXIF de {file_path}: {e}")
            return jsonify({"error": f"Error removing GPS: {e}"}), 500


    @app.route('/user/paste/stats', methods=['GET'])
    @login_required
    def user_paste_stats():
        text_count = Paste.query.filter(
            Paste.owner_id == current_user.id,
            Paste.content_type.like('text%')
        ).count()

        file_count = Paste.query.filter(
            Paste.owner_id == current_user.id,
            Paste.content_type.in_([
                'application/zip', 'application/x-tar', 
                'application/gzip', 'application/x-bzip2',
                'application/x-7z-compressed', 'application/x-rar-compressed'
            ])
        ).count()

        media_count = Paste.query.filter(
            Paste.owner_id == current_user.id,
            Paste.content_type.like('image%') |
            Paste.content_type.like('video%') |
            Paste.content_type.like('audio%')
        ).count()

        return jsonify({
            "languageLabels": ["Text", "Files", "Media"],
            "textCounts": [text_count],
            "fileCounts": [file_count],
            "mediaCounts": [media_count]
        })

    @app.route('/paste/<int:paste_id>/settings', methods=['GET', 'POST'])
    @login_required
    def paste_settings(paste_id):
        paste = Paste.query.get_or_404(paste_id)

        if paste.owner_id != current_user.id:
            flash('You do not have permission to modify this paste.', 'danger')
            return redirect(url_for('user_dashboard'))

        if request.method == 'POST':
            paste.title = request.form.get('title') or None
            paste.language = request.form.get('language') or None
            paste.editable = 'editable' in request.form
            paste.private = 'private' in request.form
            expires = request.form.get('expires_at')
            paste.expires_at = datetime.strptime(expires, "%Y-%m-%d") if expires else None
            db.session.commit()
            flash('Paste settings updated.', 'success')
            return redirect(url_for('paste_settings', paste_id=paste.id))

        # 👇 Pasamos la lista de lenguajes al template
        pygments_languages = get_readable_languages()

        return render_template(
            'paste_settings.html',
            paste=paste,
            pygments_languages=pygments_languages
        )


    @app.route('/generate_code_from_paste/<int:id>', methods=['GET'])
    def generate_code_from_paste(id):
        """
        Generates code improvement suggestions from the current paste using DeepSeek-Chat with streaming.
        """
        paste = Paste.query.get(id)
        if not paste:
            return jsonify({"error": "Paste not found"}), 404

        # Read paste content
        file_path = os.path.join(UPLOAD_FOLDER, paste.filename)
        if not os.path.exists(file_path):
            return jsonify({"error": "File not found"}), 404

        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            paste_content = f.read()

        # Limit content size to avoid exceeding token limits
        truncated_content = paste_content[:1500]

        # Remove special characters
        cleaned_content = re.sub(r"[^\x00-\x7F]+", "", truncated_content).strip()

        # Prompt in English to ensure DeepSeek responds in English
        messages = [
            {"role": "system", "content": "You are an AI coding assistant. Your task is to improve, optimize, and explain the following code."},
            {"role": "user", "content": f"Analyze and improve this code:\n\n```{cleaned_content}```\n\nProvide a well-formatted output with explanations if necessary."}
        ]

        payload = {
            "model": "deepseek-chat",
            "messages": messages,
            "temperature": 0.3,
            "max_tokens": 3000,
            "stream": True  # Enable streaming mode
        }

        # ✅ Pasamos `payload` como argumento a `stream_response()`
        return Response(stream_with_context(stream_response(payload)), content_type="text/plain; charset=utf-8")
