# -*- coding: utf-8 -*-
import os
import json
import datetime
import uuid
import re
import sys
import shutil  # Für save_data Fallback
import mimetypes  # Für korrekte Mime-Type Erkennung
from functools import wraps
# Versuche Imports und fange Fehler früh ab
try:
    from flask import (Flask, render_template, request, redirect, url_for,
                    send_from_directory, jsonify, abort, session, flash,
                    Response, send_file, make_response)
    from werkzeug.utils import secure_filename
    from PIL import Image, UnidentifiedImageError
    print("DEBUG: Flask and PIL imported successfully.")
except ImportError as e:
    print(f"FATAL IMPORT ERROR: {e}")
    print("Please ensure Flask and Pillow are installed in the virtual environment:")
    print("venv/bin/python -m pip install Flask Pillow")
    sys.exit(1)

# --- Konfiguration ---
print("DEBUG: Setting configuration...")
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads')
DATA_FOLDER = os.path.join(BASE_DIR, 'data')
THUMBNAIL_FOLDER = os.path.join(UPLOAD_FOLDER, 'thumbnails')
STATIC_IMG_FOLDER = os.path.join(BASE_DIR, 'static', 'img')

CHAT_FILE = os.path.join(DATA_FOLDER, 'chat_messages.json')
CALENDAR_FILE = os.path.join(DATA_FOLDER, 'calendar_events.json')
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp',
                    'mp4', 'mov', 'avi', 'mkv', 'webm', 'mp3', 'wav', 'ogg',
                    'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'zip', 'rar'}
THUMBNAIL_SIZE = (120, 120)

ADMIN_USERNAME = "admin"
ADMIN_PASSWORD = "0911"
ACTION_PASSWORD = "0911"
PAGE_TITLE = "Vantastic uploads"
print("DEBUG: Configuration set.")

# --- Flask App Initialisierung ---
try:
    print("DEBUG: Creating Flask app instance...")
    app = Flask(__name__)
    app.secret_key = 'aendern-sie-diesen-geheimen-schluessel-final-final-final!'  # Bitte ändern!
    app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
    # Sicherstellen dass MIME-Types korrekt erkannt werden
    mimetypes.init()
    # PDF-MIME-Type explicite hinzufügen, falls nicht vorhanden
    if '.pdf' not in mimetypes.types_map:
        mimetypes.add_type('application/pdf', '.pdf')
    # Video & Audio MIME-Types hinzufügen
    mimetypes.add_type('video/mp4', '.mp4')
    mimetypes.add_type('video/webm', '.webm')
    mimetypes.add_type('audio/mpeg', '.mp3')
    mimetypes.add_type('audio/ogg', '.ogg')
    print("DEBUG: Flask app instance created.")
except Exception as e:
    print(f"FATAL ERROR creating Flask app: {e}")
    sys.exit(1)

# --- Ordner-Setup ---
print("--- Server Initializing (Folder Check) ---")
try:
    for folder in [UPLOAD_FOLDER, DATA_FOLDER, THUMBNAIL_FOLDER, STATIC_IMG_FOLDER]:
        os.makedirs(folder, exist_ok=True)
    test_file = os.path.join(DATA_FOLDER, '.permission_test')
    with open(test_file, 'w') as f: f.write('test')
    os.remove(test_file)
    print(f"INFO: Data and Upload folders accessible at {BASE_DIR}")
except OSError as e:
    print(f"FATAL ERROR creating/accessing folders: {e}")
    print(f"Please check permissions for user UID {os.geteuid()} on path '{BASE_DIR}' and subdirectories.")
    sys.exit(1)
except Exception as e:
    print(f"FATAL UNEXPECTED ERROR during setup: {e}")
    sys.exit(1)
print("DEBUG: Folder check finished.")

# --- Authentifizierung & Decorator ---
print("DEBUG: Defining auth functions...")
def check_action_password(password):
    """Prüft das Aktionspasswort."""
    return isinstance(password, str) and password == ACTION_PASSWORD

def login_required(f):
    """Decorator für Admin-Login-Pflicht."""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'admin_logged_in' not in session:
            flash('Bitte einloggen, um auf diese Seite zuzugreifen.', 'warning')
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function
print("DEBUG: Auth functions defined.")

# --- Hilfsfunktionen (KORRIGIERTE FORMATIERUNG) ---
print("DEBUG: Defining helper functions...")
def allowed_file(filename):
    """Prüft, ob die Dateiendung erlaubt ist."""
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def get_file_size(filepath):
    """Gibt die Dateigröße formatiert zurück."""
    try:
        size = os.path.getsize(filepath)
        if size < 1024:
            return f"{size} B"
        elif size < 1024**2:
            return f"{size/1024:.1f} KB"
        elif size < 1024**3:
            return f"{size/1024**2:.1f} MB"
        else:
            return f"{size/1024**3:.1f} GB"
    except OSError:
        return "N/A"

def get_mime_type(filepath):
    """Ermittelt den MIME-Type einer Datei."""
    try:
        mime_type, _ = mimetypes.guess_type(filepath)
        if mime_type:
            return mime_type
        # Fallback für unbekannte Dateitypen
        ext = os.path.splitext(filepath)[1].lower()
        if ext == '.pdf':
            return 'application/pdf'
        elif ext in ['.jpg', '.jpeg']:
            return 'image/jpeg'
        elif ext == '.png':
            return 'image/png'
        elif ext == '.gif':
            return 'image/gif'
        # Weitere Fallbacks für häufige Typen
        elif ext in ['.mp4']:
            return 'video/mp4'
        elif ext in ['.webm']:
            return 'video/webm'
        elif ext in ['.mp3']:
            return 'audio/mpeg'
        elif ext in ['.wav']:
            return 'audio/wav'
        elif ext in ['.ogg']:
            return 'audio/ogg'
        # Generischer Fallback
        return 'application/octet-stream'
    except Exception as e:
        print(f"Error determining mime type for {filepath}: {e}")
        return 'application/octet-stream'

def is_image_file(filename):
    """Prüft, ob die Datei ein Bild ist."""
    ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
    return ext in ('png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp')

def is_video_file(filename):
    """Prüft, ob die Datei ein Video ist."""
    ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
    return ext in ('mp4', 'mov', 'avi', 'mkv', 'webm')

def is_audio_file(filename):
    """Prüft, ob die Datei eine Audiodatei ist."""
    ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
    return ext in ('mp3', 'wav', 'ogg')

def is_pdf_file(filename):
    """Prüft, ob die Datei ein PDF ist."""
    ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
    return ext == 'pdf'

def create_thumbnail(filepath, filename):
    """Erstellt Thumbnail und gibt URL zurück oder None bei Fehler."""
    thumb_path = os.path.join(THUMBNAIL_FOLDER, filename)
    try:
        should_create = not os.path.exists(thumb_path)
        if not should_create:
            try:
                should_create = os.path.getmtime(filepath) > os.path.getmtime(thumb_path)
            except OSError:
                should_create = True  # Neu erstellen, wenn mtime nicht lesbar

        if should_create:
            with Image.open(filepath) as img:
                img.thumbnail(THUMBNAIL_SIZE, Image.Resampling.LANCZOS)
                img_to_save = img
                if img.mode not in ('RGB', 'L'):
                    if img.mode == 'RGBA' or 'transparency' in img.info:
                        background = Image.new("RGB", img.size, (255, 255, 255))
                        mask = None
                        try:
                            mask = img.split()[3]
                        except IndexError:
                            pass
                        background.paste(img, (0, 0), mask=mask)
                        img_to_save = background
                    else:
                        img_to_save = img.convert('RGB')
                img_to_save.save(thumb_path, quality=85, optimize=True)
                print(f"Thumbnail created/updated: {thumb_path}")

        # Gib URL zurück, wenn Thumbnail existiert
        if os.path.exists(thumb_path):
            return url_for('uploaded_thumbnail', filename=filename)
        else:
            print(f"Thumbnail check failed after potential save: {thumb_path}")
            return None
    except UnidentifiedImageError:
        print(f"Thumbnail Error ({filename}): Cannot identify image file.")
        return None
    except Exception as e:
        print(f"Thumbnail Error ({filename}): {e}")
        if os.path.exists(thumb_path):
            try:
                os.remove(thumb_path)
            except OSError as e:
                print(f"ERROR: Could not remove corrupted thumbnail {thumb_path}: {e}")
        return None

def get_uploaded_files():
    """Holt Dateien, sortiert nach Änderungsdatum (neueste zuerst)."""
    files_list = []
    try:
        filenames = os.listdir(UPLOAD_FOLDER)
    except OSError as e:
        print(f"Error listing {UPLOAD_FOLDER}: {e}")
        return []

    for filename in filenames:
        # Überspringe Thumbnails-Ordner und versteckte Dateien
        if filename == 'thumbnails' or filename.startswith('.'):
            continue

        filepath = os.path.join(UPLOAD_FOLDER, filename)
        if os.path.isfile(filepath):
            try:
                mtime = os.path.getmtime(filepath)
                thumbnail_url = create_thumbnail(filepath, filename) if is_image_file(filename) else None
                if thumbnail_url is None:
                    thumbnail_url = url_for('static', filename='img/generic_file.png')

                ext = filename.rsplit('.', 1)[1].lower() if '.' in filename else ''
                mime_type = get_mime_type(filepath)

                files_list.append({
                    'name': filename,
                    'size': get_file_size(filepath),
                    'url': url_for('direct_file', filename=filename),  # Direkte Datei-URL
                    'preview_url': url_for('file_preview', filename=filename),  # Vorschau-URL
                    'thumbnail_url': thumbnail_url,
                    'mtime': mtime,
                    'mime_type': mime_type,
                    'is_image': is_image_file(filename),
                    'is_pdf': is_pdf_file(filename),
                    'is_video': is_video_file(filename),
                    'is_audio': is_audio_file(filename)
                })
            except OSError as e:
                print(f"Error processing file props for {filename}: {e}")

    files_list.sort(key=lambda x: x['mtime'], reverse=True)
    return files_list

def load_data(filepath):
    """Lädt JSON sicher und gibt immer eine Liste zurück."""
    data = []
    print(f"LOAD: Attempting from: {filepath}")
    try:
        if os.path.exists(filepath):
            if not os.access(filepath, os.R_OK):
                print(f"LOAD ERROR: Cannot read (permissions): {filepath}")
                return []
            # Lese nur, wenn nicht leer
            if os.path.getsize(filepath) > 0:
                with open(filepath, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                print(f"LOAD: Success, loaded {len(data)} items.")
            else:
                print(f"LOAD Warning: File exists but is empty: {filepath}")
                # Erstelle leere JSON-Datei mit einem leeren Array für neue Installationen
                with open(filepath, 'w', encoding='utf-8') as f:
                    json.dump([], f)
        else:
            print(f"LOAD Info: File not found: {filepath}")
            # Erstelle leere JSON-Datei mit einem leeren Array für neue Installationen
            os.makedirs(os.path.dirname(filepath), exist_ok=True)
            with open(filepath, 'w', encoding='utf-8') as f:
                json.dump([], f)
    except json.JSONDecodeError as e:
        print(f"LOAD ERROR: Invalid JSON in {filepath}: {e}.")
        backup_filepath = filepath + ".bak"
        if os.path.exists(backup_filepath):
            print(f"LOAD INFO: Trying to load from backup: {backup_filepath}")
            return load_data(backup_filepath)  # Versuche Backup
        else:
            # Überschreibe die fehlerhafte Datei mit einer leeren Liste
            with open(filepath, 'w', encoding='utf-8') as f:
                json.dump([], f)
    except (IOError, OSError) as e:
        print(f"LOAD ERROR (IO/OS) from {filepath}: {e}.")
    except Exception as e:
        print(f"LOAD UNEXPECTED ERROR from {filepath}: {e}.")

    # Sicherstellen, dass es eine Liste ist
    if not isinstance(data, list):
        print(f"LOAD WARNING: Loaded data is not list (type: {type(data)}). Returning empty.")
        return []
    return data

def save_data(filepath, data):
    """Speichert JSON sicher mit Backup und atomarem Verschieben."""
    print(f"SAVE: Attempting to save {len(data)} items to: {filepath}")
    if not isinstance(data, list):
        print(f"SAVE ERROR: Data is not a list!")
        return False
    dirpath = os.path.dirname(filepath)
    os.makedirs(dirpath, exist_ok=True)
    if not os.access(dirpath, os.W_OK):
        print(f"SAVE ERROR: Cannot write to directory (permissions): {dirpath}")
        return False
    tmp = filepath + ".tmp"
    bak = filepath + ".bak"
    try:
        with open(tmp, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        if os.path.exists(filepath):
            os.replace(filepath, bak)  # Atomar wenn möglich
            print(f"SAVE: Created backup: {bak}")
        os.replace(tmp, filepath)  # Atomar wenn möglich
        if os.path.exists(bak):
            try: os.remove(bak)
            except OSError as e: print(f"SAVE WARNING: Could not remove backup {bak}: {e}")
        print(f"SAVE: Success.")
        return True
    except Exception as e:
        print(f"SAVE ERROR to {filepath}: {e}")
    if os.path.exists(tmp):
        try:
            os.remove(tmp)
        except OSError:
            pass
        if os.path.exists(bak):
            try:
                os.replace(bak, filepath)
                print(f"SAVE: Restored backup {bak}")
            except Exception as re:
                print(f"SAVE CRITICAL: No restore {bak}: {re}")
        return False

def get_color_for_nickname(nickname):
    """Generiert eine Farbe basierend auf dem Nickname."""
    if not nickname:
        return "#3498db"  # Standard-Farbe, falls kein Nickname
        
    hash_code = 0
    for c in nickname:
        hash_code = ord(c) + ((hash_code << 5) - hash_code)
    hash_code = abs(hash_code)

    colors=['#1abc9c','#2ecc71','#3498db','#9b59b6','#34495e','#f1c40f','#e67e22','#e74c3c','#16a085','#27ae60','#2980b9','#8e44ad','#f39c12','#d35400','#c0392b']
    return colors[hash_code % len(colors)]

def linkify(text):
    """Wandelt URLs in klickbare Links um (ohne HTML-Escaping)."""
    if not text:
        return ""
    
    # Escaping sollte NICHT hier geschehen, sondern vor dem Aufruf dieser Funktion
    # Bei doppeltem Escaping werden Links nicht richtig angezeigt
    
    # Erkennt http/https und optional www. am Anfang
    url_pattern = re.compile(r'((?:https?://|www\.)[^\s/$.?#].[^\s<>]*[^\s<>.,)\]])')  # Verbessertes Pattern
    def replace_url(match):
        url = match.group(1)
        href = url if url.startswith('http') else 'http://' + url
        return f'<a href="{href}" target="_blank" rel="noopener noreferrer">{url}</a>'
    
    linked_text = url_pattern.sub(replace_url, text)
    return linked_text
print("DEBUG: Helper functions defined.")

# --- Context Processor ---
print("DEBUG: Defining context processor...")
@app.context_processor
def inject_common_data():
    """Übergibt gemeinsame Daten an alle Templates."""
    return {'now': datetime.datetime.now(), 'page_title': PAGE_TITLE}
print("DEBUG: Context processor defined.")

# --- Haupt-Routen ---
print("DEBUG: Defining main routes...")
@app.route('/')
def index():
    files = get_uploaded_files()
    return render_template('index.html', files=files)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if 'admin_logged_in' in session: return redirect(url_for('verwaltung'))
    if request.method == 'POST':
        un = request.form.get('username'); pw = request.form.get('password'); print(f"Login attempt: User='{un}'")
        if un == ADMIN_USERNAME and pw == ADMIN_PASSWORD:
            session['admin_logged_in'] = True; session.permanent = True; app.permanent_session_lifetime = datetime.timedelta(days=7); flash('Eingeloggt!', 'success'); next_url = request.args.get('next')
            return redirect(next_url or url_for('verwaltung'))
        else: flash('Falscher Benutzername oder Passwort.', 'danger')
    return render_template('login.html')

@app.route('/logout')
def logout():
    session.pop('admin_logged_in', None); flash('Ausgeloggt.', 'info'); return redirect(url_for('index'))

@app.route('/verwaltung')
@login_required
def verwaltung():
    files = get_uploaded_files(); return render_template('verwaltung.html', files=files)

@app.route('/chat')
def chat():
    """Chat-Seite rendern."""
    try:
        # Stelle sicher, dass die Chat-Datei existiert
        if not os.path.exists(CHAT_FILE):
            os.makedirs(os.path.dirname(CHAT_FILE), exist_ok=True)
            with open(CHAT_FILE, 'w', encoding='utf-8') as f:
                json.dump([], f)
        
        # Hole ein paar Chat-Nachrichten, um sicherzustellen, dass der Zugriff funktioniert
        msgs = load_data(CHAT_FILE)
        print(f"Chat geladen: {len(msgs)} Nachrichten gefunden")
        
        return render_template('chat.html')
    except Exception as e:
        print(f"ERROR in chat route: {e}")
        flash(f"Fehler beim Laden des Chats: {str(e)}", "danger")
        return render_template('chat.html', error=True)

@app.route('/kalender')
def kalender():
    # Stelle sicher, dass die Kalender-Datei existiert
    if not os.path.exists(CALENDAR_FILE):
        os.makedirs(os.path.dirname(CALENDAR_FILE), exist_ok=True)
        with open(CALENDAR_FILE, 'w', encoding='utf-8') as f:
            json.dump([], f)
    return render_template('kalender.html')
print("DEBUG: Main routes defined.")

# --- Datei-Operationen Routen ---
print("DEBUG: Defining file operation routes...")
@app.route('/upload', methods=['POST'])
def upload_file():
    """Verarbeitet Datei-Uploads (auch mehrere)."""
    password = request.form.get('password')
    print(f"UPLOAD: Received='{password}'({type(password).__name__}), Expected='{ACTION_PASSWORD}'({type(ACTION_PASSWORD).__name__}), Match={check_action_password(password)}")
    if not check_action_password(password): return jsonify({"success": False, "error": "Falsches Aktionspasswort!"}), 403
    uploaded_files = request.files.getlist("file")
    if not uploaded_files or all(f.filename == '' for f in uploaded_files): return jsonify({"success": False, "error": "Keine Dateien ausgewählt!"}), 400
    results = []; errors = []; saved_filenames = []
    for file in uploaded_files:
        if file and allowed_file(file.filename):
            original_filename = file.filename; filename = secure_filename(original_filename); filepath = os.path.join(UPLOAD_FOLDER, filename)
            counter = 1
            while os.path.exists(filepath): name, ext = os.path.splitext(secure_filename(original_filename)); filename = f"{name}_{counter}{ext}"; filepath = os.path.join(UPLOAD_FOLDER, filename); counter += 1
            try:
                file.save(filepath); print(f"File saved: {filepath}")
                if is_image_file(filename):  # Nur für Bilder Thumbnails erstellen
                    create_thumbnail(filepath, filename)
                results.append({"filename": filename, "success": True}); saved_filenames.append(filename)
            except Exception as e: print(f"ERROR saving {filename} (orig: {original_filename}): {e}"); errors.append({"filename": original_filename, "success": False, "error": "Speicherfehler."}); [os.remove(filepath) for _ in [''] if os.path.exists(filepath)]
        elif file and file.filename != '': errors.append({"filename": file.filename, "success": False, "error": "Typ nicht erlaubt."})
    if not errors: return jsonify({"success": True, "message": f"{len(saved_filenames)} Datei(en) hochgeladen."})
    elif not results: return jsonify({"success": False, "error": f"Fehler bei allen {len(errors)} Dateien.", "details": errors}), 400
    else: return jsonify({"success": False, "message": f"{len(results)} OK, {len(errors)} Fehler.", "details": errors, "saved": saved_filenames}), 207

@app.route('/direct-file/<path:filename>')
def direct_file(filename):
    """Liefert Dateien direkt zum Herunterladen."""
    try:
        # Schützen vor path traversal
        secure_name = secure_filename(filename)
        if secure_name != filename:
            abort(404)

        filepath = os.path.join(UPLOAD_FOLDER, filename)
        if not os.path.exists(filepath) or not os.path.isfile(filepath):
            abort(404)

        # MIME-Type ermitteln
        mime_type = get_mime_type(filepath)

        # Datei direkt mit korrektem MIME-Type senden
        response = send_file(
            filepath,
            mimetype=mime_type,
            as_attachment=False,
            download_name=filename
        )

        # Sicherheitsheader hinzufügen
        response.headers['X-Content-Type-Options'] = 'nosniff'
        return response
    except Exception as e:
        print(f"Error sending file {filename}: {e}")
        abort(500)

@app.route('/uploads/thumbnails/<path:filename>')
def uploaded_thumbnail(filename):
    """Liefert Thumbnails aus."""
    try:
        # Schützen vor path traversal
        secure_name = secure_filename(filename)
        if secure_name != filename:
            abort(404)

        thumb_path = os.path.join(THUMBNAIL_FOLDER, filename)
        if not os.path.exists(thumb_path):
            fallback_path = os.path.join(STATIC_IMG_FOLDER, 'generic_file.png')
            return send_file(fallback_path, mimetype='image/png')

        # MIME-Type für Thumbnail ermitteln
        mime_type = get_mime_type(thumb_path)

        # Thumbnail direkt senden
        response = send_file(
            thumb_path,
            mimetype=mime_type,
            as_attachment=False
        )

        # Sicherheitsheader hinzufügen
        response.headers['X-Content-Type-Options'] = 'nosniff'
        return response
    except Exception as e:
        print(f"Error sending thumbnail {filename}: {e}")
        abort(500)

@app.route('/file-preview/<path:filename>')
def file_preview(filename):
    """Zeigt eine Dateivorschau in einer eigenständigen Seite an."""
    try:
        # Schützen vor path traversal
        secure_name = secure_filename(filename)
        if secure_name != filename:
            abort(404)

        filepath = os.path.join(UPLOAD_FOLDER, filename)
        if not os.path.exists(filepath) or not os.path.isfile(filepath):
            abort(404)

        # Dateieigenschaften sammeln
        mime_type = get_mime_type(filepath)
        size = get_file_size(filepath)
        mtime = datetime.datetime.fromtimestamp(os.path.getmtime(filepath)).isoformat()

        file_data = {
            'name': filename,
            'url': url_for('direct_file', filename=filename),
            'size': size,
            'modified': mtime,
            'mime_type': mime_type,
            'is_image': is_image_file(filename),
            'is_pdf': is_pdf_file(filename),
            'is_video': is_video_file(filename),
            'is_audio': is_audio_file(filename)
        }

        print(f"File preview generated for: {filename}, mime: {mime_type}")
        # Eigenständiges Template für Dateivorschau rendern
        return render_template('file_preview.html', file=file_data)

    except Exception as e:
        print(f"Error generating preview for {filename}: {e}")
        flash(f"Fehler bei der Vorschau: {str(e)}", "danger")
        return redirect(url_for('index'))

@app.route('/preview-api/<path:filename>')
def preview_api(filename):
    """Liefert Dateiinformationen im JSON-Format für JavaScript-Vorschau."""
    try:
        # Schützen vor path traversal
        secure_name = secure_filename(filename)
        if secure_name != filename:
            return jsonify({"success": False, "error": "Ungültiger Dateiname"}), 400

        filepath = os.path.join(UPLOAD_FOLDER, filename)
        if not os.path.exists(filepath) or not os.path.isfile(filepath):
            return jsonify({"success": False, "error": "Datei nicht gefunden"}), 404

        # Dateieigenschaften sammeln
        mime_type = get_mime_type(filepath)
        size = get_file_size(filepath)
        mtime = datetime.datetime.fromtimestamp(os.path.getmtime(filepath)).isoformat()

        return jsonify({
            "success": True,
            "filename": filename,
            "mime_type": mime_type,
            "size": size,
            "modified": mtime,
            "url": url_for('direct_file', filename=filename),
            "preview_url": url_for('file_preview', filename=filename),
            "is_image": is_image_file(filename),
            "is_pdf": is_pdf_file(filename),
            "is_video": is_video_file(filename),
            "is_audio": is_audio_file(filename)
        })

    except Exception as e:
        print(f"Error generating API data for {filename}: {e}")
        return jsonify({"success": False, "error": str(e)}), 500

@app.route('/delete_file/<path:filename>', methods=['POST'])
@login_required
def delete_file(filename):
    """Löscht eine Datei und ihr Thumbnail."""
    sfn = secure_filename(filename)
    if sfn != filename: return jsonify({"success": False, "error": "Ungültiger Dateiname."}), 400
    try:
        fp = os.path.join(UPLOAD_FOLDER, sfn)
        tp = os.path.join(THUMBNAIL_FOLDER, sfn)
        fe = False
        if os.path.exists(fp) and os.path.isfile(fp):
            os.remove(fp)
            fe = True
            print(f"Deleted file: {fp}")
        if os.path.exists(tp) and os.path.isfile(tp):
            try:
                os.remove(tp)
                print(f"Deleted thumbnail: {tp}")
            except OSError as e:
                print(f"Warning: Could not remove thumbnail {tp}: {e}")
        if fe:
            flash(f'Datei "{sfn}" gelöscht.', 'success')
            return jsonify({"success": True})
        else:
            print(f"Delete Error: File not found: {fp}")
            return jsonify({"success": False, "error": "Datei nicht gefunden"}), 404
    except OSError as e:
        print(f"Error deleting {sfn}: {e}")
        return jsonify({"success": False, "error": "Fehler beim Löschen."}), 500
print("DEBUG: File operation routes defined.")

# --- API Endpunkte ---
print("DEBUG: Defining API endpoints...")
@app.route('/api/chat/messages', methods=['GET'])
def get_chat_messages():
    """Liefert alle Chat-Nachrichten."""
    try:
        msgs = load_data(CHAT_FILE)
        processed_msgs = []
        
        for m in msgs:
            if isinstance(m, dict) and 'nickname' in m:
                message_copy = m.copy()  # Kopie erstellen, um das Original nicht zu verändern
                message_copy['color'] = get_color_for_nickname(message_copy['nickname'])
                
                # Wichtig: Nur die original gespeicherte Nachricht übernehmen, ohne erneutes Linkify
                # Die Linkify-Funktion wurde bereits beim Speichern angewendet
                
                processed_msgs.append(message_copy)
            else:
                # Für ungültige Nachrichten einen Fallback bereitstellen
                if isinstance(m, dict):
                    processed_msgs.append(m)
        
        return jsonify(processed_msgs)
    except Exception as e:
        print(f"ERROR in get_chat_messages: {e}")
        return jsonify([]), 500

@app.route('/api/chat/messages', methods=['POST'])
def post_chat_message():
    """Speichert eine neue Chat-Nachricht."""
    try:
        d = request.json
        pw = d.get('password')
        nn = d.get('nickname')
        mt = d.get('message')
        print(f"CHAT POST: Received password='{pw}', Match={check_action_password(pw)}")
        
        if not check_action_password(pw):
            return jsonify({"success": False, "error": "Ungültiges Aktionspasswort!"}), 403
        if not nn or not mt:
            return jsonify({"success": False, "error": "Nickname/Nachricht erforderlich!"}), 400
        
        msgs = load_data(CHAT_FILE)
        cn = nn.strip()
        
        # Textmessage bereinigen (HTML-Escaping)
        clean_message = mt.strip()
        clean_message = clean_message.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
        
        # Links umwandeln
        message_with_links = linkify(clean_message)
        
        nm = {
            "id": str(uuid.uuid4()),
            "nickname": cn,
            "message": message_with_links,  # Message mit Links speichern
            "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
            "color": get_color_for_nickname(cn)
        }
        
        msgs.append(nm)
        
        if save_data(CHAT_FILE, msgs):
            return jsonify({"success": True, "message": nm})
        else:
            return jsonify({"success": False, "error": "Speichern fehlgeschlagen."}), 500
    except Exception as e:
        print(f"ERROR in post_chat_message: {e}")
        return jsonify({"success": False, "error": "Interner Serverfehler"}), 500

@app.route('/api/chat/delete/<message_id>', methods=['POST'])
def delete_chat_message(message_id):
    """Löscht eine Chat-Nachricht."""
    d = request.json; pw = d.get('password'); print(f"CHAT DELETE: Received password='{pw}', Match={check_action_password(pw)}")
    if not check_action_password(pw): return jsonify({"success": False, "error": "Ungültiges Aktionspasswort!"}), 403
    msgs = load_data(CHAT_FILE); il = len(msgs); msgs_new = [m for m in msgs if m.get('id') != message_id]
    if len(msgs_new) < il:
        if save_data(CHAT_FILE, msgs_new): return jsonify({"success": True})
        else: return jsonify({"success": False, "error": "Speichern fehlgeschlagen."}), 500
    else: return jsonify({"success": False, "error": "Nachricht nicht gefunden"}), 404

# --- Kalender API ---
@app.route('/api/calendar/events', methods=['GET'])
def get_calendar_events():
    """Liefert Kalenderevents im FullCalendar-Format."""
    events_data = load_data(CALENDAR_FILE); fullcalendar_events = []
    for event in events_data:
        try:
            start_dt_str = None; end_dt_str = event.get('end_date'); all_day = event.get('allDay', False); date_val = event.get('date'); time_val = event.get('time')
            if date_val:
                if not all_day and time_val: start_dt_str = f"{date_val}T{time_val}"; datetime.datetime.fromisoformat(start_dt_str)
                else: start_dt_str = date_val; datetime.date.fromisoformat(start_dt_str); all_day = True;
                if end_dt_str:
                    try: datetime.date.fromisoformat(end_dt_str);
                    except ValueError: end_dt_str = None
            else: continue
            fc_event = {'id': event.get('id'), 'title': event.get('name', '?'), 'start': start_dt_str, 'allDay': all_day, 'extendedProps': {'description': event.get('description', '')}}
            if end_dt_str: fc_event['end'] = end_dt_str
            fullcalendar_events.append(fc_event)
        except(ValueError, TypeError) as e: print(f"Warnung: Invalid Event ID {event.get('id')}: {e}")
    return jsonify(fullcalendar_events)

@app.route('/api/calendar/events', methods=['POST'])
def add_calendar_event():
    """Fügt einen neuen Kalendereintrag hinzu."""
    try:
        data = request.json
        print(f"Kalender-Event POST: Empfangene Daten: {data}")
        
        password = data.get('password')
        title = data.get('title')
        description = data.get('description', '')
        date_str = data.get('date')
        time_str = data.get('time', '')  # Zeit kann leer sein für ganztägige Events
        end_date_str = data.get('end_date')

        # Passwort prüfen
        if not check_action_password(password):
            return jsonify({"success": False, "error": "Ungültiges Aktionspasswort!"}), 403
        
        # Grundlegende Validierung
        if not title or not date_str:
            return jsonify({"success": False, "error": "Titel/Startdatum erforderlich!"}), 400

        # Wichtig: Zeit richtig verarbeiten
        print(f"Debug Zeit: '{time_str}', Typ: {type(time_str)}")
        
        # Daten validieren und konvertieren
        try:
            # Startdatum validieren
            start_date = datetime.date.fromisoformat(date_str)
            
            # Standard: Ganztägiges Event
            all_day = True
            start_time = None
            
            # Zeit verarbeiten, wenn vorhanden
            if time_str and time_str.strip():
                try:
                    # Zeitformat prüfen (HH:MM)
                    time_parts = time_str.strip().split(':')
                    if len(time_parts) >= 2:
                        hours = int(time_parts[0])
                        minutes = int(time_parts[1])
                        if 0 <= hours <= 23 and 0 <= minutes <= 59:
                            start_time = time_str.strip()
                            all_day = False
                            print(f"Gültige Zeit erkannt: {start_time}")
                except Exception as te:
                    print(f"Zeit-Parsing Fehler: {te}")
                    # Bei Fehlern: Zeit ignorieren, ganztägiges Event verwenden
            
            # ISO-Format für FullCalendar
            if all_day:
                start_iso_for_calendar = date_str
            else:
                start_iso_for_calendar = f"{date_str}T{start_time}"
            
            # Enddatum verarbeiten, wenn vorhanden
            end_iso_for_calendar = None
            valid_end_date = None
            
            if end_date_str:
                try:
                    end_date = datetime.date.fromisoformat(end_date_str)
                    if end_date > start_date:  # Nur wenn nach Startdatum
                        end_iso_for_calendar = end_date_str
                        valid_end_date = end_date_str
                except ValueError:
                    pass  # Ungültiges Enddatum ignorieren
            
            # Events aus der Datei laden
            events = load_data(CALENDAR_FILE)
            
            # Neues Event erstellen
            new_id = str(uuid.uuid4())
            new_event = {
                "id": new_id,
                "name": title.strip(),
                "title": title.strip(),  # Duplizieren für FullCalendar-Kompatibilität
                "description": description.strip(),
                "date": date_str,
                "time": start_time,  # Speichere die Zeit (oder None wenn ganztägig)
                "end_date": valid_end_date,
                "allDay": all_day,
                "start": start_iso_for_calendar,  # ISO-Format für FullCalendar
                "created_at": datetime.datetime.now(datetime.timezone.utc).isoformat()
            }
            
            # Event zur Liste hinzufügen
            events.append(new_event)
            
            # Event-Liste speichern
            if save_data(CALENDAR_FILE, events):
                # FullCalendar-Event für die Antwort erstellen
                fullcalendar_event = {
                    'id': new_id,
                    'title': new_event['name'],
                    'start': start_iso_for_calendar,
                    'allDay': all_day,
                    'extendedProps': {
                        'description': new_event['description'],
                        'time': start_time  # Zeit als zusätzliche Info
                    }
                }
                
                if end_iso_for_calendar:
                    fullcalendar_event['end'] = end_iso_for_calendar
                
                return jsonify({"success": True, "event": fullcalendar_event})
            else:
                return jsonify({"success": False, "error": "Speichern fehlgeschlagen."}), 500
            
        except ValueError as e:
            print(f"Kalender ValueError: {e}")
            return jsonify({"success": False, "error": "Ungültiges Datum/Uhrzeit!"}), 400
            
    except Exception as e:
        print(f"Kalender unerwarteter Fehler: {e}")
        return jsonify({"success": False, "error": f"Interner Fehler: {str(e)}"}), 500


@app.route('/api/calendar/delete/<event_id>', methods=['POST'])
def delete_calendar_event(event_id):
    """Löscht einen Kalendereintrag."""
    try:
        # Logging für Debugging
        app.logger.info(f"Löschanfrage für Event ID: {event_id}")
        
        # Request-Daten auslesen
        data = request.json
        if not data:
            app.logger.error(f"Keine JSON-Daten in der Löschanfrage")
            return jsonify({"success": False, "error": "Keine Daten empfangen"}), 400
            
        password = data.get('password')
        app.logger.debug(f"Passwort wurde empfangen: {bool(password)}")
        
        # Passwort prüfen
        if not check_action_password(password):
            app.logger.warning(f"Falsches Passwort bei Löschanfrage")
            return jsonify({"success": False, "error": "Ungültiges Aktionspasswort!"}), 403
        
        # Ereignisse laden
        events = load_data(CALENDAR_FILE)
        initial_length = len(events)
        app.logger.debug(f"Vor dem Löschen: {initial_length} Events")
        
        # Ereignis mit passender ID finden und entfernen
        events_after_delete = [event for event in events if event.get('id') != event_id]
        
        # Prüfen, ob ein Event entfernt wurde
        if len(events_after_delete) < initial_length:
            app.logger.info(f"Event {event_id} wird gelöscht")
            
            # Daten speichern
            if save_data(CALENDAR_FILE, events_after_delete):
                app.logger.info(f"Event {event_id} erfolgreich gelöscht")
                return jsonify({"success": True})
            else:
                app.logger.error(f"Fehler beim Speichern nach dem Löschen")
                return jsonify({"success": False, "error": "Speichern fehlgeschlagen."}), 500
        else:
            app.logger.warning(f"Event {event_id} nicht gefunden")
            return jsonify({"success": False, "error": "Eintrag nicht gefunden"}), 404
            
    except Exception as e:
        app.logger.error(f"Unerwarteter Fehler beim Löschen: {str(e)}")
        return jsonify({"success": False, "error": f"Serverfehler: {str(e)}"}), 500


# --- Fügen Sie auch diese neue Aktualisierungs-Route für Termine hinzu ---

@app.route('/api/calendar/update/<event_id>', methods=['POST'])
def update_calendar_event(event_id):
    """Aktualisiert einen bestehenden Kalendereintrag."""
    try:
        data = request.json
        print(f"Kalender-Event UPDATE: ID={event_id}, Daten: {data}")
        
        password = data.get('password')
        title = data.get('title')
        description = data.get('description', '')
        date_str = data.get('date')
        time_str = data.get('time', '')
        end_date_str = data.get('end_date')

        # Passwort prüfen
        if not check_action_password(password):
            return jsonify({"success": False, "error": "Ungültiges Aktionspasswort!"}), 403
        
        # Grundlegende Validierung
        if not title or not date_str:
            return jsonify({"success": False, "error": "Titel/Startdatum erforderlich!"}), 400

        # Events laden
        events = load_data(CALENDAR_FILE)
        
        # Event suchen
        event_found = False
        updated_event = None
        
        for event in events:
            if event.get('id') == event_id:
                event_found = True
                
                # Zeit verarbeiten wie bei add_calendar_event
                all_day = True
                start_time = None
                
                if time_str and time_str.strip():
                    try:
                        time_parts = time_str.strip().split(':')
                        if len(time_parts) >= 2:
                            hours = int(time_parts[0])
                            minutes = int(time_parts[1])
                            if 0 <= hours <= 23 and 0 <= minutes <= 59:
                                start_time = time_str.strip()
                                all_day = False
                    except Exception as te:
                        print(f"Zeit-Parsing Fehler bei Update: {te}")
                
                # ISO-Format für FullCalendar
                if all_day:
                    start_iso_for_calendar = date_str
                else:
                    start_iso_for_calendar = f"{date_str}T{start_time}"
                
                # Enddatum verarbeiten
                valid_end_date = None
                if end_date_str:
                    try:
                        end_date = datetime.date.fromisoformat(end_date_str)
                        start_date = datetime.date.fromisoformat(date_str)
                        if end_date > start_date:
                            valid_end_date = end_date_str
                    except ValueError:
                        pass
                
                # Event aktualisieren
                event['name'] = title.strip()
                event['title'] = title.strip()
                event['description'] = description.strip()
                event['date'] = date_str
                event['time'] = start_time
                event['end_date'] = valid_end_date
                event['allDay'] = all_day
                event['start'] = start_iso_for_calendar
                event['updated_at'] = datetime.datetime.now(datetime.timezone.utc).isoformat()
                
                if 'end' in event and not valid_end_date:
                    del event['end']
                elif valid_end_date:
                    event['end'] = valid_end_date
                
                updated_event = event
                break
        
        if not event_found:
            return jsonify({"success": False, "error": "Event nicht gefunden"}), 404
        
        # Events speichern
        if save_data(CALENDAR_FILE, events):
            # FullCalendar-Event für die Antwort
            fullcalendar_event = {
                'id': event_id,
                'title': updated_event['name'],
                'start': updated_event['start'],
                'allDay': updated_event['allDay'],
                'extendedProps': {
                    'description': updated_event['description'],
                    'time': updated_event.get('time')
                }
            }
            
            if updated_event.get('end_date'):
                fullcalendar_event['end'] = updated_event['end_date']
            
            return jsonify({"success": True, "event": fullcalendar_event})
        else:
            return jsonify({"success": False, "error": "Speichern fehlgeschlagen."}), 500
        
    except Exception as e:
        print(f"Kalender Update Fehler: {e}")
        return jsonify({"success": False, "error": f"Interner Fehler: {str(e)}"}), 500

# --- App Start ---
if __name__ == '__main__':
    try:
        print("--- Starting Flask Server Execution ---")
        app.run(host='0.0.0.0', port=8081, debug=True)  # Debug-Modus aktiviert
    except Exception as e:
        print(f"FATAL ERROR during app.run: {e}")
        sys.exit(1)