import os
import sys
import time
import json
import logging
import requests
import re
import random
import base64
from io import BytesIO
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from pathlib import Path

# Add current directory to Python path to ensure modules can be imported
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from flask import Flask, render_template, request, redirect, session, url_for, send_file, jsonify, flash
from flask_session import Session
from werkzeug.middleware.proxy_fix import ProxyFix
from apscheduler.schedulers.background import BackgroundScheduler
import pandas as pd
from psycopg2.extras import Json

# Import our new modules
import config
from db import get_connection, get_db_connection, get_db_cursor, execute_query
from auth.decorators import login_required
from auth.password import hash_password, verify_password
from auth.two_factor import (
    generate_totp_secret, generate_qr_code, verify_totp,
    generate_backup_codes, hash_backup_code, verify_backup_code
)
from auth.rate_limiting import (
    check_ip_blocked, record_failed_attempt,
    reset_login_attempts, get_login_attempts
)
from utils.network import get_client_ip
from utils.audit import log_audit
from utils.license import generate_license_key, validate_license_key
from utils.backup import create_backup, restore_backup, get_or_create_encryption_key
from utils.export import (
    create_excel_export, format_datetime_for_export,
    prepare_license_export_data, prepare_customer_export_data,
    prepare_session_export_data, prepare_audit_export_data
)
from models import get_user_by_username

app = Flask(__name__)
# Load configuration from config module
app.config['SECRET_KEY'] = config.SECRET_KEY
app.config['SESSION_TYPE'] = config.SESSION_TYPE
app.config['JSON_AS_ASCII'] = config.JSON_AS_ASCII
app.config['JSONIFY_MIMETYPE'] = config.JSONIFY_MIMETYPE
app.config['PERMANENT_SESSION_LIFETIME'] = config.PERMANENT_SESSION_LIFETIME
app.config['SESSION_COOKIE_HTTPONLY'] = config.SESSION_COOKIE_HTTPONLY
app.config['SESSION_COOKIE_SECURE'] = config.SESSION_COOKIE_SECURE
app.config['SESSION_COOKIE_SAMESITE'] = config.SESSION_COOKIE_SAMESITE
app.config['SESSION_COOKIE_NAME'] = config.SESSION_COOKIE_NAME
app.config['SESSION_REFRESH_EACH_REQUEST'] = config.SESSION_REFRESH_EACH_REQUEST
Session(app)

# ProxyFix für korrekte IP-Adressen hinter Nginx
app.wsgi_app = ProxyFix(
    app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)

# Configuration is now loaded from config module

# Scheduler für automatische Backups
scheduler = BackgroundScheduler()
scheduler.start()

# Logging konfigurieren
logging.basicConfig(level=logging.INFO)

# Import and register blueprints
from routes.auth_routes import auth_bp
from routes.admin_routes import admin_bp
from routes.license_routes import license_bp
from routes.customer_routes import customer_bp
from routes.resource_routes import resource_bp
from routes.session_routes import session_bp
from routes.batch_routes import batch_bp
from routes.api_routes import api_bp
from routes.export_routes import export_bp

# Register blueprints
app.register_blueprint(auth_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(license_bp)
app.register_blueprint(customer_bp)
app.register_blueprint(resource_bp)
app.register_blueprint(session_bp)
app.register_blueprint(batch_bp)
app.register_blueprint(api_bp)
app.register_blueprint(export_bp)


# Scheduled Backup Job
def scheduled_backup():
    """Führt ein geplantes Backup aus"""
    logging.info("Starte geplantes Backup...")
    create_backup(backup_type="scheduled", created_by="scheduler")

# Scheduler konfigurieren - täglich um 3:00 Uhr
scheduler.add_job(
    scheduled_backup,
    'cron',
    hour=config.SCHEDULER_CONFIG['backup_hour'],
    minute=config.SCHEDULER_CONFIG['backup_minute'],
    id='daily_backup',
    replace_existing=True
)


def verify_recaptcha(response):
    """Verifiziert die reCAPTCHA v2 Response mit Google"""
    secret_key = config.RECAPTCHA_SECRET_KEY
    
    # Wenn kein Secret Key konfiguriert ist, CAPTCHA als bestanden werten (für PoC)
    if not secret_key:
        logging.warning("RECAPTCHA_SECRET_KEY nicht konfiguriert - CAPTCHA wird übersprungen")
        return True
    
    # Verifizierung bei Google
    try:
        verify_url = 'https://www.google.com/recaptcha/api/siteverify'
        data = {
            'secret': secret_key,
            'response': response
        }
        
        # Timeout für Request setzen
        r = requests.post(verify_url, data=data, timeout=5)
        result = r.json()
        
        # Log für Debugging
        if not result.get('success'):
            logging.warning(f"reCAPTCHA Validierung fehlgeschlagen: {result.get('error-codes', [])}")
        
        return result.get('success', False)
    
    except requests.exceptions.RequestException as e:
        logging.error(f"reCAPTCHA Verifizierung fehlgeschlagen: {str(e)}")
        # Bei Netzwerkfehlern CAPTCHA als bestanden werten
        return True
    except Exception as e:
        logging.error(f"Unerwarteter Fehler bei reCAPTCHA: {str(e)}")
        return False


# @app.route("/login", methods=["GET", "POST"])
# def login():
    # Timing-Attack Schutz - Start Zeit merken
    # start_time = time.time()
    
    # IP-Adresse ermitteln
    # ip_address = get_client_ip()
    
    # Prüfen ob IP gesperrt ist
    # is_blocked, blocked_until = check_ip_blocked(ip_address)
    # if is_blocked:
        # time_remaining = (blocked_until - datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None)).total_seconds() / 3600
        # error_msg = f"IP GESPERRT! Noch {time_remaining:.1f} Stunden warten."
        # return render_template("login.html", error=error_msg, error_type="blocked")
    
    # Anzahl bisheriger Versuche
    # attempt_count = get_login_attempts(ip_address)
    
    # if request.method == "POST":
        # username = request.form.get("username")
        # password = request.form.get("password")
        # captcha_response = request.form.get("g-recaptcha-response")
        
        # CAPTCHA-Prüfung nur wenn Keys konfiguriert sind
        # recaptcha_site_key = config.RECAPTCHA_SITE_KEY
        # if attempt_count >= config.CAPTCHA_AFTER_ATTEMPTS and recaptcha_site_key:
            # if not captcha_response:
                # Timing-Attack Schutz
                # elapsed = time.time() - start_time
                # if elapsed < 1.0:
                    # time.sleep(1.0 - elapsed)
                # return render_template("login.html", 
                                     # error="CAPTCHA ERFORDERLICH!", 
                                     # show_captcha=True,
                                     # error_type="captcha",
                                     # attempts_left=max(0, config.MAX_LOGIN_ATTEMPTS - attempt_count),
                                     # recaptcha_site_key=recaptcha_site_key)
            
            # CAPTCHA validieren
            # if not verify_recaptcha(captcha_response):
                # Timing-Attack Schutz
                # elapsed = time.time() - start_time
                # if elapsed < 1.0:
                    # time.sleep(1.0 - elapsed)
                # return render_template("login.html", 
                                     # error="CAPTCHA UNGÜLTIG! Bitte erneut versuchen.", 
                                     # show_captcha=True,
                                     # error_type="captcha",
                                     # attempts_left=max(0, config.MAX_LOGIN_ATTEMPTS - attempt_count),
                                     # recaptcha_site_key=recaptcha_site_key)
        
        # Check user in database first, fallback to env vars
        # user = get_user_by_username(username)
        # login_success = False
        # needs_2fa = False
        
        # if user:
            # Database user authentication
            # if verify_password(password, user['password_hash']):
                # login_success = True
                # needs_2fa = user['totp_enabled']
        # else:
            # Fallback to environment variables for backward compatibility
            # if username in config.ADMIN_USERS and password == config.ADMIN_USERS[username]:
                # login_success = True
        
        # Timing-Attack Schutz - Mindestens 1 Sekunde warten
        # elapsed = time.time() - start_time
        # if elapsed < 1.0:
            # time.sleep(1.0 - elapsed)
        
        # if login_success:
            # Erfolgreicher Login
            # if needs_2fa:
                # Store temporary session for 2FA verification
                # session['temp_username'] = username
                # session['temp_user_id'] = user['id']
                # session['awaiting_2fa'] = True
                # return redirect(url_for('verify_2fa'))
            # else:
                # Complete login without 2FA
                # session.permanent = True  # Aktiviert das Timeout
                # session['logged_in'] = True
                # session['username'] = username
                # session['user_id'] = user['id'] if user else None
                # session['last_activity'] = datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None).isoformat()
                # reset_login_attempts(ip_address)
                # log_audit('LOGIN_SUCCESS', 'user', 
                         # additional_info=f"Erfolgreiche Anmeldung von IP: {ip_address}")
                # return redirect(url_for('dashboard'))
        # else:
            # Fehlgeschlagener Login
            # error_message = record_failed_attempt(ip_address, username)
            # new_attempt_count = get_login_attempts(ip_address)
            
            # Prüfen ob jetzt gesperrt
            # is_now_blocked, _ = check_ip_blocked(ip_address)
            # if is_now_blocked:
                # log_audit('LOGIN_BLOCKED', 'security', 
                         # additional_info=f"IP {ip_address} wurde nach {config.MAX_LOGIN_ATTEMPTS} Versuchen gesperrt")
            
            # return render_template("login.html", 
                                 # error=error_message,
                                 # show_captcha=(new_attempt_count >= config.CAPTCHA_AFTER_ATTEMPTS and config.RECAPTCHA_SITE_KEY),
                                 # error_type="failed",
                                 # attempts_left=max(0, config.MAX_LOGIN_ATTEMPTS - new_attempt_count),
                                 # recaptcha_site_key=config.RECAPTCHA_SITE_KEY)
    
    # GET Request
    # return render_template("login.html", 
                         # show_captcha=(attempt_count >= config.CAPTCHA_AFTER_ATTEMPTS and config.RECAPTCHA_SITE_KEY),
                         # attempts_left=max(0, config.MAX_LOGIN_ATTEMPTS - attempt_count),
                         # recaptcha_site_key=config.RECAPTCHA_SITE_KEY)

# @app.route("/logout")
# def logout():
    # username = session.get('username', 'unknown')
    # log_audit('LOGOUT', 'user', additional_info=f"Abmeldung")
    # session.pop('logged_in', None)
    # session.pop('username', None)
    # session.pop('user_id', None)
    # session.pop('temp_username', None)
    # session.pop('temp_user_id', None)
    # session.pop('awaiting_2fa', None)
    # return redirect(url_for('login'))

# @app.route("/verify-2fa", methods=["GET", "POST"])
# def verify_2fa():
    # if not session.get('awaiting_2fa'):
        # return redirect(url_for('login'))
    
    # if request.method == "POST":
        # token = request.form.get('token', '').replace(' ', '')
        # username = session.get('temp_username')
        # user_id = session.get('temp_user_id')
        
        # if not username or not user_id:
            # flash('Session expired. Please login again.', 'error')
            # return redirect(url_for('login'))
        
        # user = get_user_by_username(username)
        # if not user:
            # flash('User not found.', 'error')
            # return redirect(url_for('login'))
        
        # Check if it's a backup code
        # if len(token) == 8 and token.isupper():
            # Try backup code
            # backup_codes = json.loads(user['backup_codes']) if user['backup_codes'] else []
            # if verify_backup_code(token, backup_codes):
                # Remove used backup code
                # code_hash = hash_backup_code(token)
                # backup_codes.remove(code_hash)
                
                # conn = get_connection()
                # cur = conn.cursor()
                # cur.execute("UPDATE users SET backup_codes = %s WHERE id = %s",
                          # (json.dumps(backup_codes), user_id))
                # conn.commit()
                # cur.close()
                # conn.close()
                
                # Complete login
                # session.permanent = True
                # session['logged_in'] = True
                # session['username'] = username
                # session['user_id'] = user_id
                # session['last_activity'] = datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None).isoformat()
                # session.pop('temp_username', None)
                # session.pop('temp_user_id', None)
                # session.pop('awaiting_2fa', None)
                
                # flash('Login successful using backup code. Please generate new backup codes.', 'warning')
                # log_audit('LOGIN_2FA_BACKUP', 'user', additional_info=f"2FA login with backup code")
                # return redirect(url_for('dashboard'))
        # else:
            # Try TOTP token
            # if verify_totp(user['totp_secret'], token):
                # Complete login
                # session.permanent = True
                # session['logged_in'] = True
                # session['username'] = username
                # session['user_id'] = user_id
                # session['last_activity'] = datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None).isoformat()
                # session.pop('temp_username', None)
                # session.pop('temp_user_id', None)
                # session.pop('awaiting_2fa', None)
                
                # log_audit('LOGIN_2FA_SUCCESS', 'user', additional_info=f"2FA login successful")
                # return redirect(url_for('dashboard'))
        
        # Failed verification
        # conn = get_connection()
        # cur = conn.cursor()
        # cur.execute("UPDATE users SET failed_2fa_attempts = failed_2fa_attempts + 1, last_failed_2fa = %s WHERE id = %s",
                  # (datetime.now(), user_id))
        # conn.commit()
        # cur.close()
        # conn.close()
        
        # flash('Invalid authentication code. Please try again.', 'error')
        # log_audit('LOGIN_2FA_FAILED', 'user', additional_info=f"Failed 2FA attempt")
    
    # return render_template('verify_2fa.html')

# @app.route("/profile")
# @login_required
def profile():
    user = get_user_by_username(session['username'])
    if not user:
        # For environment-based users, redirect with message
        flash('Bitte führen Sie das Migrations-Script aus, um Passwort-Änderung und 2FA zu aktivieren.', 'info')
        return redirect(url_for('dashboard'))
    return render_template('profile.html', user=user)

# @app.route("/profile/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')
    
    user = get_user_by_username(session['username'])
    
    # Verify current password
    if not verify_password(current_password, user['password_hash']):
        flash('Current password is incorrect.', 'error')
        return redirect(url_for('profile'))
    
    # Check new password
    if new_password != confirm_password:
        flash('New passwords do not match.', 'error')
        return redirect(url_for('profile'))
    
    if len(new_password) < 8:
        flash('Password must be at least 8 characters long.', 'error')
        return redirect(url_for('profile'))
    
    # Update password
    new_hash = hash_password(new_password)
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("UPDATE users SET password_hash = %s, last_password_change = %s WHERE id = %s",
              (new_hash, datetime.now(), user['id']))
    conn.commit()
    cur.close()
    conn.close()
    
    log_audit('PASSWORD_CHANGE', 'user', entity_id=user['id'], 
             additional_info="Password changed successfully")
    flash('Password changed successfully.', 'success')
    return redirect(url_for('profile'))

# @app.route("/profile/setup-2fa")
# @login_required
def setup_2fa():
    user = get_user_by_username(session['username'])
    
    if user['totp_enabled']:
        flash('2FA is already enabled for your account.', 'info')
        return redirect(url_for('profile'))
    
    # Generate new TOTP secret
    totp_secret = generate_totp_secret()
    session['temp_totp_secret'] = totp_secret
    
    # Generate QR code
    qr_code = generate_qr_code(user['username'], totp_secret)
    
    return render_template('setup_2fa.html', 
                         totp_secret=totp_secret, 
                         qr_code=qr_code)

# @app.route("/profile/enable-2fa", methods=["POST"])
# @login_required
def enable_2fa():
    token = request.form.get('token', '').replace(' ', '')
    totp_secret = session.get('temp_totp_secret')
    
    if not totp_secret:
        flash('2FA setup session expired. Please try again.', 'error')
        return redirect(url_for('setup_2fa'))
    
    # Verify the token
    if not verify_totp(totp_secret, token):
        flash('Invalid authentication code. Please try again.', 'error')
        return redirect(url_for('setup_2fa'))
    
    # Generate backup codes
    backup_codes = generate_backup_codes()
    hashed_codes = [hash_backup_code(code) for code in backup_codes]
    
    # Enable 2FA
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("""
        UPDATE users 
        SET totp_secret = %s, totp_enabled = TRUE, backup_codes = %s 
        WHERE username = %s
    """, (totp_secret, json.dumps(hashed_codes), session['username']))
    conn.commit()
    cur.close()
    conn.close()
    
    session.pop('temp_totp_secret', None)
    
    log_audit('2FA_ENABLED', 'user', additional_info="2FA enabled successfully")
    
    # Show backup codes
    return render_template('backup_codes.html', backup_codes=backup_codes)

# @app.route("/profile/disable-2fa", methods=["POST"])
# @login_required
def disable_2fa():
    password = request.form.get('password')
    user = get_user_by_username(session['username'])
    
    # Verify password
    if not verify_password(password, user['password_hash']):
        flash('Incorrect password.', 'error')
        return redirect(url_for('profile'))
    
    # Disable 2FA
    conn = get_connection()
    cur = conn.cursor()
    cur.execute("""
        UPDATE users 
        SET totp_enabled = FALSE, totp_secret = NULL, backup_codes = NULL 
        WHERE username = %s
    """, (session['username'],))
    conn.commit()
    cur.close()
    conn.close()
    
    log_audit('2FA_DISABLED', 'user', additional_info="2FA disabled")
    flash('2FA has been disabled for your account.', 'success')
    return redirect(url_for('profile'))

# @app.route("/heartbeat", methods=['POST'])
# @login_required
def heartbeat():
    """Endpoint für Session Keep-Alive - aktualisiert last_activity"""
    # Aktualisiere last_activity nur wenn explizit angefordert
    session['last_activity'] = datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None).isoformat()
    # Force session save
    session.modified = True
    
    return jsonify({
        'status': 'ok',
        'last_activity': session['last_activity'],
        'username': session.get('username')
    })

# @app.route("/api/generate-license-key", methods=['POST'])
# @login_required
def api_generate_key():
    """API Endpoint zur Generierung eines neuen Lizenzschlüssels"""
    try:
        # Lizenztyp aus Request holen (default: full)
        data = request.get_json() or {}
        license_type = data.get('type', 'full')
        
        # Key generieren
        key = generate_license_key(license_type)
        
        # Prüfen ob Key bereits existiert (sehr unwahrscheinlich aber sicher ist sicher)
        conn = get_connection()
        cur = conn.cursor()
        
        # Wiederhole bis eindeutiger Key gefunden
        attempts = 0
        while attempts < 10:  # Max 10 Versuche
            cur.execute("SELECT 1 FROM licenses WHERE license_key = %s", (key,))
            if not cur.fetchone():
                break  # Key ist eindeutig
            key = generate_license_key(license_type)
            attempts += 1
        
        cur.close()
        conn.close()
        
        # Log für Audit
        log_audit('GENERATE_KEY', 'license', 
                 additional_info={'type': license_type, 'key': key})
        
        return jsonify({
            'success': True,
            'key': key,
            'type': license_type
        })
        
    except Exception as e:
        logging.error(f"Fehler bei Key-Generierung: {str(e)}")
        return jsonify({
            'success': False,
            'error': 'Fehler bei der Key-Generierung'
        }), 500

# @app.route("/api/customers", methods=['GET'])
# @login_required
def api_customers():
    """API Endpoint für die Kundensuche mit Select2"""
    try:
        # Suchparameter
        search = request.args.get('q', '').strip()
        page = request.args.get('page', 1, type=int)
        per_page = 20
        customer_id = request.args.get('id', type=int)
        
        conn = get_connection()
        cur = conn.cursor()
        
        # Einzelnen Kunden per ID abrufen
        if customer_id:
            cur.execute("""
                SELECT c.id, c.name, c.email,
                       COUNT(l.id) as license_count
                FROM customers c
                LEFT JOIN licenses l ON c.id = l.customer_id
                WHERE c.id = %s
                GROUP BY c.id, c.name, c.email
            """, (customer_id,))
            
            customer = cur.fetchone()
            results = []
            if customer:
                results.append({
                    'id': customer[0],
                    'text': f"{customer[1]} ({customer[2]})",
                    'name': customer[1],
                    'email': customer[2],
                    'license_count': customer[3]
                })
            
            cur.close()
            conn.close()
            
            return jsonify({
                'results': results,
                'pagination': {'more': False}
            })
        
        # SQL Query mit optionaler Suche
        elif search:
            cur.execute("""
                SELECT c.id, c.name, c.email,
                       COUNT(l.id) as license_count
                FROM customers c
                LEFT JOIN licenses l ON c.id = l.customer_id
                WHERE LOWER(c.name) LIKE LOWER(%s)
                   OR LOWER(c.email) LIKE LOWER(%s)
                GROUP BY c.id, c.name, c.email
                ORDER BY c.name
                LIMIT %s OFFSET %s
            """, (f'%{search}%', f'%{search}%', per_page, (page - 1) * per_page))
        else:
            cur.execute("""
                SELECT c.id, c.name, c.email,
                       COUNT(l.id) as license_count
                FROM customers c
                LEFT JOIN licenses l ON c.id = l.customer_id
                GROUP BY c.id, c.name, c.email
                ORDER BY c.name
                LIMIT %s OFFSET %s
            """, (per_page, (page - 1) * per_page))
        
        customers = cur.fetchall()
        
        # Format für Select2
        results = []
        for customer in customers:
            results.append({
                'id': customer[0],
                'text': f"{customer[1]} - {customer[2]} ({customer[3]} Lizenzen)",
                'name': customer[1],
                'email': customer[2],
                'license_count': customer[3]
            })
        
        # Gesamtanzahl für Pagination
        if search:
            cur.execute("""
                SELECT COUNT(*) FROM customers
                WHERE LOWER(name) LIKE LOWER(%s)
                   OR LOWER(email) LIKE LOWER(%s)
            """, (f'%{search}%', f'%{search}%'))
        else:
            cur.execute("SELECT COUNT(*) FROM customers")
        
        total_count = cur.fetchone()[0]
        
        cur.close()
        conn.close()
        
        # Select2 Response Format
        return jsonify({
            'results': results,
            'pagination': {
                'more': (page * per_page) < total_count
            }
        })
        
    except Exception as e:
        logging.error(f"Fehler bei Kundensuche: {str(e)}")
        return jsonify({
            'results': [],
            'error': 'Fehler bei der Kundensuche'
        }), 500

# @app.route("/")
# @login_required
def dashboard():
    conn = get_connection()
    cur = conn.cursor()
    
    # Statistiken abrufen
    # Gesamtanzahl Kunden (ohne Testdaten)
    cur.execute("SELECT COUNT(*) FROM customers WHERE is_test = FALSE")
    total_customers = cur.fetchone()[0]
    
    # Gesamtanzahl Lizenzen (ohne Testdaten)
    cur.execute("SELECT COUNT(*) FROM licenses WHERE is_test = FALSE")
    total_licenses = cur.fetchone()[0]
    
    # Aktive Lizenzen (nicht abgelaufen und is_active = true, ohne Testdaten)
    cur.execute("""
        SELECT COUNT(*) FROM licenses 
        WHERE valid_until >= CURRENT_DATE AND is_active = TRUE AND is_test = FALSE
    """)
    active_licenses = cur.fetchone()[0]
    
    # Aktive Sessions
    cur.execute("SELECT COUNT(*) FROM sessions WHERE is_active = TRUE")
    active_sessions_count = cur.fetchone()[0]
    
    # Abgelaufene Lizenzen (ohne Testdaten)
    cur.execute("""
        SELECT COUNT(*) FROM licenses 
        WHERE valid_until < CURRENT_DATE AND is_test = FALSE
    """)
    expired_licenses = cur.fetchone()[0]
    
    # Deaktivierte Lizenzen (ohne Testdaten)
    cur.execute("""
        SELECT COUNT(*) FROM licenses 
        WHERE is_active = FALSE AND is_test = FALSE
    """)
    inactive_licenses = cur.fetchone()[0]
    
    # Lizenzen die in den nächsten 30 Tagen ablaufen (ohne Testdaten)
    cur.execute("""
        SELECT COUNT(*) FROM licenses 
        WHERE valid_until >= CURRENT_DATE 
        AND valid_until < CURRENT_DATE + INTERVAL '30 days'
        AND is_active = TRUE
        AND is_test = FALSE
    """)
    expiring_soon = cur.fetchone()[0]
    
    # Testlizenzen vs Vollversionen (ohne Testdaten)
    cur.execute("""
        SELECT license_type, COUNT(*) 
        FROM licenses 
        WHERE is_test = FALSE
        GROUP BY license_type
    """)
    license_types = dict(cur.fetchall())
    
    # Anzahl Testdaten
    cur.execute("SELECT COUNT(*) FROM licenses WHERE is_test = TRUE")
    test_data_count = cur.fetchone()[0]
    
    # Anzahl Test-Kunden
    cur.execute("SELECT COUNT(*) FROM customers WHERE is_test = TRUE")
    test_customers_count = cur.fetchone()[0]
    
    # Anzahl Test-Ressourcen
    cur.execute("SELECT COUNT(*) FROM resource_pools WHERE is_test = TRUE")
    test_resources_count = cur.fetchone()[0]
    
    # Letzte 5 erstellten Lizenzen (ohne Testdaten)
    cur.execute("""
        SELECT l.id, l.license_key, c.name, l.valid_until,
               CASE 
                   WHEN l.is_active = FALSE THEN 'deaktiviert'
                   WHEN l.valid_until < CURRENT_DATE THEN 'abgelaufen'
                   WHEN l.valid_until < CURRENT_DATE + INTERVAL '30 days' THEN 'läuft bald ab'
                   ELSE 'aktiv'
               END as status
        FROM licenses l
        JOIN customers c ON l.customer_id = c.id
        WHERE l.is_test = FALSE
        ORDER BY l.id DESC
        LIMIT 5
    """)
    recent_licenses = cur.fetchall()
    
    # Bald ablaufende Lizenzen (nächste 30 Tage, ohne Testdaten)
    cur.execute("""
        SELECT l.id, l.license_key, c.name, l.valid_until,
               l.valid_until - CURRENT_DATE as days_left
        FROM licenses l
        JOIN customers c ON l.customer_id = c.id
        WHERE l.valid_until >= CURRENT_DATE 
        AND l.valid_until < CURRENT_DATE + INTERVAL '30 days'
        AND l.is_active = TRUE
        AND l.is_test = FALSE
        ORDER BY l.valid_until
        LIMIT 10
    """)
    expiring_licenses = cur.fetchall()
    
    # Letztes Backup
    cur.execute("""
        SELECT created_at, filesize, duration_seconds, backup_type, status
        FROM backup_history 
        ORDER BY created_at DESC 
        LIMIT 1
    """)
    last_backup_info = cur.fetchone()
    
    # Sicherheitsstatistiken
    # Gesperrte IPs
    cur.execute("""
        SELECT COUNT(*) FROM login_attempts 
        WHERE blocked_until IS NOT NULL AND blocked_until > CURRENT_TIMESTAMP
    """)
    blocked_ips_count = cur.fetchone()[0]
    
    # Fehlversuche heute
    cur.execute("""
        SELECT COALESCE(SUM(attempt_count), 0) FROM login_attempts
        WHERE last_attempt::date = CURRENT_DATE
    """)
    failed_attempts_today = cur.fetchone()[0]
    
    # Letzte 5 Sicherheitsereignisse
    cur.execute("""
        SELECT 
            la.ip_address, 
            la.attempt_count, 
            la.last_attempt, 
            la.blocked_until,
            la.last_username_tried,
            la.last_error_message
        FROM login_attempts la
        ORDER BY la.last_attempt DESC
        LIMIT 5
    """)
    recent_security_events = []
    for event in cur.fetchall():
        recent_security_events.append({
            'ip_address': event[0],
            'attempt_count': event[1],
            'last_attempt': event[2].strftime('%d.%m %H:%M'),
            'blocked_until': event[3].strftime('%d.%m %H:%M') if event[3] and event[3] > datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None) else None,
            'username_tried': event[4],
            'error_message': event[5]
        })
    
    # Sicherheitslevel berechnen
    if blocked_ips_count > 5 or failed_attempts_today > 50:
        security_level = 'danger'
        security_level_text = 'KRITISCH'
    elif blocked_ips_count > 2 or failed_attempts_today > 20:
        security_level = 'warning'
        security_level_text = 'ERHÖHT'
    else:
        security_level = 'success'
        security_level_text = 'NORMAL'
    
    # Resource Pool Statistiken (nur Live-Daten, keine Testdaten)
    cur.execute("""
        SELECT 
            resource_type,
            COUNT(*) FILTER (WHERE status = 'available') as available,
            COUNT(*) FILTER (WHERE status = 'allocated') as allocated,
            COUNT(*) FILTER (WHERE status = 'quarantine') as quarantine,
            COUNT(*) as total
        FROM resource_pools
        WHERE is_test = FALSE
        GROUP BY resource_type
    """)
    
    resource_stats = {}
    resource_warning = None
    
    for row in cur.fetchall():
        available_percent = round((row[1] / row[4] * 100) if row[4] > 0 else 0, 1)
        resource_stats[row[0]] = {
            'available': row[1],
            'allocated': row[2],
            'quarantine': row[3],
            'total': row[4],
            'available_percent': available_percent,
            'warning_level': 'danger' if row[1] < 50 else 'warning' if row[1] < 100 else 'success'
        }
        
        # Warnung bei niedrigem Bestand
        if row[1] < 50:
            if not resource_warning:
                resource_warning = f"Niedriger Bestand bei {row[0].upper()}: nur noch {row[1]} verfügbar!"
            else:
                resource_warning += f" | {row[0].upper()}: {row[1]}"
    
    cur.close()
    conn.close()
    
    stats = {
        'total_customers': total_customers,
        'total_licenses': total_licenses,
        'active_licenses': active_licenses,
        'expired_licenses': expired_licenses,
        'inactive_licenses': inactive_licenses,
        'expiring_soon': expiring_soon,
        'full_licenses': license_types.get('full', 0),
        'test_licenses': license_types.get('test', 0),
        'test_data_count': test_data_count,
        'test_customers_count': test_customers_count,
        'test_resources_count': test_resources_count,
        'recent_licenses': recent_licenses,
        'expiring_licenses': expiring_licenses,
        'active_sessions': active_sessions_count,
        'last_backup': last_backup_info,
        # Sicherheitsstatistiken
        'blocked_ips_count': blocked_ips_count,
        'failed_attempts_today': failed_attempts_today,
        'recent_security_events': recent_security_events,
        'security_level': security_level,
        'security_level_text': security_level_text,
        'resource_stats': resource_stats
    }
    
    return render_template("dashboard.html", 
                         stats=stats, 
                         resource_stats=resource_stats,
                         resource_warning=resource_warning,
                         username=session.get('username'))

# @app.route("/create", methods=["GET", "POST"])
# @login_required
def create_license():
    if request.method == "POST":
        customer_id = request.form.get("customer_id")
        license_key = request.form["license_key"].upper()  # Immer Großbuchstaben
        license_type = request.form["license_type"]
        valid_from = request.form["valid_from"]
        is_test = request.form.get("is_test") == "on"  # Checkbox value
        
        # Berechne valid_until basierend auf Laufzeit
        duration = int(request.form.get("duration", 1))
        duration_type = request.form.get("duration_type", "years")
        
        from datetime import datetime, timedelta
        from dateutil.relativedelta import relativedelta
        
        start_date = datetime.strptime(valid_from, "%Y-%m-%d")
        
        if duration_type == "days":
            end_date = start_date + timedelta(days=duration)
        elif duration_type == "months":
            end_date = start_date + relativedelta(months=duration)
        else:  # years
            end_date = start_date + relativedelta(years=duration)
        
        # Ein Tag abziehen, da der Starttag mitgezählt wird
        end_date = end_date - timedelta(days=1)
        valid_until = end_date.strftime("%Y-%m-%d")

        # Validiere License Key Format
        if not validate_license_key(license_key):
            flash('Ungültiges License Key Format! Erwartet: AF-YYYYMMFT-XXXX-YYYY-ZZZZ', 'error')
            return redirect(url_for('create_license'))
        
        # Resource counts
        domain_count = int(request.form.get("domain_count", 1))
        ipv4_count = int(request.form.get("ipv4_count", 1))
        phone_count = int(request.form.get("phone_count", 1))
        device_limit = int(request.form.get("device_limit", 3))

        conn = get_connection()
        cur = conn.cursor()

        try:
            # Prüfe ob neuer Kunde oder bestehender
            if customer_id == "new":
                # Neuer Kunde
                name = request.form.get("customer_name")
                email = request.form.get("email")
                
                if not name:
                    flash('Kundenname ist erforderlich!', 'error')
                    return redirect(url_for('create_license'))
                
                # Prüfe ob E-Mail bereits existiert
                if email:
                    cur.execute("SELECT id, name FROM customers WHERE LOWER(email) = LOWER(%s)", (email,))
                    existing = cur.fetchone()
                    if existing:
                        flash(f'E-Mail bereits vergeben für Kunde: {existing[1]}', 'error')
                        return redirect(url_for('create_license'))
                
                # Kunde einfügen (erbt Test-Status von Lizenz)
                cur.execute("""
                    INSERT INTO customers (name, email, is_test, created_at)
                    VALUES (%s, %s, %s, NOW())
                    RETURNING id
                """, (name, email, is_test))
                customer_id = cur.fetchone()[0]
                customer_info = {'name': name, 'email': email, 'is_test': is_test}
                
                # Audit-Log für neuen Kunden
                log_audit('CREATE', 'customer', customer_id, 
                         new_values={'name': name, 'email': email, 'is_test': is_test})
            else:
                # Bestehender Kunde - hole Infos für Audit-Log
                cur.execute("SELECT name, email, is_test FROM customers WHERE id = %s", (customer_id,))
                customer_data = cur.fetchone()
                if not customer_data:
                    flash('Kunde nicht gefunden!', 'error')
                    return redirect(url_for('create_license'))
                customer_info = {'name': customer_data[0], 'email': customer_data[1]}
                
                # Wenn Kunde Test-Kunde ist, Lizenz auch als Test markieren
                if customer_data[2]:  # is_test des Kunden
                    is_test = True

            # Lizenz hinzufügen
            cur.execute("""
                INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active, 
                                    domain_count, ipv4_count, phone_count, device_limit, is_test)
                VALUES (%s, %s, %s, %s, %s, TRUE, %s, %s, %s, %s, %s)
                RETURNING id
            """, (license_key, customer_id, license_type, valid_from, valid_until, 
                  domain_count, ipv4_count, phone_count, device_limit, is_test))
            license_id = cur.fetchone()[0]
            
            # Ressourcen zuweisen
            try:
                # Prüfe Verfügbarkeit
                cur.execute("""
                    SELECT 
                        (SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'domain' AND status = 'available' AND is_test = %s) as domains,
                        (SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'ipv4' AND status = 'available' AND is_test = %s) as ipv4s,
                        (SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'phone' AND status = 'available' AND is_test = %s) as phones
                """, (is_test, is_test, is_test))
                available = cur.fetchone()
                
                if available[0] < domain_count:
                    raise ValueError(f"Nicht genügend Domains verfügbar (benötigt: {domain_count}, verfügbar: {available[0]})")
                if available[1] < ipv4_count:
                    raise ValueError(f"Nicht genügend IPv4-Adressen verfügbar (benötigt: {ipv4_count}, verfügbar: {available[1]})")
                if available[2] < phone_count:
                    raise ValueError(f"Nicht genügend Telefonnummern verfügbar (benötigt: {phone_count}, verfügbar: {available[2]})")
                
                # Domains zuweisen
                if domain_count > 0:
                    cur.execute("""
                        SELECT id FROM resource_pools
                        WHERE resource_type = 'domain' AND status = 'available' AND is_test = %s
                        LIMIT %s FOR UPDATE
                    """, (is_test, domain_count))
                    for (resource_id,) in cur.fetchall():
                        cur.execute("""
                            UPDATE resource_pools
                            SET status = 'allocated', allocated_to_license = %s,
                                status_changed_at = CURRENT_TIMESTAMP, status_changed_by = %s
                            WHERE id = %s
                        """, (license_id, session['username'], resource_id))
                        
                        cur.execute("""
                            INSERT INTO license_resources (license_id, resource_id, assigned_by)
                            VALUES (%s, %s, %s)
                        """, (license_id, resource_id, session['username']))
                        
                        cur.execute("""
                            INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                            VALUES (%s, %s, 'allocated', %s, %s)
                        """, (resource_id, license_id, session['username'], get_client_ip()))
                
                # IPv4s zuweisen
                if ipv4_count > 0:
                    cur.execute("""
                        SELECT id FROM resource_pools
                        WHERE resource_type = 'ipv4' AND status = 'available' AND is_test = %s
                        LIMIT %s FOR UPDATE
                    """, (is_test, ipv4_count))
                    for (resource_id,) in cur.fetchall():
                        cur.execute("""
                            UPDATE resource_pools
                            SET status = 'allocated', allocated_to_license = %s,
                                status_changed_at = CURRENT_TIMESTAMP, status_changed_by = %s
                            WHERE id = %s
                        """, (license_id, session['username'], resource_id))
                        
                        cur.execute("""
                            INSERT INTO license_resources (license_id, resource_id, assigned_by)
                            VALUES (%s, %s, %s)
                        """, (license_id, resource_id, session['username']))
                        
                        cur.execute("""
                            INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                            VALUES (%s, %s, 'allocated', %s, %s)
                        """, (resource_id, license_id, session['username'], get_client_ip()))
                
                # Telefonnummern zuweisen
                if phone_count > 0:
                    cur.execute("""
                        SELECT id FROM resource_pools
                        WHERE resource_type = 'phone' AND status = 'available' AND is_test = %s
                        LIMIT %s FOR UPDATE
                    """, (is_test, phone_count))
                    for (resource_id,) in cur.fetchall():
                        cur.execute("""
                            UPDATE resource_pools
                            SET status = 'allocated', allocated_to_license = %s,
                                status_changed_at = CURRENT_TIMESTAMP, status_changed_by = %s
                            WHERE id = %s
                        """, (license_id, session['username'], resource_id))
                        
                        cur.execute("""
                            INSERT INTO license_resources (license_id, resource_id, assigned_by)
                            VALUES (%s, %s, %s)
                        """, (license_id, resource_id, session['username']))
                        
                        cur.execute("""
                            INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                            VALUES (%s, %s, 'allocated', %s, %s)
                        """, (resource_id, license_id, session['username'], get_client_ip()))
                
            except ValueError as e:
                conn.rollback()
                flash(str(e), 'error')
                return redirect(url_for('create_license'))

            conn.commit()
            
            # Audit-Log
            log_audit('CREATE', 'license', license_id, 
                     new_values={
                         'license_key': license_key,
                         'customer_name': customer_info['name'],
                         'customer_email': customer_info['email'],
                         'license_type': license_type,
                         'valid_from': valid_from,
                         'valid_until': valid_until,
                         'device_limit': device_limit,
                         'is_test': is_test
                     })
            
            flash(f'Lizenz {license_key} erfolgreich erstellt!', 'success')
            
        except Exception as e:
            conn.rollback()
            logging.error(f"Fehler beim Erstellen der Lizenz: {str(e)}")
            flash('Fehler beim Erstellen der Lizenz!', 'error')
        finally:
            cur.close()
            conn.close()

        # Preserve show_test parameter if present
        redirect_url = "/create"
        if request.args.get('show_test') == 'true':
            redirect_url += "?show_test=true"
        return redirect(redirect_url)

    # Unterstützung für vorausgewählten Kunden
    preselected_customer_id = request.args.get('customer_id', type=int)
    return render_template("index.html", username=session.get('username'), preselected_customer_id=preselected_customer_id)

# @app.route("/batch", methods=["GET", "POST"])
# @login_required
def batch_licenses():
    """Batch-Generierung mehrerer Lizenzen für einen Kunden"""
    if request.method == "POST":
        # Formulardaten
        customer_id = request.form.get("customer_id")
        license_type = request.form["license_type"]
        quantity = int(request.form["quantity"])
        valid_from = request.form["valid_from"]
        is_test = request.form.get("is_test") == "on"  # Checkbox value
        
        # Berechne valid_until basierend auf Laufzeit
        duration = int(request.form.get("duration", 1))
        duration_type = request.form.get("duration_type", "years")
        
        from datetime import datetime, timedelta
        from dateutil.relativedelta import relativedelta
        
        start_date = datetime.strptime(valid_from, "%Y-%m-%d")
        
        if duration_type == "days":
            end_date = start_date + timedelta(days=duration)
        elif duration_type == "months":
            end_date = start_date + relativedelta(months=duration)
        else:  # years
            end_date = start_date + relativedelta(years=duration)
        
        # Ein Tag abziehen, da der Starttag mitgezählt wird
        end_date = end_date - timedelta(days=1)
        valid_until = end_date.strftime("%Y-%m-%d")
        
        # Resource counts
        domain_count = int(request.form.get("domain_count", 1))
        ipv4_count = int(request.form.get("ipv4_count", 1))
        phone_count = int(request.form.get("phone_count", 1))
        device_limit = int(request.form.get("device_limit", 3))
        
        # Sicherheitslimit
        if quantity < 1 or quantity > 100:
            flash('Anzahl muss zwischen 1 und 100 liegen!', 'error')
            return redirect(url_for('batch_licenses'))
        
        conn = get_connection()
        cur = conn.cursor()
        
        try:
            # Prüfe ob neuer Kunde oder bestehender
            if customer_id == "new":
                # Neuer Kunde
                name = request.form.get("customer_name")
                email = request.form.get("email")
                
                if not name:
                    flash('Kundenname ist erforderlich!', 'error')
                    return redirect(url_for('batch_licenses'))
                
                # Prüfe ob E-Mail bereits existiert
                if email:
                    cur.execute("SELECT id, name FROM customers WHERE LOWER(email) = LOWER(%s)", (email,))
                    existing = cur.fetchone()
                    if existing:
                        flash(f'E-Mail bereits vergeben für Kunde: {existing[1]}', 'error')
                        return redirect(url_for('batch_licenses'))
                
                # Kunde einfügen (erbt Test-Status von Lizenz)
                cur.execute("""
                    INSERT INTO customers (name, email, is_test, created_at)
                    VALUES (%s, %s, %s, NOW())
                    RETURNING id
                """, (name, email, is_test))
                customer_id = cur.fetchone()[0]
                
                # Audit-Log für neuen Kunden
                log_audit('CREATE', 'customer', customer_id, 
                         new_values={'name': name, 'email': email, 'is_test': is_test})
            else:
                # Bestehender Kunde - hole Infos
                cur.execute("SELECT name, email, is_test FROM customers WHERE id = %s", (customer_id,))
                customer_data = cur.fetchone()
                if not customer_data:
                    flash('Kunde nicht gefunden!', 'error')
                    return redirect(url_for('batch_licenses'))
                name = customer_data[0]
                email = customer_data[1]
                
                # Wenn Kunde Test-Kunde ist, Lizenzen auch als Test markieren
                if customer_data[2]:  # is_test des Kunden
                    is_test = True
            
            # Prüfe Ressourcen-Verfügbarkeit für gesamten Batch
            total_domains_needed = domain_count * quantity
            total_ipv4s_needed = ipv4_count * quantity
            total_phones_needed = phone_count * quantity
            
            cur.execute("""
                SELECT 
                    (SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'domain' AND status = 'available' AND is_test = %s) as domains,
                    (SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'ipv4' AND status = 'available' AND is_test = %s) as ipv4s,
                    (SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'phone' AND status = 'available' AND is_test = %s) as phones
            """, (is_test, is_test, is_test))
            available = cur.fetchone()
            
            if available[0] < total_domains_needed:
                flash(f"Nicht genügend Domains verfügbar (benötigt: {total_domains_needed}, verfügbar: {available[0]})", 'error')
                return redirect(url_for('batch_licenses'))
            if available[1] < total_ipv4s_needed:
                flash(f"Nicht genügend IPv4-Adressen verfügbar (benötigt: {total_ipv4s_needed}, verfügbar: {available[1]})", 'error')
                return redirect(url_for('batch_licenses'))
            if available[2] < total_phones_needed:
                flash(f"Nicht genügend Telefonnummern verfügbar (benötigt: {total_phones_needed}, verfügbar: {available[2]})", 'error')
                return redirect(url_for('batch_licenses'))
            
            # Lizenzen generieren und speichern
            generated_licenses = []
            for i in range(quantity):
                # Eindeutigen Key generieren
                attempts = 0
                while attempts < 10:
                    license_key = generate_license_key(license_type)
                    cur.execute("SELECT 1 FROM licenses WHERE license_key = %s", (license_key,))
                    if not cur.fetchone():
                        break
                    attempts += 1
                
                # Lizenz einfügen
                cur.execute("""
                    INSERT INTO licenses (license_key, customer_id, license_type, is_test, 
                                        valid_from, valid_until, is_active,
                                        domain_count, ipv4_count, phone_count, device_limit)
                    VALUES (%s, %s, %s, %s, %s, %s, true, %s, %s, %s, %s)
                    RETURNING id
                """, (license_key, customer_id, license_type, is_test, valid_from, valid_until,
                      domain_count, ipv4_count, phone_count, device_limit))
                license_id = cur.fetchone()[0]
                
                # Ressourcen für diese Lizenz zuweisen
                # Domains
                if domain_count > 0:
                    cur.execute("""
                        SELECT id FROM resource_pools
                        WHERE resource_type = 'domain' AND status = 'available' AND is_test = %s
                        LIMIT %s FOR UPDATE
                    """, (is_test, domain_count))
                    for (resource_id,) in cur.fetchall():
                        cur.execute("""
                            UPDATE resource_pools
                            SET status = 'allocated', allocated_to_license = %s,
                                status_changed_at = CURRENT_TIMESTAMP, status_changed_by = %s
                            WHERE id = %s
                        """, (license_id, session['username'], resource_id))
                        
                        cur.execute("""
                            INSERT INTO license_resources (license_id, resource_id, assigned_by)
                            VALUES (%s, %s, %s)
                        """, (license_id, resource_id, session['username']))
                        
                        cur.execute("""
                            INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                            VALUES (%s, %s, 'allocated', %s, %s)
                        """, (resource_id, license_id, session['username'], get_client_ip()))
                
                # IPv4s
                if ipv4_count > 0:
                    cur.execute("""
                        SELECT id FROM resource_pools
                        WHERE resource_type = 'ipv4' AND status = 'available' AND is_test = %s
                        LIMIT %s FOR UPDATE
                    """, (is_test, ipv4_count))
                    for (resource_id,) in cur.fetchall():
                        cur.execute("""
                            UPDATE resource_pools
                            SET status = 'allocated', allocated_to_license = %s,
                                status_changed_at = CURRENT_TIMESTAMP, status_changed_by = %s
                            WHERE id = %s
                        """, (license_id, session['username'], resource_id))
                        
                        cur.execute("""
                            INSERT INTO license_resources (license_id, resource_id, assigned_by)
                            VALUES (%s, %s, %s)
                        """, (license_id, resource_id, session['username']))
                        
                        cur.execute("""
                            INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                            VALUES (%s, %s, 'allocated', %s, %s)
                        """, (resource_id, license_id, session['username'], get_client_ip()))
                
                # Telefonnummern
                if phone_count > 0:
                    cur.execute("""
                        SELECT id FROM resource_pools
                        WHERE resource_type = 'phone' AND status = 'available' AND is_test = %s
                        LIMIT %s FOR UPDATE
                    """, (is_test, phone_count))
                    for (resource_id,) in cur.fetchall():
                        cur.execute("""
                            UPDATE resource_pools
                            SET status = 'allocated', allocated_to_license = %s,
                                status_changed_at = CURRENT_TIMESTAMP, status_changed_by = %s
                            WHERE id = %s
                        """, (license_id, session['username'], resource_id))
                        
                        cur.execute("""
                            INSERT INTO license_resources (license_id, resource_id, assigned_by)
                            VALUES (%s, %s, %s)
                        """, (license_id, resource_id, session['username']))
                        
                        cur.execute("""
                            INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                            VALUES (%s, %s, 'allocated', %s, %s)
                        """, (resource_id, license_id, session['username'], get_client_ip()))
                
                generated_licenses.append({
                    'id': license_id,
                    'key': license_key,
                    'type': license_type
                })
            
            conn.commit()
            
            # Audit-Log
            log_audit('CREATE_BATCH', 'license', 
                     new_values={'customer': name, 'quantity': quantity, 'type': license_type},
                     additional_info=f"Batch-Generierung von {quantity} Lizenzen")
            
            # Session für Export speichern
            session['batch_export'] = {
                'customer': name,
                'email': email,
                'licenses': generated_licenses,
                'valid_from': valid_from,
                'valid_until': valid_until,
                'timestamp': datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None).isoformat()
            }
            
            flash(f'{quantity} Lizenzen erfolgreich generiert!', 'success')
            return render_template("batch_result.html", 
                                 customer=name,
                                 email=email,
                                 licenses=generated_licenses,
                                 valid_from=valid_from,
                                 valid_until=valid_until)
            
        except Exception as e:
            conn.rollback()
            logging.error(f"Fehler bei Batch-Generierung: {str(e)}")
            flash('Fehler bei der Batch-Generierung!', 'error')
            return redirect(url_for('batch_licenses'))
        finally:
            cur.close()
            conn.close()
    
    # GET Request
    return render_template("batch_form.html")

# @app.route("/batch/export")
# @login_required
def export_batch():
    """Exportiert die zuletzt generierten Batch-Lizenzen"""
    batch_data = session.get('batch_export')
    if not batch_data:
        flash('Keine Batch-Daten zum Exportieren vorhanden!', 'error')
        return redirect(url_for('batch_licenses'))
    
    # CSV generieren
    output = io.StringIO()
    output.write('\ufeff')  # UTF-8 BOM für Excel
    
    # Header
    output.write(f"Kunde: {batch_data['customer']}\n")
    output.write(f"E-Mail: {batch_data['email']}\n")
    output.write(f"Generiert am: {datetime.fromisoformat(batch_data['timestamp']).strftime('%d.%m.%Y %H:%M')}\n")
    output.write(f"Gültig von: {batch_data['valid_from']} bis {batch_data['valid_until']}\n")
    output.write("\n")
    output.write("Nr;Lizenzschlüssel;Typ\n")
    
    # Lizenzen
    for i, license in enumerate(batch_data['licenses'], 1):
        typ_text = "Vollversion" if license['type'] == 'full' else "Testversion"
        output.write(f"{i};{license['key']};{typ_text}\n")
    
    output.seek(0)
    
    # Audit-Log
    log_audit('EXPORT', 'batch_licenses', 
             additional_info=f"Export von {len(batch_data['licenses'])} Batch-Lizenzen")
    
    return send_file(
        io.BytesIO(output.getvalue().encode('utf-8-sig')),
        mimetype='text/csv',
        as_attachment=True,
        download_name=f"batch_licenses_{batch_data['customer'].replace(' ', '_')}_{datetime.now(ZoneInfo('Europe/Berlin')).strftime('%Y%m%d_%H%M%S')}.csv"
    )

# @app.route("/licenses")
# @login_required
def licenses():
    # Redirect zur kombinierten Ansicht
    return redirect("/customers-licenses")

# @app.route("/license/edit/<int:license_id>", methods=["GET", "POST"])
# @login_required
def edit_license(license_id):
    conn = get_connection()
    cur = conn.cursor()
    
    if request.method == "POST":
        # Alte Werte für Audit-Log abrufen
        cur.execute("""
            SELECT license_key, license_type, valid_from, valid_until, is_active, is_test, device_limit 
            FROM licenses WHERE id = %s
        """, (license_id,))
        old_license = cur.fetchone()
        
        # Update license
        license_key = request.form["license_key"]
        license_type = request.form["license_type"]
        valid_from = request.form["valid_from"]
        valid_until = request.form["valid_until"]
        is_active = request.form.get("is_active") == "on"
        is_test = request.form.get("is_test") == "on"
        device_limit = int(request.form.get("device_limit", 3))
        
        cur.execute("""
            UPDATE licenses 
            SET license_key = %s, license_type = %s, valid_from = %s, 
                valid_until = %s, is_active = %s, is_test = %s, device_limit = %s
            WHERE id = %s
        """, (license_key, license_type, valid_from, valid_until, is_active, is_test, device_limit, license_id))
        
        conn.commit()
        
        # Audit-Log
        log_audit('UPDATE', 'license', license_id,
                 old_values={
                     'license_key': old_license[0],
                     'license_type': old_license[1],
                     'valid_from': str(old_license[2]),
                     'valid_until': str(old_license[3]),
                     'is_active': old_license[4],
                     'is_test': old_license[5],
                     'device_limit': old_license[6]
                 },
                 new_values={
                     'license_key': license_key,
                     'license_type': license_type,
                     'valid_from': valid_from,
                     'valid_until': valid_until,
                     'is_active': is_active,
                     'is_test': is_test,
                     'device_limit': device_limit
                 })
        
        cur.close()
        conn.close()
        
        # Redirect zurück zu customers-licenses mit beibehaltenen Parametern
        redirect_url = "/customers-licenses"
        
        # Behalte show_test Parameter bei (aus Form oder GET-Parameter)
        show_test = request.form.get('show_test') or request.args.get('show_test')
        if show_test == 'true':
            redirect_url += "?show_test=true"
            
        # Behalte customer_id bei wenn vorhanden
        if request.referrer and 'customer_id=' in request.referrer:
            import re
            match = re.search(r'customer_id=(\d+)', request.referrer)
            if match:
                connector = "&" if "?" in redirect_url else "?"
                redirect_url += f"{connector}customer_id={match.group(1)}"
                
        return redirect(redirect_url)
    
    # Get license data
    cur.execute("""
        SELECT l.id, l.license_key, c.name, c.email, l.license_type, 
               l.valid_from, l.valid_until, l.is_active, c.id, l.is_test, l.device_limit
        FROM licenses l
        JOIN customers c ON l.customer_id = c.id
        WHERE l.id = %s
    """, (license_id,))
    
    license = cur.fetchone()
    cur.close()
    conn.close()
    
    if not license:
        return redirect("/licenses")
    
    return render_template("edit_license.html", license=license, username=session.get('username'))

# @app.route("/license/delete/<int:license_id>", methods=["POST"])
# @login_required
def delete_license(license_id):
    conn = get_connection()
    cur = conn.cursor()
    
    # Lizenzdetails für Audit-Log abrufen
    cur.execute("""
        SELECT l.license_key, c.name, l.license_type 
        FROM licenses l 
        JOIN customers c ON l.customer_id = c.id 
        WHERE l.id = %s
    """, (license_id,))
    license_info = cur.fetchone()
    
    cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,))
    
    conn.commit()
    
    # Audit-Log
    if license_info:
        log_audit('DELETE', 'license', license_id,
                 old_values={
                     'license_key': license_info[0],
                     'customer_name': license_info[1],
                     'license_type': license_info[2]
                 })
    
    cur.close()
    conn.close()
    
    return redirect("/licenses")

# @app.route("/customers")
# @login_required
def customers():
    # Redirect zur kombinierten Ansicht
    return redirect("/customers-licenses")

# @app.route("/customer/edit/<int:customer_id>", methods=["GET", "POST"])
# @login_required
def edit_customer(customer_id):
    conn = get_connection()
    cur = conn.cursor()
    
    if request.method == "POST":
        # Alte Werte für Audit-Log abrufen
        cur.execute("SELECT name, email, is_test FROM customers WHERE id = %s", (customer_id,))
        old_customer = cur.fetchone()
        
        # Update customer
        name = request.form["name"]
        email = request.form["email"]
        is_test = request.form.get("is_test") == "on"
        
        cur.execute("""
            UPDATE customers 
            SET name = %s, email = %s, is_test = %s
            WHERE id = %s
        """, (name, email, is_test, customer_id))
        
        conn.commit()
        
        # Audit-Log
        log_audit('UPDATE', 'customer', customer_id,
                 old_values={
                     'name': old_customer[0],
                     'email': old_customer[1],
                     'is_test': old_customer[2]
                 },
                 new_values={
                     'name': name,
                     'email': email,
                     'is_test': is_test
                 })
        
        cur.close()
        conn.close()
        
        # Redirect zurück zu customers-licenses mit beibehaltenen Parametern
        redirect_url = "/customers-licenses"
        
        # Behalte show_test Parameter bei (aus Form oder GET-Parameter)
        show_test = request.form.get('show_test') or request.args.get('show_test')
        if show_test == 'true':
            redirect_url += "?show_test=true"
            
        # Behalte customer_id bei (immer der aktuelle Kunde)
        connector = "&" if "?" in redirect_url else "?"
        redirect_url += f"{connector}customer_id={customer_id}"
                
        return redirect(redirect_url)
    
    # Get customer data with licenses
    cur.execute("""
        SELECT id, name, email, created_at, is_test FROM customers WHERE id = %s
    """, (customer_id,))
    
    customer = cur.fetchone()
    if not customer:
        cur.close()
        conn.close()
        return "Kunde nicht gefunden", 404
    
    
    # Get customer's licenses
    cur.execute("""
        SELECT id, license_key, license_type, valid_from, valid_until, is_active
        FROM licenses
        WHERE customer_id = %s
        ORDER BY valid_until DESC
    """, (customer_id,))
    
    licenses = cur.fetchall()
    
    cur.close()
    conn.close()
    
    if not customer:
        return redirect("/customers-licenses")
    
    return render_template("edit_customer.html", customer=customer, licenses=licenses, username=session.get('username'))

# @app.route("/customer/create", methods=["GET", "POST"])
# @login_required
def create_customer():
    """Erstellt einen neuen Kunden ohne Lizenz"""
    if request.method == "POST":
        name = request.form.get('name')
        email = request.form.get('email')
        is_test = request.form.get('is_test') == 'on'
        
        if not name or not email:
            flash("Name und E-Mail sind Pflichtfelder!", "error")
            return render_template("create_customer.html", username=session.get('username'))
        
        conn = get_connection()
        cur = conn.cursor()
        
        try:
            # Prüfen ob E-Mail bereits existiert
            cur.execute("SELECT id, name FROM customers WHERE email = %s", (email,))
            existing = cur.fetchone()
            if existing:
                flash(f"Ein Kunde mit der E-Mail '{email}' existiert bereits: {existing[1]}", "error")
                return render_template("create_customer.html", username=session.get('username'))
            
            # Kunde erstellen
            cur.execute("""
                INSERT INTO customers (name, email, created_at, is_test) 
                VALUES (%s, %s, %s, %s) RETURNING id
            """, (name, email, datetime.now(), is_test))
            
            customer_id = cur.fetchone()[0]
            conn.commit()
            
            # Audit-Log
            log_audit('CREATE', 'customer', customer_id,
                     new_values={
                         'name': name,
                         'email': email,
                         'is_test': is_test
                     })
            
            flash(f"Kunde '{name}' wurde erfolgreich angelegt!", "success")
            return redirect(f"/customer/edit/{customer_id}")
            
        except Exception as e:
            conn.rollback()
            flash(f"Fehler beim Anlegen des Kunden: {str(e)}", "error")
            return render_template("create_customer.html", username=session.get('username'))
        finally:
            cur.close()
            conn.close()
    
    # GET Request - Formular anzeigen
    return render_template("create_customer.html", username=session.get('username'))

# @app.route("/customer/delete/<int:customer_id>", methods=["POST"])
# @login_required
def delete_customer(customer_id):
    conn = get_connection()
    cur = conn.cursor()
    
    # Prüfen ob Kunde Lizenzen hat
    cur.execute("SELECT COUNT(*) FROM licenses WHERE customer_id = %s", (customer_id,))
    license_count = cur.fetchone()[0]
    
    if license_count > 0:
        # Kunde hat Lizenzen - nicht löschen
        cur.close()
        conn.close()
        return redirect("/customers")
    
    # Kundendetails für Audit-Log abrufen
    cur.execute("SELECT name, email FROM customers WHERE id = %s", (customer_id,))
    customer_info = cur.fetchone()
    
    # Kunde löschen wenn keine Lizenzen vorhanden
    cur.execute("DELETE FROM customers WHERE id = %s", (customer_id,))
    
    conn.commit()
    
    # Audit-Log
    if customer_info:
        log_audit('DELETE', 'customer', customer_id,
                 old_values={
                     'name': customer_info[0],
                     'email': customer_info[1]
                 })
    
    cur.close()
    conn.close()
    
    return redirect("/customers")

# @app.route("/customers-licenses")
# @login_required
def customers_licenses():
    """Kombinierte Ansicht für Kunden und deren Lizenzen"""
    conn = get_connection()
    cur = conn.cursor()
    
    # Hole alle Kunden mit Lizenzstatistiken (inkl. Testkunden wenn gewünscht)
    show_test = request.args.get('show_test', 'false').lower() == 'true'
    
    query = """
        SELECT 
            c.id,
            c.name,
            c.email,
            c.created_at,
            COUNT(l.id) as total_licenses,
            COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE THEN 1 END) as active_licenses,
            COUNT(CASE WHEN l.valid_until < CURRENT_DATE THEN 1 END) as expired_licenses
        FROM customers c
        LEFT JOIN licenses l ON c.id = l.customer_id
    """
    
    if not show_test:
        query += " WHERE c.is_test = FALSE"
    
    query += """
        GROUP BY c.id, c.name, c.email, c.created_at
        ORDER BY c.name
    """
    
    cur.execute(query)
    customers = cur.fetchall()
    
    # Hole ausgewählten Kunden nur wenn explizit in URL angegeben
    selected_customer_id = request.args.get('customer_id', type=int)
    licenses = []
    selected_customer = None
    
    if customers and selected_customer_id:
        # Hole Daten des ausgewählten Kunden
        for customer in customers:
            if customer[0] == selected_customer_id:
                selected_customer = customer
                break
        
        # Hole Lizenzen des ausgewählten Kunden
        if selected_customer:
            cur.execute("""
                SELECT 
                    l.id,
                    l.license_key,
                    l.license_type,
                    l.valid_from,
                    l.valid_until,
                    l.is_active,
                    CASE 
                        WHEN l.is_active = FALSE THEN 'deaktiviert'
                        WHEN l.valid_until < CURRENT_DATE THEN 'abgelaufen'
                        WHEN l.valid_until < CURRENT_DATE + INTERVAL '30 days' THEN 'läuft bald ab'
                        ELSE 'aktiv'
                    END as status,
                    l.domain_count,
                    l.ipv4_count,
                    l.phone_count,
                    l.device_limit,
                    (SELECT COUNT(*) FROM device_registrations WHERE license_id = l.id AND is_active = TRUE) as active_devices,
                    -- Actual resource counts
                    (SELECT COUNT(*) FROM license_resources lr 
                     JOIN resource_pools rp ON lr.resource_id = rp.id 
                     WHERE lr.license_id = l.id AND lr.is_active = true AND rp.resource_type = 'domain') as actual_domain_count,
                    (SELECT COUNT(*) FROM license_resources lr 
                     JOIN resource_pools rp ON lr.resource_id = rp.id 
                     WHERE lr.license_id = l.id AND lr.is_active = true AND rp.resource_type = 'ipv4') as actual_ipv4_count,
                    (SELECT COUNT(*) FROM license_resources lr 
                     JOIN resource_pools rp ON lr.resource_id = rp.id 
                     WHERE lr.license_id = l.id AND lr.is_active = true AND rp.resource_type = 'phone') as actual_phone_count
                FROM licenses l
                WHERE l.customer_id = %s
                ORDER BY l.created_at DESC, l.id DESC
            """, (selected_customer_id,))
            licenses = cur.fetchall()
    
    cur.close()
    conn.close()
    
    return render_template("customers_licenses.html",
                         customers=customers,
                         selected_customer=selected_customer,
                         selected_customer_id=selected_customer_id,
                         licenses=licenses,
                         show_test=show_test)

# @app.route("/api/customer/<int:customer_id>/licenses")
# @login_required
def api_customer_licenses(customer_id):
    """API-Endpoint für AJAX-Abruf der Lizenzen eines Kunden"""
    conn = get_connection()
    cur = conn.cursor()
    
    # Hole Lizenzen des Kunden
    cur.execute("""
        SELECT 
            l.id,
            l.license_key,
            l.license_type,
            l.valid_from,
            l.valid_until,
            l.is_active,
            CASE 
                WHEN l.is_active = FALSE THEN 'deaktiviert'
                WHEN l.valid_until < CURRENT_DATE THEN 'abgelaufen'
                WHEN l.valid_until < CURRENT_DATE + INTERVAL '30 days' THEN 'läuft bald ab'
                ELSE 'aktiv'
            END as status,
            l.domain_count,
            l.ipv4_count,
            l.phone_count,
            l.device_limit,
            (SELECT COUNT(*) FROM device_registrations WHERE license_id = l.id AND is_active = TRUE) as active_devices,
            -- Actual resource counts
            (SELECT COUNT(*) FROM license_resources lr 
             JOIN resource_pools rp ON lr.resource_id = rp.id 
             WHERE lr.license_id = l.id AND lr.is_active = true AND rp.resource_type = 'domain') as actual_domain_count,
            (SELECT COUNT(*) FROM license_resources lr 
             JOIN resource_pools rp ON lr.resource_id = rp.id 
             WHERE lr.license_id = l.id AND lr.is_active = true AND rp.resource_type = 'ipv4') as actual_ipv4_count,
            (SELECT COUNT(*) FROM license_resources lr 
             JOIN resource_pools rp ON lr.resource_id = rp.id 
             WHERE lr.license_id = l.id AND lr.is_active = true AND rp.resource_type = 'phone') as actual_phone_count
        FROM licenses l
        WHERE l.customer_id = %s
        ORDER BY l.created_at DESC, l.id DESC
    """, (customer_id,))
    
    licenses = []
    for row in cur.fetchall():
        license_id = row[0]
        
        # Hole die konkreten zugewiesenen Ressourcen für diese Lizenz
        cur.execute("""
            SELECT rp.id, rp.resource_type, rp.resource_value, lr.assigned_at
            FROM resource_pools rp
            JOIN license_resources lr ON rp.id = lr.resource_id
            WHERE lr.license_id = %s AND lr.is_active = true
            ORDER BY rp.resource_type, rp.resource_value
        """, (license_id,))
        
        resources = {
            'domains': [],
            'ipv4s': [],
            'phones': []
        }
        
        for res_row in cur.fetchall():
            resource_info = {
                'id': res_row[0],
                'value': res_row[2],
                'assigned_at': res_row[3].strftime('%d.%m.%Y') if res_row[3] else ''
            }
            
            if res_row[1] == 'domain':
                resources['domains'].append(resource_info)
            elif res_row[1] == 'ipv4':
                resources['ipv4s'].append(resource_info)
            elif res_row[1] == 'phone':
                resources['phones'].append(resource_info)
        
        licenses.append({
            'id': row[0],
            'license_key': row[1],
            'license_type': row[2],
            'valid_from': row[3].strftime('%d.%m.%Y') if row[3] else '',
            'valid_until': row[4].strftime('%d.%m.%Y') if row[4] else '',
            'is_active': row[5],
            'status': row[6],
            'domain_count': row[7],  # limit
            'ipv4_count': row[8],     # limit
            'phone_count': row[9],    # limit
            'device_limit': row[10],
            'active_devices': row[11],
            'actual_domain_count': row[12],  # actual count
            'actual_ipv4_count': row[13],    # actual count
            'actual_phone_count': row[14],   # actual count
            'resources': resources
        })
    
    cur.close()
    conn.close()
    
    return jsonify({
        'success': True,
        'licenses': licenses,
        'count': len(licenses)
    })

# @app.route("/api/customer/<int:customer_id>/quick-stats")
# @login_required
def api_customer_quick_stats(customer_id):
    """API-Endpoint für Schnellstatistiken eines Kunden"""
    conn = get_connection()
    cur = conn.cursor()
    
    # Hole Kundenstatistiken
    cur.execute("""
        SELECT 
            COUNT(l.id) as total_licenses,
            COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE THEN 1 END) as active_licenses,
            COUNT(CASE WHEN l.valid_until < CURRENT_DATE THEN 1 END) as expired_licenses,
            COUNT(CASE WHEN l.valid_until < CURRENT_DATE + INTERVAL '30 days' AND l.valid_until >= CURRENT_DATE THEN 1 END) as expiring_soon
        FROM licenses l
        WHERE l.customer_id = %s
    """, (customer_id,))
    
    stats = cur.fetchone()
    
    cur.close()
    conn.close()
    
    return jsonify({
        'success': True,
        'stats': {
            'total': stats[0],
            'active': stats[1],
            'expired': stats[2],
            'expiring_soon': stats[3]
        }
    })

# @app.route("/api/license/<int:license_id>/quick-edit", methods=['POST'])
# @login_required
def api_license_quick_edit(license_id):
    """API-Endpoint für schnelle Lizenz-Bearbeitung"""
    conn = get_connection()
    cur = conn.cursor()
    
    try:
        data = request.get_json()
        
        # Hole alte Werte für Audit-Log
        cur.execute("""
            SELECT is_active, valid_until, license_type 
            FROM licenses WHERE id = %s
        """, (license_id,))
        old_values = cur.fetchone()
        
        if not old_values:
            return jsonify({'success': False, 'error': 'Lizenz nicht gefunden'}), 404
        
        # Update-Felder vorbereiten
        updates = []
        params = []
        new_values = {}
        
        if 'is_active' in data:
            updates.append("is_active = %s")
            params.append(data['is_active'])
            new_values['is_active'] = data['is_active']
        
        if 'valid_until' in data:
            updates.append("valid_until = %s")
            params.append(data['valid_until'])
            new_values['valid_until'] = data['valid_until']
        
        if 'license_type' in data:
            updates.append("license_type = %s")
            params.append(data['license_type'])
            new_values['license_type'] = data['license_type']
        
        if updates:
            params.append(license_id)
            cur.execute(f"""
                UPDATE licenses 
                SET {', '.join(updates)}
                WHERE id = %s
            """, params)
            
            conn.commit()
            
            # Audit-Log
            log_audit('UPDATE', 'license', license_id,
                     old_values={
                         'is_active': old_values[0],
                         'valid_until': old_values[1].isoformat() if old_values[1] else None,
                         'license_type': old_values[2]
                     },
                     new_values=new_values)
        
        cur.close()
        conn.close()
        
        return jsonify({'success': True})
    
    except Exception as e:
        conn.rollback()
        cur.close()
        conn.close()
        return jsonify({'success': False, 'error': str(e)}), 500

# @app.route("/api/license/<int:license_id>/resources")
# @login_required
def api_license_resources(license_id):
    """API-Endpoint für detaillierte Ressourcen-Informationen einer Lizenz"""
    conn = get_connection()
    cur = conn.cursor()
    
    try:
        # Hole die konkreten zugewiesenen Ressourcen für diese Lizenz
        cur.execute("""
            SELECT rp.id, rp.resource_type, rp.resource_value, lr.assigned_at
            FROM resource_pools rp
            JOIN license_resources lr ON rp.id = lr.resource_id
            WHERE lr.license_id = %s AND lr.is_active = true
            ORDER BY rp.resource_type, rp.resource_value
        """, (license_id,))
        
        resources = {
            'domains': [],
            'ipv4s': [],
            'phones': []
        }
        
        for row in cur.fetchall():
            resource_info = {
                'id': row[0],
                'value': row[2],
                'assigned_at': row[3].strftime('%d.%m.%Y') if row[3] else ''
            }
            
            if row[1] == 'domain':
                resources['domains'].append(resource_info)
            elif row[1] == 'ipv4':
                resources['ipv4s'].append(resource_info)
            elif row[1] == 'phone':
                resources['phones'].append(resource_info)
        
        cur.close()
        conn.close()
        
        return jsonify({
            'success': True,
            'resources': resources
        })
        
    except Exception as e:
        cur.close()
        conn.close()
        return jsonify({'success': False, 'error': str(e)}), 500

# @app.route("/sessions")
# @login_required
def sessions():
    conn = get_connection()
    cur = conn.cursor()
    
    # Sortierparameter
    active_sort = request.args.get('active_sort', 'last_heartbeat')
    active_order = request.args.get('active_order', 'desc')
    ended_sort = request.args.get('ended_sort', 'ended_at')
    ended_order = request.args.get('ended_order', 'desc')
    
    # Whitelist für erlaubte Sortierfelder - Aktive Sessions
    active_sort_fields = {
        'customer': 'c.name',
        'license': 'l.license_key',
        'ip': 's.ip_address',
        'started': 's.started_at',
        'last_heartbeat': 's.last_heartbeat',
        'inactive': 'minutes_inactive'
    }
    
    # Whitelist für erlaubte Sortierfelder - Beendete Sessions
    ended_sort_fields = {
        'customer': 'c.name',
        'license': 'l.license_key',
        'ip': 's.ip_address',
        'started': 's.started_at',
        'ended_at': 's.ended_at',
        'duration': 'duration_minutes'
    }
    
    # Validierung
    if active_sort not in active_sort_fields:
        active_sort = 'last_heartbeat'
    if ended_sort not in ended_sort_fields:
        ended_sort = 'ended_at'
    if active_order not in ['asc', 'desc']:
        active_order = 'desc'
    if ended_order not in ['asc', 'desc']:
        ended_order = 'desc'
    
    # Aktive Sessions abrufen
    cur.execute(f"""
        SELECT s.id, s.session_id, l.license_key, c.name, s.ip_address, 
               s.user_agent, s.started_at, s.last_heartbeat,
               EXTRACT(EPOCH FROM (NOW() - s.last_heartbeat))/60 as minutes_inactive
        FROM sessions s
        JOIN licenses l ON s.license_id = l.id
        JOIN customers c ON l.customer_id = c.id
        WHERE s.is_active = TRUE
        ORDER BY {active_sort_fields[active_sort]} {active_order.upper()}
    """)
    active_sessions = cur.fetchall()
    
    # Inaktive Sessions der letzten 24 Stunden
    cur.execute(f"""
        SELECT s.id, s.session_id, l.license_key, c.name, s.ip_address, 
               s.started_at, s.ended_at, 
               EXTRACT(EPOCH FROM (s.ended_at - s.started_at))/60 as duration_minutes
        FROM sessions s
        JOIN licenses l ON s.license_id = l.id
        JOIN customers c ON l.customer_id = c.id
        WHERE s.is_active = FALSE 
        AND s.ended_at > NOW() - INTERVAL '24 hours'
        ORDER BY {ended_sort_fields[ended_sort]} {ended_order.upper()}
        LIMIT 50
    """)
    recent_sessions = cur.fetchall()
    
    cur.close()
    conn.close()
    
    return render_template("sessions.html", 
                         active_sessions=active_sessions,
                         recent_sessions=recent_sessions,
                         active_sort=active_sort,
                         active_order=active_order,
                         ended_sort=ended_sort,
                         ended_order=ended_order,
                         username=session.get('username'))

# @app.route("/session/end/<int:session_id>", methods=["POST"])
# @login_required
def end_session(session_id):
    conn = get_connection()
    cur = conn.cursor()
    
    # Session beenden
    cur.execute("""
        UPDATE sessions 
        SET is_active = FALSE, ended_at = NOW()
        WHERE id = %s AND is_active = TRUE
    """, (session_id,))
    
    conn.commit()
    cur.close()
    conn.close()
    
    return redirect("/sessions")

# @app.route("/export/licenses")
# @login_required
def export_licenses():
    conn = get_connection()
    cur = conn.cursor()
    
    # Alle Lizenzen mit Kundeninformationen abrufen (ohne Testdaten, außer explizit gewünscht)
    include_test = request.args.get('include_test', 'false').lower() == 'true'
    customer_id = request.args.get('customer_id', type=int)
    
    query = """
        SELECT l.id, l.license_key, c.name as customer_name, c.email as customer_email, 
               l.license_type, l.valid_from, l.valid_until, l.is_active, l.is_test,
               CASE 
                   WHEN l.is_active = FALSE THEN 'Deaktiviert'
                   WHEN l.valid_until < CURRENT_DATE THEN 'Abgelaufen'
                   WHEN l.valid_until < CURRENT_DATE + INTERVAL '30 days' THEN 'Läuft bald ab'
                   ELSE 'Aktiv'
               END as status
        FROM licenses l
        JOIN customers c ON l.customer_id = c.id
    """
    
    # Build WHERE clause
    where_conditions = []
    params = []
    
    if not include_test:
        where_conditions.append("l.is_test = FALSE")
    
    if customer_id:
        where_conditions.append("l.customer_id = %s")
        params.append(customer_id)
    
    if where_conditions:
        query += " WHERE " + " AND ".join(where_conditions)
    
    query += " ORDER BY l.id"
    
    cur.execute(query, params)
    
    # Spaltennamen
    columns = ['ID', 'Lizenzschlüssel', 'Kunde', 'E-Mail', 'Typ', 
               'Gültig von', 'Gültig bis', 'Aktiv', 'Testdaten', 'Status']
    
    # Daten in DataFrame
    data = cur.fetchall()
    df = pd.DataFrame(data, columns=columns)
    
    # Datumsformatierung
    df['Gültig von'] = pd.to_datetime(df['Gültig von']).dt.strftime('%d.%m.%Y')
    df['Gültig bis'] = pd.to_datetime(df['Gültig bis']).dt.strftime('%d.%m.%Y')
    
    # Typ und Aktiv Status anpassen
    df['Typ'] = df['Typ'].replace({'full': 'Vollversion', 'test': 'Testversion'})
    df['Aktiv'] = df['Aktiv'].replace({True: 'Ja', False: 'Nein'})
    df['Testdaten'] = df['Testdaten'].replace({True: 'Ja', False: 'Nein'})
    
    cur.close()
    conn.close()
    
    # Export Format
    export_format = request.args.get('format', 'excel')
    
    # Audit-Log
    log_audit('EXPORT', 'license', 
             additional_info=f"Export aller Lizenzen als {export_format.upper()}")
    filename = f'lizenzen_export_{datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y%m%d_%H%M%S")}'
    
    if export_format == 'csv':
        # CSV Export
        output = io.StringIO()
        df.to_csv(output, index=False, encoding='utf-8-sig', sep=';')
        output.seek(0)
        
        return send_file(
            io.BytesIO(output.getvalue().encode('utf-8-sig')),
            mimetype='text/csv',
            as_attachment=True,
            download_name=f'{filename}.csv'
        )
    else:
        # Excel Export
        output = io.BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            df.to_excel(writer, sheet_name='Lizenzen', index=False)
            
            # Formatierung
            worksheet = writer.sheets['Lizenzen']
            for column in worksheet.columns:
                max_length = 0
                column_letter = column[0].column_letter
                for cell in column:
                    try:
                        if len(str(cell.value)) > max_length:
                            max_length = len(str(cell.value))
                    except:
                        pass
                adjusted_width = min(max_length + 2, 50)
                worksheet.column_dimensions[column_letter].width = adjusted_width
        
        output.seek(0)
        
        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'{filename}.xlsx'
        )

# @app.route("/export/audit")
# @login_required
def export_audit():
    conn = get_connection()
    cur = conn.cursor()
    
    # Holen der Filter-Parameter
    filter_user = request.args.get('user', '')
    filter_action = request.args.get('action', '')
    filter_entity = request.args.get('entity', '')
    export_format = request.args.get('format', 'excel')
    
    # SQL Query mit Filtern
    query = """
        SELECT id, timestamp, username, action, entity_type, entity_id, 
               old_values, new_values, ip_address, user_agent, additional_info
        FROM audit_log
        WHERE 1=1
    """
    params = []
    
    if filter_user:
        query += " AND username ILIKE %s"
        params.append(f'%{filter_user}%')
    
    if filter_action:
        query += " AND action = %s"
        params.append(filter_action)
    
    if filter_entity:
        query += " AND entity_type = %s"
        params.append(filter_entity)
    
    query += " ORDER BY timestamp DESC"
    
    cur.execute(query, params)
    audit_logs = cur.fetchall()
    cur.close()
    conn.close()
    
    # Daten für Export vorbereiten
    data = []
    for log in audit_logs:
        action_text = {
            'CREATE': 'Erstellt',
            'UPDATE': 'Bearbeitet',
            'DELETE': 'Gelöscht',
            'LOGIN': 'Anmeldung',
            'LOGOUT': 'Abmeldung',
            'AUTO_LOGOUT': 'Auto-Logout',
            'EXPORT': 'Export',
            'GENERATE_KEY': 'Key generiert',
            'CREATE_BATCH': 'Batch erstellt',
            'BACKUP': 'Backup erstellt',
            'LOGIN_2FA_SUCCESS': '2FA-Anmeldung',
            'LOGIN_2FA_BACKUP': '2FA-Backup-Code',
            'LOGIN_2FA_FAILED': '2FA-Fehlgeschlagen',
            'LOGIN_BLOCKED': 'Login-Blockiert',
            'RESTORE': 'Wiederhergestellt',
            'PASSWORD_CHANGE': 'Passwort geändert',
            '2FA_ENABLED': '2FA aktiviert',
            '2FA_DISABLED': '2FA deaktiviert'
        }.get(log[3], log[3])
        
        data.append({
            'ID': log[0],
            'Zeitstempel': log[1].strftime('%d.%m.%Y %H:%M:%S'),
            'Benutzer': log[2],
            'Aktion': action_text,
            'Entität': log[4],
            'Entität-ID': log[5] or '',
            'IP-Adresse': log[8] or '',
            'Zusatzinfo': log[10] or ''
        })
    
    # DataFrame erstellen
    df = pd.DataFrame(data)
    
    # Timestamp für Dateiname
    timestamp = datetime.now(ZoneInfo("Europe/Berlin")).strftime('%Y%m%d_%H%M%S')
    filename = f'audit_log_export_{timestamp}'
    
    # Audit Log für Export
    log_audit('EXPORT', 'audit_log', 
             additional_info=f"{export_format.upper()} Export mit {len(data)} Einträgen")
    
    if export_format == 'csv':
        # CSV Export
        output = io.StringIO()
        # UTF-8 BOM für Excel
        output.write('\ufeff')
        df.to_csv(output, index=False, sep=';', encoding='utf-8')
        output.seek(0)
        
        return send_file(
            io.BytesIO(output.getvalue().encode('utf-8')),
            mimetype='text/csv;charset=utf-8',
            as_attachment=True,
            download_name=f'{filename}.csv'
        )
    else:
        # Excel Export
        output = BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            df.to_excel(writer, index=False, sheet_name='Audit Log')
            
            # Spaltenbreiten anpassen
            worksheet = writer.sheets['Audit Log']
            for idx, col in enumerate(df.columns):
                max_length = max(
                    df[col].astype(str).map(len).max(),
                    len(col)
                ) + 2
                worksheet.column_dimensions[get_column_letter(idx + 1)].width = min(max_length, 50)
        
        output.seek(0)
        
        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'{filename}.xlsx'
        )

# @app.route("/export/customers")
# @login_required
def export_customers():
    conn = get_connection()
    cur = conn.cursor()
    
    # Check if test data should be included
    include_test = request.args.get('include_test', 'false').lower() == 'true'
    
    # Build query based on test data filter
    if include_test:
        # Include all customers
        query = """
            SELECT c.id, c.name, c.email, c.created_at, c.is_test,
                   COUNT(l.id) as total_licenses,
                   COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE THEN 1 END) as active_licenses,
                   COUNT(CASE WHEN l.valid_until < CURRENT_DATE THEN 1 END) as expired_licenses
            FROM customers c
            LEFT JOIN licenses l ON c.id = l.customer_id
            GROUP BY c.id, c.name, c.email, c.created_at, c.is_test
            ORDER BY c.id
        """
    else:
        # Exclude test customers and test licenses
        query = """
            SELECT c.id, c.name, c.email, c.created_at, c.is_test,
                   COUNT(CASE WHEN l.is_test = FALSE THEN 1 END) as total_licenses,
                   COUNT(CASE WHEN l.is_active = TRUE AND l.valid_until >= CURRENT_DATE AND l.is_test = FALSE THEN 1 END) as active_licenses,
                   COUNT(CASE WHEN l.valid_until < CURRENT_DATE AND l.is_test = FALSE THEN 1 END) as expired_licenses
            FROM customers c
            LEFT JOIN licenses l ON c.id = l.customer_id
            WHERE c.is_test = FALSE
            GROUP BY c.id, c.name, c.email, c.created_at, c.is_test
            ORDER BY c.id
        """
    
    cur.execute(query)
    
    # Spaltennamen
    columns = ['ID', 'Name', 'E-Mail', 'Erstellt am', 'Testdaten',
               'Lizenzen gesamt', 'Aktive Lizenzen', 'Abgelaufene Lizenzen']
    
    # Daten in DataFrame
    data = cur.fetchall()
    df = pd.DataFrame(data, columns=columns)
    
    # Datumsformatierung
    df['Erstellt am'] = pd.to_datetime(df['Erstellt am']).dt.strftime('%d.%m.%Y %H:%M')
    
    # Testdaten formatting
    df['Testdaten'] = df['Testdaten'].replace({True: 'Ja', False: 'Nein'})
    
    cur.close()
    conn.close()
    
    # Export Format
    export_format = request.args.get('format', 'excel')
    
    # Audit-Log
    log_audit('EXPORT', 'customer', 
             additional_info=f"Export aller Kunden als {export_format.upper()}")
    filename = f'kunden_export_{datetime.now(ZoneInfo("Europe/Berlin")).strftime("%Y%m%d_%H%M%S")}'
    
    if export_format == 'csv':
        # CSV Export
        output = io.StringIO()
        df.to_csv(output, index=False, encoding='utf-8-sig', sep=';')
        output.seek(0)
        
        return send_file(
            io.BytesIO(output.getvalue().encode('utf-8-sig')),
            mimetype='text/csv',
            as_attachment=True,
            download_name=f'{filename}.csv'
        )
    else:
        # Excel Export
        output = io.BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            df.to_excel(writer, sheet_name='Kunden', index=False)
            
            # Formatierung
            worksheet = writer.sheets['Kunden']
            for column in worksheet.columns:
                max_length = 0
                column_letter = column[0].column_letter
                for cell in column:
                    try:
                        if len(str(cell.value)) > max_length:
                            max_length = len(str(cell.value))
                    except:
                        pass
                adjusted_width = min(max_length + 2, 50)
                worksheet.column_dimensions[column_letter].width = adjusted_width
        
        output.seek(0)
        
        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'{filename}.xlsx'
        )

# @app.route("/export/sessions")
# @login_required
def export_sessions():
    conn = get_connection()
    cur = conn.cursor()
    
    # Holen des Session-Typs (active oder ended)
    session_type = request.args.get('type', 'active')
    export_format = request.args.get('format', 'excel')
    
    # Daten je nach Typ abrufen
    if session_type == 'active':
        # Aktive Lizenz-Sessions
        cur.execute("""
            SELECT s.id, l.license_key, c.name as customer_name, s.session_id,
                   s.started_at, s.last_heartbeat,
                   EXTRACT(EPOCH FROM (NOW() - s.started_at))::INT as duration_seconds,
                   s.ip_address, s.user_agent
            FROM sessions s
            JOIN licenses l ON s.license_id = l.id
            JOIN customers c ON l.customer_id = c.id
            WHERE s.is_active = true
            ORDER BY s.last_heartbeat DESC
        """)
        sessions = cur.fetchall()
        
        # Daten für Export vorbereiten
        data = []
        for sess in sessions:
            duration = sess[6]
            hours = duration // 3600
            minutes = (duration % 3600) // 60
            seconds = duration % 60
            
            data.append({
                'Session-ID': sess[0],
                'Lizenzschlüssel': sess[1],
                'Kunde': sess[2],
                'Session-ID (Tech)': sess[3],
                'Startzeit': sess[4].strftime('%d.%m.%Y %H:%M:%S'),
                'Letzte Aktivität': sess[5].strftime('%d.%m.%Y %H:%M:%S'),
                'Dauer': f"{hours}h {minutes}m {seconds}s",
                'IP-Adresse': sess[7],
                'Browser': sess[8]
            })
        
        sheet_name = 'Aktive Sessions'
        filename_prefix = 'aktive_sessions'
    else:
        # Beendete Lizenz-Sessions
        cur.execute("""
            SELECT s.id, l.license_key, c.name as customer_name, s.session_id,
                   s.started_at, s.ended_at,
                   EXTRACT(EPOCH FROM (s.ended_at - s.started_at))::INT as duration_seconds,
                   s.ip_address, s.user_agent
            FROM sessions s
            JOIN licenses l ON s.license_id = l.id
            JOIN customers c ON l.customer_id = c.id
            WHERE s.is_active = false AND s.ended_at IS NOT NULL
            ORDER BY s.ended_at DESC
            LIMIT 1000
        """)
        sessions = cur.fetchall()
        
        # Daten für Export vorbereiten
        data = []
        for sess in sessions:
            duration = sess[6] if sess[6] else 0
            hours = duration // 3600
            minutes = (duration % 3600) // 60
            seconds = duration % 60
            
            data.append({
                'Session-ID': sess[0],
                'Lizenzschlüssel': sess[1],
                'Kunde': sess[2],
                'Session-ID (Tech)': sess[3],
                'Startzeit': sess[4].strftime('%d.%m.%Y %H:%M:%S'),
                'Endzeit': sess[5].strftime('%d.%m.%Y %H:%M:%S') if sess[5] else '',
                'Dauer': f"{hours}h {minutes}m {seconds}s",
                'IP-Adresse': sess[7],
                'Browser': sess[8]
            })
        
        sheet_name = 'Beendete Sessions'
        filename_prefix = 'beendete_sessions'
    
    cur.close()
    conn.close()
    
    # DataFrame erstellen
    df = pd.DataFrame(data)
    
    # Timestamp für Dateiname
    timestamp = datetime.now(ZoneInfo("Europe/Berlin")).strftime('%Y%m%d_%H%M%S')
    filename = f'{filename_prefix}_export_{timestamp}'
    
    # Audit Log für Export
    log_audit('EXPORT', 'sessions', 
             additional_info=f"{export_format.upper()} Export von {session_type} Sessions mit {len(data)} Einträgen")
    
    if export_format == 'csv':
        # CSV Export
        output = io.StringIO()
        # UTF-8 BOM für Excel
        output.write('\ufeff')
        df.to_csv(output, index=False, sep=';', encoding='utf-8')
        output.seek(0)
        
        return send_file(
            io.BytesIO(output.getvalue().encode('utf-8')),
            mimetype='text/csv;charset=utf-8',
            as_attachment=True,
            download_name=f'{filename}.csv'
        )
    else:
        # Excel Export
        output = BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            df.to_excel(writer, index=False, sheet_name=sheet_name)
            
            # Spaltenbreiten anpassen
            worksheet = writer.sheets[sheet_name]
            for idx, col in enumerate(df.columns):
                max_length = max(
                    df[col].astype(str).map(len).max(),
                    len(col)
                ) + 2
                worksheet.column_dimensions[get_column_letter(idx + 1)].width = min(max_length, 50)
        
        output.seek(0)
        
        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'{filename}.xlsx'
        )

# @app.route("/export/resources")
# @login_required
def export_resources():
    conn = get_connection()
    cur = conn.cursor()
    
    # Holen der Filter-Parameter
    filter_type = request.args.get('type', '')
    filter_status = request.args.get('status', '')
    search_query = request.args.get('search', '')
    show_test = request.args.get('show_test', 'false').lower() == 'true'
    export_format = request.args.get('format', 'excel')
    
    # SQL Query mit Filtern
    query = """
        SELECT r.id, r.resource_type, r.resource_value, r.status, r.allocated_to_license, 
               r.created_at, r.status_changed_at,
               l.license_key, c.name as customer_name, c.email as customer_email,
               l.license_type
        FROM resource_pools r
        LEFT JOIN licenses l ON r.allocated_to_license = l.id
        LEFT JOIN customers c ON l.customer_id = c.id
        WHERE 1=1
    """
    params = []
    
    # Filter für Testdaten
    if not show_test:
        query += " AND (r.is_test = false OR r.is_test IS NULL)"
    
    # Filter für Ressourcentyp
    if filter_type:
        query += " AND r.resource_type = %s"
        params.append(filter_type)
    
    # Filter für Status
    if filter_status:
        query += " AND r.status = %s"
        params.append(filter_status)
    
    # Suchfilter
    if search_query:
        query += " AND (r.resource_value ILIKE %s OR l.license_key ILIKE %s OR c.name ILIKE %s)"
        params.extend([f'%{search_query}%', f'%{search_query}%', f'%{search_query}%'])
    
    query += " ORDER BY r.id DESC"
    
    cur.execute(query, params)
    resources = cur.fetchall()
    cur.close()
    conn.close()
    
    # Daten für Export vorbereiten
    data = []
    for res in resources:
        status_text = {
            'available': 'Verfügbar',
            'allocated': 'Zugewiesen',
            'quarantine': 'Quarantäne'
        }.get(res[3], res[3])
        
        type_text = {
            'domain': 'Domain',
            'ipv4': 'IPv4',
            'phone': 'Telefon'
        }.get(res[1], res[1])
        
        data.append({
            'ID': res[0],
            'Typ': type_text,
            'Ressource': res[2],
            'Status': status_text,
            'Lizenzschlüssel': res[7] or '',
            'Kunde': res[8] or '',
            'Kunden-Email': res[9] or '',
            'Lizenztyp': res[10] or '',
            'Erstellt am': res[5].strftime('%d.%m.%Y %H:%M:%S') if res[5] else '',
            'Zugewiesen am': res[6].strftime('%d.%m.%Y %H:%M:%S') if res[6] else ''
        })
    
    # DataFrame erstellen
    df = pd.DataFrame(data)
    
    # Timestamp für Dateiname
    timestamp = datetime.now(ZoneInfo("Europe/Berlin")).strftime('%Y%m%d_%H%M%S')
    filename = f'resources_export_{timestamp}'
    
    # Audit Log für Export
    log_audit('EXPORT', 'resources', 
             additional_info=f"{export_format.upper()} Export mit {len(data)} Ressourcen")
    
    if export_format == 'csv':
        # CSV Export
        output = io.StringIO()
        # UTF-8 BOM für Excel
        output.write('\ufeff')
        df.to_csv(output, index=False, sep=';', encoding='utf-8')
        output.seek(0)
        
        return send_file(
            io.BytesIO(output.getvalue().encode('utf-8')),
            mimetype='text/csv;charset=utf-8',
            as_attachment=True,
            download_name=f'{filename}.csv'
        )
    else:
        # Excel Export
        output = BytesIO()
        with pd.ExcelWriter(output, engine='openpyxl') as writer:
            df.to_excel(writer, index=False, sheet_name='Resources')
            
            # Spaltenbreiten anpassen
            worksheet = writer.sheets['Resources']
            for idx, col in enumerate(df.columns):
                max_length = max(
                    df[col].astype(str).map(len).max(),
                    len(col)
                ) + 2
                worksheet.column_dimensions[get_column_letter(idx + 1)].width = min(max_length, 50)
        
        output.seek(0)
        
        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'{filename}.xlsx'
        )

# @app.route("/audit")
# @login_required
def audit_log():
    conn = get_connection()
    cur = conn.cursor()
    
    # Parameter
    filter_user = request.args.get('user', '').strip()
    filter_action = request.args.get('action', '').strip()
    filter_entity = request.args.get('entity', '').strip()
    page = request.args.get('page', 1, type=int)
    sort = request.args.get('sort', 'timestamp')
    order = request.args.get('order', 'desc')
    per_page = 50
    
    # Whitelist für erlaubte Sortierfelder
    allowed_sort_fields = {
        'timestamp': 'timestamp',
        'username': 'username',
        'action': 'action',
        'entity': 'entity_type',
        'ip': 'ip_address'
    }
    
    # Validierung
    if sort not in allowed_sort_fields:
        sort = 'timestamp'
    if order not in ['asc', 'desc']:
        order = 'desc'
        
    sort_field = allowed_sort_fields[sort]
    
    # SQL Query mit optionalen Filtern
    query = """
        SELECT id, timestamp, username, action, entity_type, entity_id, 
               old_values, new_values, ip_address, user_agent, additional_info
        FROM audit_log
        WHERE 1=1
    """
    
    params = []
    
    # Filter
    if filter_user:
        query += " AND LOWER(username) LIKE LOWER(%s)"
        params.append(f'%{filter_user}%')
    
    if filter_action:
        query += " AND action = %s"
        params.append(filter_action)
    
    if filter_entity:
        query += " AND entity_type = %s"
        params.append(filter_entity)
    
    # Gesamtanzahl für Pagination
    count_query = "SELECT COUNT(*) FROM (" + query + ") as count_table"
    cur.execute(count_query, params)
    total = cur.fetchone()[0]
    
    # Pagination
    offset = (page - 1) * per_page
    query += f" ORDER BY {sort_field} {order.upper()} LIMIT %s OFFSET %s"
    params.extend([per_page, offset])
    
    cur.execute(query, params)
    logs = cur.fetchall()
    
    # JSON-Werte parsen
    parsed_logs = []
    for log in logs:
        parsed_log = list(log)
        # old_values und new_values sind bereits Dictionaries (JSONB)
        # Keine Konvertierung nötig
        parsed_logs.append(parsed_log)
    
    # Pagination Info
    total_pages = (total + per_page - 1) // per_page
    
    cur.close()
    conn.close()
    
    return render_template("audit_log.html", 
                         logs=parsed_logs,
                         filter_user=filter_user,
                         filter_action=filter_action,
                         filter_entity=filter_entity,
                         page=page,
                         total_pages=total_pages,
                         total=total,
                         sort=sort,
                         order=order,
                         username=session.get('username'))

# @app.route("/backups")
# @login_required
def backups():
    """Zeigt die Backup-Historie an"""
    conn = get_connection()
    cur = conn.cursor()
    
    # Letztes erfolgreiches Backup für Dashboard
    cur.execute("""
        SELECT created_at, filesize, duration_seconds 
        FROM backup_history 
        WHERE status = 'success' 
        ORDER BY created_at DESC 
        LIMIT 1
    """)
    last_backup = cur.fetchone()
    
    # Alle Backups abrufen
    cur.execute("""
        SELECT id, filename, filesize, backup_type, status, error_message,
               created_at, created_by, tables_count, records_count, 
               duration_seconds, is_encrypted
        FROM backup_history
        ORDER BY created_at DESC
    """)
    backups = cur.fetchall()
    
    cur.close()
    conn.close()
    
    return render_template("backups.html", 
                         backups=backups,
                         last_backup=last_backup,
                         username=session.get('username'))

# @app.route("/backup/create", methods=["POST"])
# @login_required
def create_backup_route():
    """Erstellt ein manuelles Backup"""
    username = session.get('username')
    success, result = create_backup(backup_type="manual", created_by=username)
    
    if success:
        return jsonify({
            'success': True,
            'message': f'Backup erfolgreich erstellt: {result}'
        })
    else:
        return jsonify({
            'success': False,
            'message': f'Backup fehlgeschlagen: {result}'
        }), 500

# @app.route("/backup/restore/<int:backup_id>", methods=["POST"])
# @login_required
def restore_backup_route(backup_id):
    """Stellt ein Backup wieder her"""
    encryption_key = request.form.get('encryption_key')
    
    success, message = restore_backup(backup_id, encryption_key)
    
    if success:
        return jsonify({
            'success': True,
            'message': message
        })
    else:
        return jsonify({
            'success': False,
            'message': message
        }), 500

# @app.route("/backup/download/<int:backup_id>")
# @login_required
def download_backup(backup_id):
    """Lädt eine Backup-Datei herunter"""
    conn = get_connection()
    cur = conn.cursor()
    
    cur.execute("""
        SELECT filename, filepath 
        FROM backup_history 
        WHERE id = %s
    """, (backup_id,))
    backup_info = cur.fetchone()
    
    cur.close()
    conn.close()
    
    if not backup_info:
        return "Backup nicht gefunden", 404
    
    filename, filepath = backup_info
    filepath = Path(filepath)
    
    if not filepath.exists():
        return "Backup-Datei nicht gefunden", 404
    
    # Audit-Log
    log_audit('DOWNLOAD', 'backup', backup_id,
             additional_info=f"Backup heruntergeladen: {filename}")
    
    return send_file(filepath, as_attachment=True, download_name=filename)

# @app.route("/backup/delete/<int:backup_id>", methods=["DELETE"])
# @login_required
def delete_backup(backup_id):
    """Löscht ein Backup"""
    conn = get_connection()
    cur = conn.cursor()
    
    try:
        # Backup-Informationen abrufen
        cur.execute("""
            SELECT filename, filepath 
            FROM backup_history 
            WHERE id = %s
        """, (backup_id,))
        backup_info = cur.fetchone()
        
        if not backup_info:
            return jsonify({
                'success': False,
                'message': 'Backup nicht gefunden'
            }), 404
        
        filename, filepath = backup_info
        filepath = Path(filepath)
        
        # Datei löschen, wenn sie existiert
        if filepath.exists():
            filepath.unlink()
        
        # Aus Datenbank löschen
        cur.execute("""
            DELETE FROM backup_history 
            WHERE id = %s
        """, (backup_id,))
        
        conn.commit()
        
        # Audit-Log
        log_audit('DELETE', 'backup', backup_id,
                 additional_info=f"Backup gelöscht: {filename}")
        
        return jsonify({
            'success': True,
            'message': f'Backup "{filename}" wurde erfolgreich gelöscht'
        })
        
    except Exception as e:
        conn.rollback()
        return jsonify({
            'success': False,
            'message': f'Fehler beim Löschen des Backups: {str(e)}'
        }), 500
    finally:
        cur.close()
        conn.close()

# @app.route("/security/blocked-ips")
# @login_required
def blocked_ips():
    """Zeigt alle gesperrten IPs an"""
    conn = get_connection()
    cur = conn.cursor()
    
    cur.execute("""
        SELECT 
            ip_address,
            attempt_count,
            first_attempt,
            last_attempt,
            blocked_until,
            last_username_tried,
            last_error_message
        FROM login_attempts
        WHERE blocked_until IS NOT NULL
        ORDER BY blocked_until DESC
    """)
    
    blocked_ips_list = []
    for ip in cur.fetchall():
        blocked_ips_list.append({
            'ip_address': ip[0],
            'attempt_count': ip[1],
            'first_attempt': ip[2].strftime('%d.%m.%Y %H:%M'),
            'last_attempt': ip[3].strftime('%d.%m.%Y %H:%M'),
            'blocked_until': ip[4].strftime('%d.%m.%Y %H:%M'),
            'is_active': ip[4] > datetime.now(ZoneInfo("Europe/Berlin")).replace(tzinfo=None),
            'last_username': ip[5],
            'last_error': ip[6]
        })
    
    cur.close()
    conn.close()
    
    return render_template("blocked_ips.html", 
                         blocked_ips=blocked_ips_list,
                         username=session.get('username'))

# @app.route("/security/unblock-ip", methods=["POST"])
# @login_required
def unblock_ip():
    """Entsperrt eine IP-Adresse"""
    ip_address = request.form.get('ip_address')
    
    if ip_address:
        conn = get_connection()
        cur = conn.cursor()
        
        cur.execute("""
            UPDATE login_attempts
            SET blocked_until = NULL
            WHERE ip_address = %s
        """, (ip_address,))
        
        conn.commit()
        cur.close()
        conn.close()
        
        # Audit-Log
        log_audit('UNBLOCK_IP', 'security', 
                 additional_info=f"IP {ip_address} manuell entsperrt")
    
    return redirect(url_for('blocked_ips'))

# @app.route("/security/clear-attempts", methods=["POST"])
# @login_required
def clear_attempts():
    """Löscht alle Login-Versuche für eine IP"""
    ip_address = request.form.get('ip_address')
    
    if ip_address:
        reset_login_attempts(ip_address)
        
        # Audit-Log
        log_audit('CLEAR_ATTEMPTS', 'security', 
                 additional_info=f"Login-Versuche für IP {ip_address} zurückgesetzt")
    
    return redirect(url_for('blocked_ips'))

# API Endpoints for License Management
# @app.route("/api/license/<int:license_id>/toggle", methods=["POST"])
# @login_required
def toggle_license_api(license_id):
    """Toggle license active status via API"""
    try:
        data = request.get_json()
        is_active = data.get('is_active', False)
        
        conn = get_connection()
        cur = conn.cursor()
        
        # Update license status
        cur.execute("""
            UPDATE licenses 
            SET is_active = %s
            WHERE id = %s
        """, (is_active, license_id))
        
        conn.commit()
        
        # Log the action
        log_audit('UPDATE', 'license', license_id,
                 new_values={'is_active': is_active},
                 additional_info=f"Lizenz {'aktiviert' if is_active else 'deaktiviert'} via Toggle")
        
        cur.close()
        conn.close()
        
        return jsonify({'success': True, 'message': 'Status erfolgreich geändert'})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500

# @app.route("/api/licenses/bulk-activate", methods=["POST"])
# @login_required
def bulk_activate_licenses():
    """Activate multiple licenses at once"""
    try:
        data = request.get_json()
        license_ids = data.get('ids', [])
        
        if not license_ids:
            return jsonify({'success': False, 'message': 'Keine Lizenzen ausgewählt'}), 400
        
        conn = get_connection()
        cur = conn.cursor()
        
        # Update all selected licenses (nur Live-Daten)
        cur.execute("""
            UPDATE licenses 
            SET is_active = TRUE
            WHERE id = ANY(%s) AND is_test = FALSE
        """, (license_ids,))
        
        affected_rows = cur.rowcount
        conn.commit()
        
        # Log the bulk action
        log_audit('BULK_UPDATE', 'licenses', None,
                 new_values={'is_active': True, 'count': affected_rows},
                 additional_info=f"{affected_rows} Lizenzen aktiviert")
        
        cur.close()
        conn.close()
        
        return jsonify({'success': True, 'message': f'{affected_rows} Lizenzen aktiviert'})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500

# @app.route("/api/licenses/bulk-deactivate", methods=["POST"])
# @login_required
def bulk_deactivate_licenses():
    """Deactivate multiple licenses at once"""
    try:
        data = request.get_json()
        license_ids = data.get('ids', [])
        
        if not license_ids:
            return jsonify({'success': False, 'message': 'Keine Lizenzen ausgewählt'}), 400
        
        conn = get_connection()
        cur = conn.cursor()
        
        # Update all selected licenses (nur Live-Daten)
        cur.execute("""
            UPDATE licenses 
            SET is_active = FALSE
            WHERE id = ANY(%s) AND is_test = FALSE
        """, (license_ids,))
        
        affected_rows = cur.rowcount
        conn.commit()
        
        # Log the bulk action
        log_audit('BULK_UPDATE', 'licenses', None,
                 new_values={'is_active': False, 'count': affected_rows},
                 additional_info=f"{affected_rows} Lizenzen deaktiviert")
        
        cur.close()
        conn.close()
        
        return jsonify({'success': True, 'message': f'{affected_rows} Lizenzen deaktiviert'})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500

# @app.route("/api/license/<int:license_id>/devices")
# @login_required
def get_license_devices(license_id):
    """Hole alle registrierten Geräte einer Lizenz"""
    try:
        conn = get_connection()
        cur = conn.cursor()
        
        # Prüfe ob Lizenz existiert und hole device_limit
        cur.execute("""
            SELECT device_limit FROM licenses WHERE id = %s
        """, (license_id,))
        license_data = cur.fetchone()
        
        if not license_data:
            return jsonify({'success': False, 'message': 'Lizenz nicht gefunden'}), 404
        
        device_limit = license_data[0]
        
        # Hole alle Geräte für diese Lizenz
        cur.execute("""
            SELECT id, hardware_id, device_name, operating_system,
                   first_seen, last_seen, is_active, ip_address
            FROM device_registrations
            WHERE license_id = %s
            ORDER BY is_active DESC, last_seen DESC
        """, (license_id,))
        
        devices = []
        for row in cur.fetchall():
            devices.append({
                'id': row[0],
                'hardware_id': row[1],
                'device_name': row[2] or 'Unbekanntes Gerät',
                'operating_system': row[3] or 'Unbekannt',
                'first_seen': row[4].strftime('%d.%m.%Y %H:%M') if row[4] else '',
                'last_seen': row[5].strftime('%d.%m.%Y %H:%M') if row[5] else '',
                'is_active': row[6],
                'ip_address': row[7] or '-'
            })
        
        cur.close()
        conn.close()
        
        return jsonify({
            'success': True,
            'devices': devices,
            'device_limit': device_limit,
            'active_count': sum(1 for d in devices if d['is_active'])
        })
        
    except Exception as e:
        logging.error(f"Fehler beim Abrufen der Geräte: {str(e)}")
        return jsonify({'success': False, 'message': 'Fehler beim Abrufen der Geräte'}), 500

# @app.route("/api/license/<int:license_id>/register-device", methods=["POST"])
# def register_device(license_id):
    # """Registriere ein neues Gerät für eine Lizenz"""
    # try:
        # data = request.get_json()
        # hardware_id = data.get('hardware_id')
        # device_name = data.get('device_name', '')
        # operating_system = data.get('operating_system', '')
        
        # if not hardware_id:
            # return jsonify({'success': False, 'message': 'Hardware-ID fehlt'}), 400
        
        # conn = get_connection()
        # cur = conn.cursor()
        
        # Prüfe ob Lizenz existiert und aktiv ist
        # cur.execute("""
            # SELECT device_limit, is_active, valid_until
            # FROM licenses 
            # WHERE id = %s
        # """, (license_id,))
        # license_data = cur.fetchone()
        
        # if not license_data:
            # return jsonify({'success': False, 'message': 'Lizenz nicht gefunden'}), 404
        
        # device_limit, is_active, valid_until = license_data
        
        # Prüfe ob Lizenz aktiv und gültig ist
        # if not is_active:
            # return jsonify({'success': False, 'message': 'Lizenz ist deaktiviert'}), 403
        
        # if valid_until < datetime.now(ZoneInfo("Europe/Berlin")).date():
            # return jsonify({'success': False, 'message': 'Lizenz ist abgelaufen'}), 403
        
        # Prüfe ob Gerät bereits registriert ist
        # cur.execute("""
            # SELECT id, is_active FROM device_registrations
            # WHERE license_id = %s AND hardware_id = %s
        # """, (license_id, hardware_id))
        # existing_device = cur.fetchone()
        
        # if existing_device:
            # device_id, is_device_active = existing_device
            # if is_device_active:
                # Gerät ist bereits aktiv, update last_seen
                # cur.execute("""
                    # UPDATE device_registrations
                    # SET last_seen = CURRENT_TIMESTAMP,
                        # ip_address = %s,
                        # user_agent = %s
                    # WHERE id = %s
                # """, (get_client_ip(), request.headers.get('User-Agent', ''), device_id))
                # conn.commit()
                # return jsonify({'success': True, 'message': 'Gerät bereits registriert', 'device_id': device_id})
            # else:
                # Gerät war deaktiviert, prüfe ob wir es reaktivieren können
                # cur.execute("""
                    # SELECT COUNT(*) FROM device_registrations
                    # WHERE license_id = %s AND is_active = TRUE
                # """, (license_id,))
                # active_count = cur.fetchone()[0]
                
                # if active_count >= device_limit:
                    # return jsonify({'success': False, 'message': f'Gerätelimit erreicht ({device_limit} Geräte)'}), 403
                
                # Reaktiviere das Gerät
                # cur.execute("""
                    # UPDATE device_registrations
                    # SET is_active = TRUE,
                        # last_seen = CURRENT_TIMESTAMP,
                        # deactivated_at = NULL,
                        # deactivated_by = NULL,
                        # ip_address = %s,
                        # user_agent = %s
                    # WHERE id = %s
                # """, (get_client_ip(), request.headers.get('User-Agent', ''), device_id))
                # conn.commit()
                # return jsonify({'success': True, 'message': 'Gerät reaktiviert', 'device_id': device_id})
        
        # Neues Gerät - prüfe Gerätelimit
        # cur.execute("""
            # SELECT COUNT(*) FROM device_registrations
            # WHERE license_id = %s AND is_active = TRUE
        # """, (license_id,))
        # active_count = cur.fetchone()[0]
        
        # if active_count >= device_limit:
            # return jsonify({'success': False, 'message': f'Gerätelimit erreicht ({device_limit} Geräte)'}), 403
        
        # Registriere neues Gerät
        # cur.execute("""
            # INSERT INTO device_registrations 
            # (license_id, hardware_id, device_name, operating_system, ip_address, user_agent)
            # VALUES (%s, %s, %s, %s, %s, %s)
            # RETURNING id
        # """, (license_id, hardware_id, device_name, operating_system, 
              # get_client_ip(), request.headers.get('User-Agent', '')))
        # device_id = cur.fetchone()[0]
        
        # conn.commit()
        
        # Audit Log
        # log_audit('DEVICE_REGISTER', 'device', device_id,
                 # new_values={'license_id': license_id, 'hardware_id': hardware_id})
        
        # cur.close()
        # conn.close()
        
        # return jsonify({'success': True, 'message': 'Gerät erfolgreich registriert', 'device_id': device_id})
        
    # except Exception as e:
        # logging.error(f"Fehler bei Geräte-Registrierung: {str(e)}")
        # return jsonify({'success': False, 'message': 'Fehler bei der Registrierung'}), 500

# @app.route("/api/license/<int:license_id>/deactivate-device/<int:device_id>", methods=["POST"])
# @login_required
def deactivate_device(license_id, device_id):
    """Deaktiviere ein registriertes Gerät"""
    try:
        conn = get_connection()
        cur = conn.cursor()
        
        # Prüfe ob das Gerät zu dieser Lizenz gehört
        cur.execute("""
            SELECT id FROM device_registrations
            WHERE id = %s AND license_id = %s AND is_active = TRUE
        """, (device_id, license_id))
        
        if not cur.fetchone():
            return jsonify({'success': False, 'message': 'Gerät nicht gefunden oder bereits deaktiviert'}), 404
        
        # Deaktiviere das Gerät
        cur.execute("""
            UPDATE device_registrations
            SET is_active = FALSE,
                deactivated_at = CURRENT_TIMESTAMP,
                deactivated_by = %s
            WHERE id = %s
        """, (session['username'], device_id))
        
        conn.commit()
        
        # Audit Log
        log_audit('DEVICE_DEACTIVATE', 'device', device_id,
                 old_values={'is_active': True},
                 new_values={'is_active': False})
        
        cur.close()
        conn.close()
        
        return jsonify({'success': True, 'message': 'Gerät erfolgreich deaktiviert'})
        
    except Exception as e:
        logging.error(f"Fehler beim Deaktivieren des Geräts: {str(e)}")
        return jsonify({'success': False, 'message': 'Fehler beim Deaktivieren'}), 500

# @app.route("/api/licenses/bulk-delete", methods=["POST"])
# @login_required
def bulk_delete_licenses():
    """Delete multiple licenses at once"""
    try:
        data = request.get_json()
        license_ids = data.get('ids', [])
        
        if not license_ids:
            return jsonify({'success': False, 'message': 'Keine Lizenzen ausgewählt'}), 400
        
        conn = get_connection()
        cur = conn.cursor()
        
        # Get license info for audit log (nur Live-Daten)
        cur.execute("""
            SELECT license_key 
            FROM licenses 
            WHERE id = ANY(%s) AND is_test = FALSE
        """, (license_ids,))
        license_keys = [row[0] for row in cur.fetchall()]
        
        # Delete all selected licenses (nur Live-Daten)
        cur.execute("""
            DELETE FROM licenses 
            WHERE id = ANY(%s) AND is_test = FALSE
        """, (license_ids,))
        
        affected_rows = cur.rowcount
        conn.commit()
        
        # Log the bulk action
        log_audit('BULK_DELETE', 'licenses', None,
                 old_values={'license_keys': license_keys, 'count': affected_rows},
                 additional_info=f"{affected_rows} Lizenzen gelöscht")
        
        cur.close()
        conn.close()
        
        return jsonify({'success': True, 'message': f'{affected_rows} Lizenzen gelöscht'})
    except Exception as e:
        return jsonify({'success': False, 'message': str(e)}), 500

# ===================== RESOURCE POOL MANAGEMENT =====================

# @app.route('/resources')
# @login_required
def resources():
    """Resource Pool Hauptübersicht"""
    conn = get_connection()
    cur = conn.cursor()
    
    # Prüfe ob Testdaten angezeigt werden sollen (gleiche Logik wie bei Kunden)
    show_test = request.args.get('show_test', 'false').lower() == 'true'
    
    # Statistiken abrufen
    cur.execute("""
        SELECT 
            resource_type,
            COUNT(*) FILTER (WHERE status = 'available') as available,
            COUNT(*) FILTER (WHERE status = 'allocated') as allocated,
            COUNT(*) FILTER (WHERE status = 'quarantine') as quarantine,
            COUNT(*) as total
        FROM resource_pools
        WHERE is_test = %s
        GROUP BY resource_type
    """, (show_test,))
    
    stats = {}
    for row in cur.fetchall():
        stats[row[0]] = {
            'available': row[1],
            'allocated': row[2],
            'quarantine': row[3],
            'total': row[4],
            'available_percent': round((row[1] / row[4] * 100) if row[4] > 0 else 0, 1)
        }
    
    # Letzte Aktivitäten (gefiltert nach Test/Live)
    cur.execute("""
        SELECT 
            rh.action,
            rh.action_by,
            rh.action_at,
            rp.resource_type,
            rp.resource_value,
            rh.details
        FROM resource_history rh
        JOIN resource_pools rp ON rh.resource_id = rp.id
        WHERE rp.is_test = %s
        ORDER BY rh.action_at DESC
        LIMIT 10
    """, (show_test,))
    recent_activities = cur.fetchall()
    
    # Ressourcen-Liste mit Pagination
    page = request.args.get('page', 1, type=int)
    per_page = 50
    offset = (page - 1) * per_page
    
    resource_type = request.args.get('type', '')
    status_filter = request.args.get('status', '')
    search = request.args.get('search', '')
    
    # Sortierung
    sort_by = request.args.get('sort', 'id')
    sort_order = request.args.get('order', 'desc')
    
    # Base Query
    query = """
        SELECT 
            rp.id,
            rp.resource_type,
            rp.resource_value,
            rp.status,
            rp.allocated_to_license,
            l.license_key,
            c.name as customer_name,
            rp.status_changed_at,
            rp.quarantine_reason,
            rp.quarantine_until,
            c.id as customer_id
        FROM resource_pools rp
        LEFT JOIN licenses l ON rp.allocated_to_license = l.id
        LEFT JOIN customers c ON l.customer_id = c.id
        WHERE rp.is_test = %s
    """
    params = [show_test]
    
    if resource_type:
        query += " AND rp.resource_type = %s"
        params.append(resource_type)
    
    if status_filter:
        query += " AND rp.status = %s"
        params.append(status_filter)
    
    if search:
        query += " AND rp.resource_value ILIKE %s"
        params.append(f'%{search}%')
    
    # Count total
    count_query = f"SELECT COUNT(*) FROM ({query}) as cnt"
    cur.execute(count_query, params)
    total = cur.fetchone()[0]
    total_pages = (total + per_page - 1) // per_page
    
    # Get paginated results with dynamic sorting
    sort_column_map = {
        'id': 'rp.id',
        'type': 'rp.resource_type',
        'resource': 'rp.resource_value',
        'status': 'rp.status',
        'assigned': 'c.name',
        'changed': 'rp.status_changed_at'
    }
    
    sort_column = sort_column_map.get(sort_by, 'rp.id')
    sort_direction = 'ASC' if sort_order == 'asc' else 'DESC'
    
    query += f" ORDER BY {sort_column} {sort_direction} LIMIT %s OFFSET %s"
    params.extend([per_page, offset])
    
    cur.execute(query, params)
    resources = cur.fetchall()
    
    cur.close()
    conn.close()
    
    return render_template('resources.html',
                         stats=stats,
                         resources=resources,
                         recent_activities=recent_activities,
                         page=page,
                         total_pages=total_pages,
                         total=total,
                         resource_type=resource_type,
                         status_filter=status_filter,
                         search=search,
                         show_test=show_test,
                         sort_by=sort_by,
                         sort_order=sort_order,
                         datetime=datetime,
                         timedelta=timedelta)

# @app.route('/resources/add', methods=['GET', 'POST'])
# @login_required
def add_resources():
    """Ressourcen zum Pool hinzufügen"""
    # Hole show_test Parameter für die Anzeige
    show_test = request.args.get('show_test', 'false').lower() == 'true'
    
    if request.method == 'POST':
        resource_type = request.form.get('resource_type')
        resources_text = request.form.get('resources_text', '')
        is_test = request.form.get('is_test') == 'on'  # Checkbox für Testdaten
        
        # Parse resources (one per line)
        resources = [r.strip() for r in resources_text.split('\n') if r.strip()]
        
        if not resources:
            flash('Keine Ressourcen angegeben', 'error')
            return redirect(url_for('add_resources', show_test=show_test))
        
        conn = get_connection()
        cur = conn.cursor()
        
        added = 0
        duplicates = 0
        
        for resource_value in resources:
            try:
                cur.execute("""
                    INSERT INTO resource_pools (resource_type, resource_value, status_changed_by, is_test)
                    VALUES (%s, %s, %s, %s)
                    ON CONFLICT (resource_type, resource_value) DO NOTHING
                """, (resource_type, resource_value, session['username'], is_test))
                
                if cur.rowcount > 0:
                    added += 1
                    # Get the inserted ID
                    cur.execute("SELECT id FROM resource_pools WHERE resource_type = %s AND resource_value = %s", 
                              (resource_type, resource_value))
                    resource_id = cur.fetchone()[0]
                    
                    # Log in history
                    cur.execute("""
                        INSERT INTO resource_history (resource_id, action, action_by, ip_address)
                        VALUES (%s, 'created', %s, %s)
                    """, (resource_id, session['username'], get_client_ip()))
                else:
                    duplicates += 1
                    
            except Exception as e:
                app.logger.error(f"Error adding resource {resource_value}: {e}")
        
        conn.commit()
        cur.close()
        conn.close()
        
        log_audit('CREATE', 'resource_pool', None,
                 new_values={'type': resource_type, 'added': added, 'duplicates': duplicates, 'is_test': is_test},
                 additional_info=f"{added} {'Test-' if is_test else ''}Ressourcen hinzugefügt, {duplicates} Duplikate übersprungen")
        
        flash(f'{added} {"Test-" if is_test else ""}Ressourcen hinzugefügt, {duplicates} Duplikate übersprungen', 'success')
        return redirect(url_for('resources', show_test=show_test))
    
    return render_template('add_resources.html', show_test=show_test)

# @app.route('/resources/quarantine/<int:resource_id>', methods=['POST'])
# @login_required
def quarantine_resource(resource_id):
    """Ressource in Quarantäne setzen"""
    reason = request.form.get('reason', 'review')
    until_date = request.form.get('until_date')
    notes = request.form.get('notes', '')
    
    conn = get_connection()
    cur = conn.cursor()
    
    # Get current resource info
    cur.execute("SELECT resource_type, resource_value, status FROM resource_pools WHERE id = %s", (resource_id,))
    resource = cur.fetchone()
    
    if not resource:
        flash('Ressource nicht gefunden', 'error')
        return redirect(url_for('resources'))
    
    old_status = resource[2]
    
    # Update resource
    cur.execute("""
        UPDATE resource_pools
        SET status = 'quarantine',
            quarantine_reason = %s,
            quarantine_until = %s,
            notes = %s,
            status_changed_at = CURRENT_TIMESTAMP,
            status_changed_by = %s
        WHERE id = %s
    """, (reason, until_date if until_date else None, notes, session['username'], resource_id))
    
    # Log in history
    cur.execute("""
        INSERT INTO resource_history (resource_id, action, action_by, ip_address, details)
        VALUES (%s, 'quarantined', %s, %s, %s)
    """, (resource_id, session['username'], get_client_ip(), 
          Json({'reason': reason, 'until': until_date, 'notes': notes, 'old_status': old_status})))
    
    conn.commit()
    cur.close()
    conn.close()
    
    log_audit('UPDATE', 'resource', resource_id,
             old_values={'status': old_status},
             new_values={'status': 'quarantine', 'reason': reason},
             additional_info=f"Ressource {resource[0]}: {resource[1]} in Quarantäne")
    
    flash('Ressource in Quarantäne gesetzt', 'success')
    
    # Redirect mit allen aktuellen Filtern
    return redirect(url_for('resources', 
                          show_test=request.args.get('show_test', request.form.get('show_test', 'false')),
                          type=request.args.get('type', request.form.get('type', '')),
                          status=request.args.get('status', request.form.get('status', '')),
                          search=request.args.get('search', request.form.get('search', ''))))

# @app.route('/resources/release', methods=['POST'])
# @login_required
def release_resources():
    """Ressourcen aus Quarantäne freigeben"""
    resource_ids = request.form.getlist('resource_ids')
    
    if not resource_ids:
        flash('Keine Ressourcen ausgewählt', 'error')
        return redirect(url_for('resources'))
    
    conn = get_connection()
    cur = conn.cursor()
    
    released = 0
    for resource_id in resource_ids:
        cur.execute("""
            UPDATE resource_pools
            SET status = 'available',
                quarantine_reason = NULL,
                quarantine_until = NULL,
                allocated_to_license = NULL,
                status_changed_at = CURRENT_TIMESTAMP,
                status_changed_by = %s
            WHERE id = %s AND status = 'quarantine'
        """, (session['username'], resource_id))
        
        if cur.rowcount > 0:
            released += 1
            # Log in history
            cur.execute("""
                INSERT INTO resource_history (resource_id, action, action_by, ip_address)
                VALUES (%s, 'released', %s, %s)
            """, (resource_id, session['username'], get_client_ip()))
    
    conn.commit()
    cur.close()
    conn.close()
    
    log_audit('UPDATE', 'resource_pool', None,
             new_values={'released': released},
             additional_info=f"{released} Ressourcen aus Quarantäne freigegeben")
    
    flash(f'{released} Ressourcen freigegeben', 'success')
    
    # Redirect mit allen aktuellen Filtern
    return redirect(url_for('resources', 
                          show_test=request.args.get('show_test', request.form.get('show_test', 'false')),
                          type=request.args.get('type', request.form.get('type', '')),
                          status=request.args.get('status', request.form.get('status', '')),
                          search=request.args.get('search', request.form.get('search', ''))))

# @app.route('/api/resources/allocate', methods=['POST'])
# @login_required
def allocate_resources_api():
    """API für Ressourcen-Zuweisung bei Lizenzerstellung"""
    data = request.json
    license_id = data.get('license_id')
    domain_count = data.get('domain_count', 1)
    ipv4_count = data.get('ipv4_count', 1)
    phone_count = data.get('phone_count', 1)
    
    conn = get_connection()
    cur = conn.cursor()
    
    try:
        allocated = {'domains': [], 'ipv4s': [], 'phones': []}
        
        # Allocate domains
        if domain_count > 0:
            cur.execute("""
                SELECT id, resource_value FROM resource_pools
                WHERE resource_type = 'domain' AND status = 'available'
                LIMIT %s FOR UPDATE
            """, (domain_count,))
            domains = cur.fetchall()
            
            if len(domains) < domain_count:
                raise ValueError(f"Nicht genügend Domains verfügbar (benötigt: {domain_count}, verfügbar: {len(domains)})")
            
            for domain_id, domain_value in domains:
                # Update resource status
                cur.execute("""
                    UPDATE resource_pools
                    SET status = 'allocated',
                        allocated_to_license = %s,
                        status_changed_at = CURRENT_TIMESTAMP,
                        status_changed_by = %s
                    WHERE id = %s
                """, (license_id, session['username'], domain_id))
                
                # Create assignment
                cur.execute("""
                    INSERT INTO license_resources (license_id, resource_id, assigned_by)
                    VALUES (%s, %s, %s)
                """, (license_id, domain_id, session['username']))
                
                # Log history
                cur.execute("""
                    INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                    VALUES (%s, %s, 'allocated', %s, %s)
                """, (domain_id, license_id, session['username'], get_client_ip()))
                
                allocated['domains'].append(domain_value)
        
        # Allocate IPv4s (similar logic)
        if ipv4_count > 0:
            cur.execute("""
                SELECT id, resource_value FROM resource_pools
                WHERE resource_type = 'ipv4' AND status = 'available'
                LIMIT %s FOR UPDATE
            """, (ipv4_count,))
            ipv4s = cur.fetchall()
            
            if len(ipv4s) < ipv4_count:
                raise ValueError(f"Nicht genügend IPv4-Adressen verfügbar")
            
            for ipv4_id, ipv4_value in ipv4s:
                cur.execute("""
                    UPDATE resource_pools
                    SET status = 'allocated',
                        allocated_to_license = %s,
                        status_changed_at = CURRENT_TIMESTAMP,
                        status_changed_by = %s
                    WHERE id = %s
                """, (license_id, session['username'], ipv4_id))
                
                cur.execute("""
                    INSERT INTO license_resources (license_id, resource_id, assigned_by)
                    VALUES (%s, %s, %s)
                """, (license_id, ipv4_id, session['username']))
                
                cur.execute("""
                    INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                    VALUES (%s, %s, 'allocated', %s, %s)
                """, (ipv4_id, license_id, session['username'], get_client_ip()))
                
                allocated['ipv4s'].append(ipv4_value)
        
        # Allocate phones (similar logic)
        if phone_count > 0:
            cur.execute("""
                SELECT id, resource_value FROM resource_pools
                WHERE resource_type = 'phone' AND status = 'available'
                LIMIT %s FOR UPDATE
            """, (phone_count,))
            phones = cur.fetchall()
            
            if len(phones) < phone_count:
                raise ValueError(f"Nicht genügend Telefonnummern verfügbar")
            
            for phone_id, phone_value in phones:
                cur.execute("""
                    UPDATE resource_pools
                    SET status = 'allocated',
                        allocated_to_license = %s,
                        status_changed_at = CURRENT_TIMESTAMP,
                        status_changed_by = %s
                    WHERE id = %s
                """, (license_id, session['username'], phone_id))
                
                cur.execute("""
                    INSERT INTO license_resources (license_id, resource_id, assigned_by)
                    VALUES (%s, %s, %s)
                """, (license_id, phone_id, session['username']))
                
                cur.execute("""
                    INSERT INTO resource_history (resource_id, license_id, action, action_by, ip_address)
                    VALUES (%s, %s, 'allocated', %s, %s)
                """, (phone_id, license_id, session['username'], get_client_ip()))
                
                allocated['phones'].append(phone_value)
        
        # Update license resource counts
        cur.execute("""
            UPDATE licenses
            SET domain_count = %s,
                ipv4_count = %s,
                phone_count = %s
            WHERE id = %s
        """, (domain_count, ipv4_count, phone_count, license_id))
        
        conn.commit()
        cur.close()
        conn.close()
        
        return jsonify({
            'success': True,
            'allocated': allocated
        })
        
    except Exception as e:
        conn.rollback()
        cur.close()
        conn.close()
        return jsonify({
            'success': False,
            'error': str(e)
        }), 400

# @app.route('/api/resources/check-availability', methods=['GET'])
# @login_required
def check_resource_availability():
    """Prüft verfügbare Ressourcen"""
    resource_type = request.args.get('type', '')
    count = request.args.get('count', 10, type=int)
    show_test = request.args.get('show_test', 'false').lower() == 'true'
    
    conn = get_connection()
    cur = conn.cursor()
    
    if resource_type:
        # Spezifische Ressourcen für einen Typ
        cur.execute("""
            SELECT id, resource_value
            FROM resource_pools
            WHERE status = 'available' 
            AND resource_type = %s
            AND is_test = %s
            ORDER BY resource_value
            LIMIT %s
        """, (resource_type, show_test, count))
        
        resources = []
        for row in cur.fetchall():
            resources.append({
                'id': row[0],
                'value': row[1]
            })
        
        cur.close()
        conn.close()
        
        return jsonify({
            'available': resources,
            'type': resource_type,
            'count': len(resources)
        })
    else:
        # Zusammenfassung aller Typen
        cur.execute("""
            SELECT 
                resource_type,
                COUNT(*) as available
            FROM resource_pools
            WHERE status = 'available'
            AND is_test = %s
            GROUP BY resource_type
        """, (show_test,))
        
        availability = {}
        for row in cur.fetchall():
            availability[row[0]] = row[1]
        
        cur.close()
        conn.close()
        
        return jsonify(availability)

# @app.route('/api/global-search', methods=['GET'])
# @login_required
def global_search():
    """Global search API endpoint for searching customers and licenses"""
    query = request.args.get('q', '').strip()
    
    if not query or len(query) < 2:
        return jsonify({'customers': [], 'licenses': []})
    
    conn = get_connection()
    cur = conn.cursor()
    
    # Search pattern with wildcards
    search_pattern = f'%{query}%'
    
    # Search customers
    cur.execute("""
        SELECT id, name, email, company_name
        FROM customers
        WHERE (LOWER(name) LIKE LOWER(%s) 
               OR LOWER(email) LIKE LOWER(%s)
               OR LOWER(company_name) LIKE LOWER(%s))
        AND is_test = FALSE
        ORDER BY name
        LIMIT 5
    """, (search_pattern, search_pattern, search_pattern))
    
    customers = []
    for row in cur.fetchall():
        customers.append({
            'id': row[0],
            'name': row[1],
            'email': row[2],
            'company_name': row[3]
        })
    
    # Search licenses
    cur.execute("""
        SELECT l.id, l.license_key, c.name as customer_name
        FROM licenses l
        JOIN customers c ON l.customer_id = c.id
        WHERE LOWER(l.license_key) LIKE LOWER(%s)
        AND l.is_test = FALSE
        ORDER BY l.created_at DESC
        LIMIT 5
    """, (search_pattern,))
    
    licenses = []
    for row in cur.fetchall():
        licenses.append({
            'id': row[0],
            'license_key': row[1],
            'customer_name': row[2]
        })
    
    cur.close()
    conn.close()
    
    return jsonify({
        'customers': customers,
        'licenses': licenses
    })

# @app.route('/resources/history/<int:resource_id>')
# @login_required
def resource_history(resource_id):
    """Zeigt die komplette Historie einer Ressource"""
    conn = get_connection()
    cur = conn.cursor()
    
    # Get complete resource info using named columns
    cur.execute("""
        SELECT id, resource_type, resource_value, status, allocated_to_license,
               status_changed_at, status_changed_by, quarantine_reason, 
               quarantine_until, created_at, notes
        FROM resource_pools
        WHERE id = %s
    """, (resource_id,))
    row = cur.fetchone()
    
    if not row:
        flash('Ressource nicht gefunden', 'error')
        return redirect(url_for('resources'))
    
    # Create resource object with named attributes
    resource = {
        'id': row[0],
        'resource_type': row[1],
        'resource_value': row[2],
        'status': row[3],
        'allocated_to_license': row[4],
        'status_changed_at': row[5],
        'status_changed_by': row[6],
        'quarantine_reason': row[7],
        'quarantine_until': row[8],
        'created_at': row[9],
        'notes': row[10]
    }
    
    # Get license info if allocated
    license_info = None
    if resource['allocated_to_license']:
        cur.execute("SELECT license_key FROM licenses WHERE id = %s", 
                   (resource['allocated_to_license'],))
        lic = cur.fetchone()
        if lic:
            license_info = {'license_key': lic[0]}
    
    # Get history with named columns
    cur.execute("""
        SELECT 
            rh.action,
            rh.action_by,
            rh.action_at,
            rh.details,
            rh.license_id,
            rh.ip_address
        FROM resource_history rh
        WHERE rh.resource_id = %s
        ORDER BY rh.action_at DESC
    """, (resource_id,))
    
    history = []
    for row in cur.fetchall():
        history.append({
            'action': row[0],
            'action_by': row[1],
            'action_at': row[2],
            'details': row[3],
            'license_id': row[4],
            'ip_address': row[5]
        })
    
    cur.close()
    conn.close()
    
    # Convert to object-like for template
    class ResourceObj:
        def __init__(self, data):
            for key, value in data.items():
                setattr(self, key, value)
    
    resource_obj = ResourceObj(resource)
    history_objs = [ResourceObj(h) for h in history]
    
    return render_template('resource_history.html',
                         resource=resource_obj,
                         license_info=license_info,
                         history=history_objs)

# @app.route('/resources/metrics')
# @login_required
def resources_metrics():
    """Dashboard für Resource Metrics und Reports"""
    conn = get_connection()
    cur = conn.cursor()
    
    # Overall stats with fallback values
    cur.execute("""
        SELECT 
            COUNT(DISTINCT resource_id) as total_resources,
            COALESCE(AVG(performance_score), 0) as avg_performance,
            COALESCE(SUM(cost), 0) as total_cost,
            COALESCE(SUM(revenue), 0) as total_revenue,
            COALESCE(SUM(issues_count), 0) as total_issues
        FROM resource_metrics
        WHERE metric_date >= CURRENT_DATE - INTERVAL '30 days'
    """)
    row = cur.fetchone()
    
    # Calculate ROI
    roi = 0
    if row[2] > 0:  # if total_cost > 0
        roi = row[3] / row[2]  # revenue / cost
    
    stats = {
        'total_resources': row[0] or 0,
        'avg_performance': row[1] or 0,
        'total_cost': row[2] or 0,
        'total_revenue': row[3] or 0,
        'total_issues': row[4] or 0,
        'roi': roi
    }
    
    # Performance by type
    cur.execute("""
        SELECT 
            rp.resource_type,
            COALESCE(AVG(rm.performance_score), 0) as avg_score,
            COUNT(DISTINCT rp.id) as resource_count
        FROM resource_pools rp
        LEFT JOIN resource_metrics rm ON rp.id = rm.resource_id
            AND rm.metric_date >= CURRENT_DATE - INTERVAL '30 days'
        GROUP BY rp.resource_type
        ORDER BY rp.resource_type
    """)
    performance_by_type = cur.fetchall()
    
    # Utilization data
    cur.execute("""
        SELECT 
            resource_type,
            COUNT(*) FILTER (WHERE status = 'allocated') as allocated,
            COUNT(*) as total,
            ROUND(COUNT(*) FILTER (WHERE status = 'allocated') * 100.0 / COUNT(*), 1) as allocated_percent
        FROM resource_pools
        GROUP BY resource_type
    """)
    utilization_rows = cur.fetchall()
    utilization_data = [
        {
            'type': row[0].upper(),
            'allocated': row[1],
            'total': row[2],
            'allocated_percent': row[3]
        }
        for row in utilization_rows
    ]
    
    # Top performing resources
    cur.execute("""
        SELECT 
            rp.id,
            rp.resource_type,
            rp.resource_value,
            COALESCE(AVG(rm.performance_score), 0) as avg_score,
            COALESCE(SUM(rm.revenue), 0) as total_revenue,
            COALESCE(SUM(rm.cost), 1) as total_cost,
            CASE 
                WHEN COALESCE(SUM(rm.cost), 0) = 0 THEN 0
                ELSE COALESCE(SUM(rm.revenue), 0) / COALESCE(SUM(rm.cost), 1)
            END as roi
        FROM resource_pools rp
        LEFT JOIN resource_metrics rm ON rp.id = rm.resource_id
            AND rm.metric_date >= CURRENT_DATE - INTERVAL '30 days'
        WHERE rp.status != 'quarantine'
        GROUP BY rp.id, rp.resource_type, rp.resource_value
        HAVING AVG(rm.performance_score) IS NOT NULL
        ORDER BY avg_score DESC
        LIMIT 10
    """)
    top_rows = cur.fetchall()
    top_performers = [
        {
            'id': row[0],
            'resource_type': row[1],
            'resource_value': row[2],
            'avg_score': row[3],
            'roi': row[6]
        }
        for row in top_rows
    ]
    
    # Resources with issues
    cur.execute("""
        SELECT 
            rp.id,
            rp.resource_type,
            rp.resource_value,
            rp.status,
            COALESCE(SUM(rm.issues_count), 0) as total_issues
        FROM resource_pools rp
        LEFT JOIN resource_metrics rm ON rp.id = rm.resource_id
            AND rm.metric_date >= CURRENT_DATE - INTERVAL '30 days'
        WHERE rm.issues_count > 0 OR rp.status = 'quarantine'
        GROUP BY rp.id, rp.resource_type, rp.resource_value, rp.status
        HAVING SUM(rm.issues_count) > 0
        ORDER BY total_issues DESC
        LIMIT 10
    """)
    problem_rows = cur.fetchall()
    problem_resources = [
        {
            'id': row[0],
            'resource_type': row[1],
            'resource_value': row[2],
            'status': row[3],
            'total_issues': row[4]
        }
        for row in problem_rows
    ]
    
    # Daily metrics for trend chart (last 30 days)
    cur.execute("""
        SELECT 
            metric_date,
            COALESCE(AVG(performance_score), 0) as avg_performance,
            COALESCE(SUM(issues_count), 0) as total_issues
        FROM resource_metrics
        WHERE metric_date >= CURRENT_DATE - INTERVAL '30 days'
        GROUP BY metric_date
        ORDER BY metric_date
    """)
    daily_rows = cur.fetchall()
    daily_metrics = [
        {
            'date': row[0].strftime('%d.%m'),
            'performance': float(row[1]),
            'issues': int(row[2])
        }
        for row in daily_rows
    ]
    
    cur.close()
    conn.close()
    
    return render_template('resource_metrics.html',
                         stats=stats,
                         performance_by_type=performance_by_type,
                         utilization_data=utilization_data,
                         top_performers=top_performers,
                         problem_resources=problem_resources,
                         daily_metrics=daily_metrics)

# @app.route('/resources/report', methods=['GET'])
# @login_required
def resources_report():
    """Generiert Ressourcen-Reports oder zeigt Report-Formular"""
    # Prüfe ob Download angefordert wurde
    if request.args.get('download') == 'true':
        report_type = request.args.get('type', 'usage')
        format_type = request.args.get('format', 'excel')
        date_from = request.args.get('from', (datetime.now(ZoneInfo("Europe/Berlin")) - timedelta(days=30)).strftime('%Y-%m-%d'))
        date_to = request.args.get('to', datetime.now(ZoneInfo("Europe/Berlin")).strftime('%Y-%m-%d'))
        
        conn = get_connection()
        cur = conn.cursor()
        
        if report_type == 'usage':
            # Auslastungsreport
            query = """
                SELECT 
                    rp.resource_type,
                    rp.resource_value,
                    rp.status,
                    COUNT(DISTINCT rh.license_id) as unique_licenses,
                    COUNT(rh.id) as total_allocations,
                    MIN(rh.action_at) as first_used,
                    MAX(rh.action_at) as last_used
                FROM resource_pools rp
                LEFT JOIN resource_history rh ON rp.id = rh.resource_id 
                    AND rh.action = 'allocated'
                    AND rh.action_at BETWEEN %s AND %s
                GROUP BY rp.id, rp.resource_type, rp.resource_value, rp.status
                ORDER BY rp.resource_type, total_allocations DESC
            """
            cur.execute(query, (date_from, date_to))
            columns = ['Typ', 'Ressource', 'Status', 'Unique Lizenzen', 'Gesamt Zuweisungen', 'Erste Nutzung', 'Letzte Nutzung']
            
        elif report_type == 'performance':
            # Performance-Report
            query = """
                SELECT 
                    rp.resource_type,
                rp.resource_value,
                AVG(rm.performance_score) as avg_performance,
                SUM(rm.usage_count) as total_usage,
                SUM(rm.revenue) as total_revenue,
                SUM(rm.cost) as total_cost,
                SUM(rm.revenue - rm.cost) as profit,
                SUM(rm.issues_count) as total_issues
            FROM resource_pools rp
            JOIN resource_metrics rm ON rp.id = rm.resource_id
            WHERE rm.metric_date BETWEEN %s AND %s
            GROUP BY rp.id, rp.resource_type, rp.resource_value
            ORDER BY profit DESC
        """
            cur.execute(query, (date_from, date_to))
            columns = ['Typ', 'Ressource', 'Durchschn. Performance', 'Gesamt Nutzung', 'Umsatz', 'Kosten', 'Gewinn', 'Issues']
            
        elif report_type == 'compliance':
            # Compliance-Report
            query = """
            SELECT 
                rh.action_at,
                rh.action,
                rh.action_by,
                rp.resource_type,
                rp.resource_value,
                l.license_key,
                c.name as customer_name,
                rh.ip_address
            FROM resource_history rh
            JOIN resource_pools rp ON rh.resource_id = rp.id
            LEFT JOIN licenses l ON rh.license_id = l.id
            LEFT JOIN customers c ON l.customer_id = c.id
            WHERE rh.action_at BETWEEN %s AND %s
                ORDER BY rh.action_at DESC
            """
            cur.execute(query, (date_from, date_to))
            columns = ['Zeit', 'Aktion', 'Von', 'Typ', 'Ressource', 'Lizenz', 'Kunde', 'IP-Adresse']
        
        else:  # inventory report
            # Inventar-Report
            query = """
                SELECT 
                    resource_type,
                    COUNT(*) FILTER (WHERE status = 'available') as available,
                    COUNT(*) FILTER (WHERE status = 'allocated') as allocated,
                    COUNT(*) FILTER (WHERE status = 'quarantine') as quarantine,
                    COUNT(*) as total
                FROM resource_pools
                GROUP BY resource_type
                ORDER BY resource_type
            """
            cur.execute(query)
            columns = ['Typ', 'Verfügbar', 'Zugeteilt', 'Quarantäne', 'Gesamt']
        
        # Convert to DataFrame
        data = cur.fetchall()
        df = pd.DataFrame(data, columns=columns)
        
        cur.close()
        conn.close()
        
        # Generate file
        timestamp = datetime.now(ZoneInfo("Europe/Berlin")).strftime('%Y%m%d_%H%M%S')
        filename = f"resource_report_{report_type}_{timestamp}"
        
        if format_type == 'excel':
            output = io.BytesIO()
            with pd.ExcelWriter(output, engine='openpyxl') as writer:
                df.to_excel(writer, sheet_name='Report', index=False)
                
                # Auto-adjust columns width
                worksheet = writer.sheets['Report']
                for column in worksheet.columns:
                    max_length = 0
                    column = [cell for cell in column]
                    for cell in column:
                        try:
                            if len(str(cell.value)) > max_length:
                                max_length = len(str(cell.value))
                        except:
                            pass
                    adjusted_width = (max_length + 2)
                    worksheet.column_dimensions[column[0].column_letter].width = adjusted_width
            
            output.seek(0)
            
            log_audit('EXPORT', 'resource_report', None,
                     new_values={'type': report_type, 'format': 'excel', 'rows': len(df)},
                     additional_info=f"Resource Report {report_type} exportiert")
            
            return send_file(output, 
                            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                            as_attachment=True,
                            download_name=f'{filename}.xlsx')
        
        else:  # CSV
            output = io.StringIO()
            df.to_csv(output, index=False, sep=';', encoding='utf-8-sig')
            output.seek(0)
            
            log_audit('EXPORT', 'resource_report', None,
                     new_values={'type': report_type, 'format': 'csv', 'rows': len(df)},
                     additional_info=f"Resource Report {report_type} exportiert")
            
            return send_file(io.BytesIO(output.getvalue().encode('utf-8-sig')),
                            mimetype='text/csv',
                            as_attachment=True,
                            download_name=f'{filename}.csv')
    
    # Wenn kein Download, zeige Report-Formular
    return render_template('resource_report.html',
                         datetime=datetime,
                         timedelta=timedelta,
                         username=session.get('username'))

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
