import json
import time
import threading
import requests
from flask import Flask, request, jsonify, send_file
import os
import psutil
import tempfile
import subprocess
# WICHTIG: Display Environment für virtuellen Display setzen (für headless Server)
os.environ['DISPLAY'] = ':99'

import sqlite3
import hashlib
import socket
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementClickInterceptedException
from selenium.common.exceptions import TimeoutException

import logging
import random
import sys
import string
from flask_cors import CORS
from pyvirtualdisplay import Display
import cv2
import datetime

from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# urllib3 warning detection
from urllib3.exceptions import ReadTimeoutError
# urllib3 warning detection
import urllib3


class BlockingHTTPAdapter(HTTPAdapter):
    def __init__(self, pool_connections=10, pool_maxsize=10, max_retries=None, pool_block=False):
        self.pool_block = pool_block
        super().__init__(pool_connections=pool_connections, pool_maxsize=pool_maxsize, max_retries=max_retries)
    
    def init_poolmanager(self, *args, **kwargs):
        kwargs['block'] = self.pool_block
        return super().init_poolmanager(*args, **kwargs)

# Custom log handler to detect urllib3 warnings
class Urllib3WarningDetector(logging.Handler):
    def __init__(self):
        super().__init__()
        self.warning_detected = threading.Event()
        
    def emit(self, record):
        if (record.name == 'urllib3.connectionpool' and 
            record.levelname == 'WARNING' and 
            'Retrying' in record.getMessage()):
            self.warning_detected.set()
            
    def reset(self):
        self.warning_detected.clear()
        
    def is_warning_detected(self):
        return self.warning_detected.is_set()

# Global detector instance
urllib3_detector = Urllib3WarningDetector()
logging.getLogger('urllib3.connectionpool').addHandler(urllib3_detector)
logging.getLogger('urllib3.connectionpool').setLevel(logging.WARNING)

# ===== MULTILOGIN CONFIGURATION =====
MULTILOGIN_EMAIL = "e.kamuf88@gmail.com"
MULTILOGIN_PASSWORD = "Start#12345"

# Multilogin API endpoints
MLX_SIGNIN_URL = "https://api.multilogin.com/user/signin"
MLX_LAUNCHER_BASE = "https://launcher.mlx.yt:45001/api/v2"
MLX_PROFILE_STOP_URL = "https://launcher.mlx.yt:45001/api/v1/profile/stop"

# Default Folder ID (kann in process_data überschrieben werden)
# ===== CONFIGURATION =====
# Update these with your Multilogin credentials and profile information

# Default Folder ID (kann in process_data überschrieben werden)
DEFAULT_FOLDER_ID = "ec4a6221-f8f3-4843-83d7-d66a3d101c68"

# Automation settings
AUTOMATION_TYPE = "puppeteer" # Im Server nutzen wir Selenium
HEADLESS_MODE = "false"

# ===== MULTILOGIN FUNCTIONS =====


def handle_messaging_popup(driver):
    try:
       
        print("Pop-up gefunden – bereite Klick in leere Fläche vor...")
        
       
        actions = ActionChains(driver)
        actions.move_by_offset(100, 100).click().perform()
        
        time.sleep(1)  # Kurze Pause, um zu sicherstellen, dass es schließt
        print("✓ Pop-up durch Klick in leere Fläche geschlossen.")
    
    except TimeoutException:
        print("Kein Pop-up erschienen – fahre fort.")
    except NoSuchElementException as e:
        print(f"Element nicht gefunden: {e} – Pop-up könnte schon weg sein.")
    except Exception as e:
        print(f"Unerwarteter Fehler beim Schließen des Pop-ups: {e}")



def sign_in_multilogin(email: str, password: str) -> str:
    """
    Sign in to Multilogin API and get authentication token.
    """
    logging.info("Step 1: Signing in to Multilogin API...")
    
    # Multilogin API requires MD5 hashed password
    if len(password) == 32 and all(c in '0123456789abcdef' for c in password.lower()):
        password_hash = password
    else:
        password_hash = hashlib.md5(password.encode()).hexdigest()
    
    payload = {
        "email": email,
        "password": password_hash
    }
    
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
    
    try:
        response = requests.post(MLX_SIGNIN_URL, json=payload, headers=headers, timeout=30)
        
        try:
            data = response.json()
        except:
            raise RuntimeError(f"Invalid JSON response: {response.text[:200]}")
        
        if response.status_code != 200:
            error_msg = data.get("status", {}).get("message", f"HTTP {response.status_code}")
            error_code = data.get("status", {}).get("error_code", "")
            raise ConnectionError(f"Multilogin API returned error {response.status_code}: {error_msg} (code: {error_code})")
        
        if "data" in data and "token" in data["data"]:
            token = data["data"]["token"]
        elif "token" in data:
            token = data["token"]
        else:
            raise ValueError("Token not found in response")
        
        if not token:
            raise ValueError("Token is empty in response")
        
        logging.info("✓ Successfully signed in to Multilogin")
        return token
            
    except requests.exceptions.RequestException as e:
        raise ConnectionError(f"Failed to sign in to Multilogin API: {e}")


def stop_multilogin_profile(profile_id: str, token: str) -> bool:
    """
    Stop a running Multilogin profile.
    Returns True if successfully stopped, False otherwise (e.g., already stopped).
    """
    logging.info(f"Stopping Multilogin profile {profile_id}...")
    
    url = f"{MLX_PROFILE_STOP_URL}/p/{profile_id}"
    headers = {
        "Accept": "application/json",
        "Authorization": f"Bearer {token}"
    }
    
    try:
        # WICHTIG: Das funktionierende Skript nutzt GET, nicht POST
        response = requests.get(url, headers=headers, timeout=30)
        
        # Check response status code first
        if response.status_code == 404:
            logging.info(f"Profile {profile_id} not found (may already be stopped or doesn't exist)")
            return False
        
        try:
            data = response.json()
            status = data.get("status", {})
            http_code = status.get("http_code", response.status_code)
            
            if http_code == 200:
                logging.info(f"✓ Profile {profile_id} stopped successfully")
                time.sleep(3) # Wartezeit etwas erhöht für Stabilität
                return True
            else:
                error_msg = status.get("message", "Unknown error")
                logging.warning(f"Profile stop returned non-200 status: {error_msg} (HTTP {http_code})")
                # Auch wenn es fehlschlägt, könnte es sein, dass es schon gestoppt ist
                return False
        except json.JSONDecodeError:
            # If response is not JSON, check HTTP status
            if response.status_code == 200:
                logging.info(f"✓ Profile {profile_id} stopped successfully (non-JSON response)")
                time.sleep(3)
                return True
            else:
                logging.warning(f"Profile stop returned non-JSON response with status {response.status_code}")
                response.raise_for_status()  # This will raise if status is not 2xx
                return False
            
    except requests.exceptions.HTTPError as e:
        # Handle specific HTTP errors
        if e.response.status_code in (404, 500):
            logging.info(f"Profile {profile_id} stop request returned {e.response.status_code} (likely already stopped)")
        else:
            logging.warning(f"HTTP error stopping profile: {e}")
        return False
    except requests.exceptions.RequestException as e:
        logging.warning(f"Could not stop profile (might be already stopped): {e}")
        return False


def start_multilogin_profile(folder_id: str, profile_id: str, token: str, 
                             automation_type: str = "selenium", 
                             headless: str = "false") -> int:
    """
    Start a Multilogin profile and get the debugging port.
    ROBUST VERSION: Handles 'already running' errors correctly.
    """
    logging.info(f"Step 2: Starting Multilogin profile {profile_id}...")
    logging.info(f"  - Folder ID: {folder_id}")
    logging.info(f"  - Profile ID: {profile_id}")
    logging.info(f"  - Automation Type: {automation_type}")
    logging.info(f"  - Headless Mode: {headless}")
    
    # Präventives Stoppen versuchen (aber nicht als Fehler behandeln, falls es fehlschlägt)
    try:
        stop_multilogin_profile(profile_id, token)
    except Exception as e:
        logging.warning(f"Pre-emptive profile stop failed (non-critical): {e}")
    
    url = f"{MLX_LAUNCHER_BASE}/profile/f/{folder_id}/p/{profile_id}/start"
    params = {
        "automation_type": automation_type,
        "headless_mode": headless
    }
    
    headers = {
        "Accept": "application/json",
        "Authorization": f"Bearer {token}"
    }
    
    try:
        response = requests.get(url, params=params, headers=headers, timeout=60)
        
        # Antwort prüfen ohne sofortigen raise, um Fehlercode zu lesen
        data = {}
        try:
            data = response.json()
        except json.JSONDecodeError:
            # Log raw response for debugging
            logging.error(f"Non-JSON response from Multilogin API: {response.text[:500]}")
            if response.status_code >= 400:
                response.raise_for_status()
            # If status is OK but not JSON, raise a more specific error
            raise ValueError(f"Expected JSON response from Multilogin API, but got: {response.text[:200]}")

        status = data.get("status", {})
        http_code = status.get("http_code", response.status_code)
        
        # Fehlerbehandlung wenn Start nicht OK
        if http_code != 200:
            error_msg = status.get("message", "Unknown error")
            error_code = status.get("error_code", "")
            logging.warning(f"Initial start failed: {error_msg} (code: {error_code}, http_code: {http_code})")
            logging.warning(f"Full API response: {json.dumps(data, indent=2)}")
            
            # Spezielle Behandlung für Authorization-Fehler
            if "authorization" in error_msg.lower() or "unauthorized" in error_msg.lower() or http_code == 401:
                error_detail = (
                    f"Profile Authorization Error: The profile_id '{profile_id}' may not exist, "
                    f"may not belong to folder_id '{folder_id}', or may not be accessible with the current token. "
                    f"Please verify:\n"
                    f"  1. The profile_id is correct\n"
                    f"  2. The folder_id is correct\n"
                    f"  3. The profile exists in your Multilogin account\n"
                    f"  4. The token has permission to access this profile"
                )
                raise RuntimeError(error_detail)
            
            # WICHTIG: Hier ist die Logik, die im Server gefehlt hat
            if "already running" in error_msg.lower() or "already started" in error_msg.lower() or "browser process is running" in error_msg.lower():
                logging.info("Profile is already running. Force stopping and retrying...")
                if stop_multilogin_profile(profile_id, token):
                    logging.info("Retrying profile start in 5 seconds...")
                    time.sleep(5)
                    response = requests.get(url, params=params, headers=headers, timeout=60)
                    try:
                        data = response.json()
                        status = data.get("status", {})
                        if status.get("http_code") != 200:
                            error_msg = status.get("message", "Unknown error")
                            raise RuntimeError(f"Profile start failed after retry: {error_msg}")
                    except json.JSONDecodeError:
                        response.raise_for_status()
                else:
                    raise RuntimeError("Failed to stop already running profile")
            else:
                raise RuntimeError(f"Profile start failed: {error_msg} (HTTP {http_code})")
        
        # Port extrahieren
        profile_data = data.get("data", {})
        port_str = profile_data.get("port")
        
        if not port_str:
             # Fallback Suche nach Port
            port_str = data.get("status", {}).get("message")
            if not (port_str and str(port_str).isdigit()):
                 raise ValueError(f"Port not found in response: {json.dumps(data)}")
        
        port = int(port_str)
        logging.info(f"✓ Profile started successfully on debugging port {port}")
        
        # Warten bis Browser bereit ist
        time.sleep(5)
        
        return port
        
    except requests.exceptions.RequestException as e:
        raise ConnectionError(f"Failed to start Multilogin profile: {e}")
    except (KeyError, ValueError) as e:
        raise RuntimeError(f"Invalid response from Multilogin launcher: {e}")


def check_port_accessible(port: int, host: str = "127.0.0.1") -> bool:
    """Check if a port is accessible (browser is listening)."""
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(1)
        result = sock.connect_ex((host, port))
        sock.close()
        print("Test")

        return result == 0
    except Exception:
        return False


def attach_to_multilogin_browser(port: int) -> webdriver.Chrome:
    logging.info(f"Step 3: Attaching Selenium to browser on port {port}...")
    
    # 1. Port Check (bleibt gleich)
    max_retries = 10
    for i in range(max_retries):
        if check_port_accessible(port):
            break
        logging.info(f"Port {port} not ready yet, waiting... ({i+1}/{max_retries})")
        time.sleep(2)
    else:
        raise ConnectionError(f"Browser on port {port} is not accessible.")
    
    # 2. Options (bleibt gleich)
    chrome_options = Options()
    chrome_options.debugger_address = f"127.0.0.1:{port}"
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-gpu")
    
    # 3. KEIN Service mehr! Entferne das komplett.
    # Stattdessen direkt webdriver.Chrome(options=...) wie im ersten Skript.
    try:
        logging.info(f"Attaching to existing browser on port {port} (no new service)...")
        driver = webdriver.Chrome(options=chrome_options)  # KEIN service=...
        
        logging.info(f"✓ Successfully attached to browser!")
        return driver
        
    except WebDriverException as e:
        logging.error(f"WebDriver Fehler: {e}")
        raise ConnectionError(f"Failed to attach: {e}")


app = Flask(__name__)
CORS(app)  # <-- HIER INITIALISIEREN
# Global dictionary to keep track of running bot threads and control flags
running_bots = {}
running_bots_lock = threading.Lock()

retries = Retry(
    total=3,
    backoff_factor=0.3,
    status_forcelist=[500, 502, 503, 504]
)

# Adapter-Konfiguration mit dem neuen BlockingHTTPAdapter.
# Die Pool-Größe bleibt auf 10, aber dank "block=True" gibt es keine Fehler mehr.
adapter = BlockingHTTPAdapter(
    pool_connections=1,
    pool_maxsize=10,
    max_retries=retries,
    pool_block=True
)

session = requests.Session()

session.mount('http://', adapter)
session.mount('https://', adapter)

def track_system_usage():
    """
    Diese Funktion sammelt und gibt Informationen zur Systemauslastung aus.
    Sie gibt die globale RAM-Nutzung, die CPU-Nutzung und
    die RAM-Nutzung des aktuellen Python-Prozesses aus.
    """
    print("--- System-Ressourcen-Tracking ---")

    # Gesamte RAM-Nutzung
    mem_info = psutil.virtual_memory()
    print(f"Gesamt-RAM: {mem_info.total / (1024**3):.2f} GB")
    print(f"Verfügbarer RAM: {mem_info.available / (1024**3):.2f} GB")
    print(f"Benutzter RAM: {mem_info.used / (1024**3):.2f} GB")
    print(f"RAM-Nutzung (%): {mem_info.percent}%")

    # Gesamte CPU-Nutzung
    print(f"CPU-Nutzung (%): {psutil.cpu_percent(interval=1)}%")

    # RAM-Nutzung des aktuellen Python-Prozesses
    process = psutil.Process(os.getpid())
    process_mem = process.memory_info().rss / (1024**2)
    print(f"RAM-Nutzung des Python-Prozesses: {process_mem:.2f} MB")

    # Multilogin-Prozesse finden und deren Nutzung tracken
    try:
        multilogin_processes = [p for p in psutil.process_iter(['name', 'memory_info', 'cpu_percent']) if 'multilogin' in p.info['name'].lower() or 'mlx' in p.info['name'].lower()]
        if multilogin_processes:
            print("--- Multilogin-Prozesse ---")
            for p in multilogin_processes:
                mlx_mem = p.info['memory_info'].rss / (1024**2)
                mlx_cpu = p.info['cpu_percent']
                print(f"PID: {p.pid}, RAM: {mlx_mem:.2f} MB, CPU: {mlx_cpu:.2f}%")
        else:
            print("Keine Multilogin-Prozesse gefunden.")
    except Exception as e:
        print(f"Fehler beim Suchen nach Multilogin-Prozessen: {e}")

    print("---------------------------------")

def get_video_path_by_id(video_id):
    """
    Gibt den Video-Pfad zurück, der zur übergebenen Video-ID gehört.
    Wenn die ID nicht gefunden wird, wird None zurückgegeben.
    """
    db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'database.sqlite')
    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT filename FROM videos WHERE id = ?", (video_id,))
        row = cursor.fetchone()
        conn.close()
        if row:
            return row[0]
        else:
            return None
    except Exception as e:
        logging.error(f"Fehler beim Abrufen des Video-Pfads für ID {video_id}: {e}")
        return None

# Verzeichnis für Screenshots
SCREENSHOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'debug_screenshots')
if not os.path.exists(SCREENSHOT_DIR):
    try:
        os.makedirs(SCREENSHOT_DIR)
        logging.info(f"Screenshot-Verzeichnis erstellt: {SCREENSHOT_DIR}")
    except OSError as e:
        logging.error(f"Fehler beim Erstellen des Screenshot-Verzeichnisses {SCREENSHOT_DIR}: {e}")

# Konfiguration für die Kommunikation mit dem PHP-Backend
PHP_DASHBOARD_URL = 'http://45.93.249.142//dashboard.php'

# Ein Event-Objekt, um den Screenshot-Thread zu signalisieren, dass er beendet werden soll
stop_screenshot_event = threading.Event()

# Reduzierte CAPTIONS-Liste
DEFAULT_CAPTIONS = [
    "Sauberkeit, die überzeugt und bleibt #überzeugend #dauerhaft #küchenqualität",
    "Keine Chemikalien mehr nötig #chemiefrei #natürlich #gesund",
    "SinkMate: Die Spüle bleibt sauber #bleibtsauber #zuverlässig #küchenhilfe"
]

def get_shop_id_by_process_id(process_id):
    """
    Ermittelt die shop_id anhand der process_id aus der Tabelle bot_processes.
    """
    db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'database.sqlite')
    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT shop_id FROM bot_processes WHERE id = ?", (process_id,))
        row = cursor.fetchone()
        conn.close()
        if row:
            return row[0]
        return None
    except Exception as e:
        logging.error(f"Fehler beim Abrufen der shop_id für Prozess {process_id}: {e}")
        return None


def get_captions_from_db(shop_id):
    """
    Lädt alle Captions für einen bestimmten Shop aus der Datenbank.
    Gibt eine Liste von Strings zurück.
    """
    db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'database.sqlite')
    captions = []
    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT content FROM shop_captions WHERE shop_id = ?", (shop_id,))
        rows = cursor.fetchall()
        conn.close()
        
        # Flache Liste erstellen
        for row in rows:
            if row[0] and row[0].strip():
                captions.append(row[0])
                
        logging.info(f"Habe {len(captions)} Captions aus der DB für Shop {shop_id} geladen.")
        return captions
    except Exception as e:
        logging.error(f"Fehler beim Laden der Captions aus der DB: {e}")
        return []

def update_process_status_php(process_id, current_video_index, total_videos_uploaded, status, error_message=None):
    """
    Sendet ein Update des Bot-Prozessstatus an das PHP-Dashboard.
    Erweitert um eine optionale Fehlermeldung.
    """
    payload = {
        'action': 'update_process_status',
        'process_id': process_id,
        'current_video_index': current_video_index,
        'total_videos_uploaded': total_videos_uploaded,
        'status': status
    }
    if error_message:
        payload['error_message'] = error_message
    
    try:
        with session.post(PHP_DASHBOARD_URL, data=payload) as response:
            response.raise_for_status()
            logging.info(f"PHP process status update response: {response.json()}")
    except requests.exceptions.RequestException as e:
        logging.error(f"Error updating process status to PHP: {e}")
    except json.JSONDecodeError:
        logging.error(f"PHP process status update received non-JSON response: {response.text}")

def diagnose_bot_state(process_id, driver, debug_port):
    """
    Führt eine einmalige umfassende Diagnose des aktuellen Bot-Zustands durch.
    Nutze sie z.B. bei Debug oder nach einem Verdacht auf Instabilität.
    """

    logging.info(f"🔍 DIAGNOSE START für Prozess {process_id}{'=' * 50}")

    # 1. WebDriver: Reaktion testen
    try:
        result = driver.execute_script("return 42;")
        logging.info(f"[{process_id}] WebDriver reagiert: Rückgabe von JS = {result}")
    except Exception as e:
        logging.error(f"[{process_id}] WebDriver NICHT erreichbar: {e}")

    # 2. Aktuelle URL (Seite noch offen?)
    try:
        current_url = driver.current_url
        logging.info(f"[{process_id}] Aktuelle URL: {current_url}")
    except Exception as e:
        logging.warning(f"[{process_id}] Fehler beim Abrufen der aktuellen URL: {e}")

    # 3. Speicherverbrauch
    try:
        mem = psutil.Process(os.getpid()).memory_info().rss / (1024 * 1024)
        logging.info(f"[{process_id}] Speicherverbrauch: {mem:.2f} MB")
        if mem > 1500:
            logging.warning(f"[{process_id}] WARNUNG: Speicherverbrauch über 1.5 GB – möglicher Leak")
    except Exception as e:
        logging.warning(f"[{process_id}] Konnte Speicher nicht ermitteln: {e}")

    # 4. DevTools-Debugger-Port erreichbar?
    try:
        port = debug_port if isinstance(debug_port, int) else int(debug_port)
        with socket.create_connection(("127.0.0.1", port), timeout=2):
            logging.info(f"[{process_id}] DevTools-Debugger-Port {port} erreichbar ✅")
    except Exception as e:
        logging.error(f"[{process_id}] DevTools-Debugger-Port NICHT erreichbar ❌: {e}")

    # 5. Screenshot-Test
    try:
        filename = f"debug_{process_id}_diagnose.png"
        filepath = os.path.join(SCREENSHOT_DIR, filename)
        success = driver.save_screenshot(filepath)
        if success:
            logging.info(f"[{process_id}] Screenshot erfolgreich gespeichert: {filepath}")
        else:
            logging.warning(f"[{process_id}] Screenshot fehlgeschlagen (save_screenshot returned False)")
    except Exception as e:
        logging.error(f"[{process_id}] Screenshot-Fehler: {e}")

    logging.info(f"✅ DIAGNOSE ENDE für Prozess {process_id}{'=' * 50}")


def update_video_status_php(video_id, status):
    """
    Sendet ein Update des Videostatus an das PHP-Dashboard.
    """
    payload = {
        'action': 'update_video_status',
        'video_id': video_id,
        'status': status
    }
    try:
        with session.post(PHP_DASHBOARD_URL, data=payload) as response:
            response.raise_for_status()
            logging.info(f"PHP video status update response: {response.json()}")
    except requests.exceptions.RequestException as e:
        logging.error(f"Error updating video status to PHP: {e}")
    except json.JSONDecodeError:
        logging.error(f"PHP video status update received non-JSON response: {response.text}")

import os
import time
import logging

# Assumption: SCREENSHOT_DIR, diagnose_bot_state, running_bots sind global definiert

import signal
from contextlib import contextmanager

class TimeoutException(Exception): 
    pass

@contextmanager
def time_limit(seconds):
    def signal_handler(signum, frame):
        raise TimeoutException("Timed out!")
    signal.signal(signal.SIGALRM, signal_handler)
    signal.alarm(seconds)
    try:
        yield
    finally:
        signal.alarm(0)

from threading import Timer

class TimeoutException(Exception):
    pass

def take_screenshot(driver, process_id, timeout_seconds=30):
    """Screenshot mit THREAD-SICHEREN Timeout (funktioniert auch in Threading!)"""
    if driver is None:
        logging.warning(f"[{process_id}] Screenshot übersprungen: Driver ist None.")
        return False

    filename = f"debug_{process_id}.png"
    filepath = os.path.join(SCREENSHOT_DIR, filename)
    
    timeout_occurred = False
    
    def timeout_handler():
        nonlocal timeout_occurred
        timeout_occurred = True
    
    try:
        # THREAD-SAFE TIMEOUT mit threading.Timer
        timer = Timer(timeout_seconds, timeout_handler)
        timer.daemon = True
        timer.start()
        
        try:
            success = driver.save_screenshot(filepath)
            timer.cancel()  # Timer stoppen
            
            if timeout_occurred:
                logging.error(f"[{process_id}] ⚠️ Screenshot TIMEOUT ({timeout_seconds}s) - ÜBERSPRUNGEN")
                return False
            
            if success:
                logging.info(f"[{process_id}] Screenshot erfolgreich: {filepath}")
                return True
            else:
                logging.warning(f"[{process_id}] Screenshot fehlgeschlagen")
                return False
                
        except Exception as inner_error:
            timer.cancel()
            
            if timeout_occurred:
                logging.error(f"[{process_id}] ⚠️ Screenshot TIMEOUT - ÜBERSPRUNGEN")
                return False
            
            if isinstance(inner_error, ReadTimeoutError):
                logging.error(f"[{process_id}] ⚠️ Screenshot urllib3 Timeout - ÜBERSPRUNGEN")
            else:
                logging.error(f"[{process_id}] ⚠️ Screenshot Fehler - ÜBERSPRUNGEN: {inner_error}")
            
            return False
            
    except Exception as e:
        logging.error(f"[{process_id}] ⚠️ Screenshot unerwarteter Fehler: {e}")
        return False


import socket


def smart_sleep(base_seconds):
    min_time = base_seconds * 0.8
    max_time = base_seconds * 1.5
    sleep_time = random.uniform(min_time, max_time)
    time.sleep(sleep_time)



def upload_video(driver, video_path, caption, process_id=None, stop_event=None):
    if stop_event is not None and process_id is not None:
        check_stop(process_id, stop_event)
    logging.info(f"Starte Upload für Video: {video_path}")
    take_screenshot(driver, process_id)
    smart_sleep(5)
    if stop_event is not None and process_id is not None:
        check_stop(process_id, stop_event)
    try:
        create_button = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.XPATH, "//span[contains(text(), 'Create') or contains(text(), 'Erstellen')]"))        )
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        create_button.click()
        logging.info("Erstellen-Button geklickt.")
        take_screenshot(driver, process_id)
        smart_sleep(5)
        take_screenshot(driver, process_id)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
    except Exception as e:
        logging.error(f"Fehler beim Klicken des 'Erstellen'-Buttons: {e}")
        return False
    try:
        EC.element_to_be_clickable((By.XPATH, "//span[contains(text(), 'Create') or contains(text(), 'Erstellen')]"))
        take_screenshot(driver, process_id)
        element = driver.find_element(
                By.XPATH,
                "//span[contains(text(), 'Beitrag') or contains(text(), 'Post')]",
            )
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        element.click()
        logging.info("Beitrag-Button geklickt.")
        take_screenshot(driver, process_id)
    except Exception:
        logging.info("Kein Werbeanzeige-Dialog gefunden oder Fehler beim Klicken.")
        pass
    smart_sleep(5)
    if stop_event is not None and process_id is not None:
        check_stop(process_id, stop_event)
    try:
        take_screenshot(driver, process_id)
        file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file'][multiple]")
        # WICHTIG: Stelle sicher, dass der video_path existiert und korrekt ist.
        # Im Beispiel wird ein Platzhalter verwendet. Du musst einen echten Pfad hier haben!
        if not os.path.exists(video_path):
            logging.error(f"Video-Datei existiert nicht: {video_path}")
            return False
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        file_input.send_keys(os.path.abspath(video_path))
        logging.info(f"Video-Datei {os.path.abspath(video_path)} zum Upload ausgewählt.")
        take_screenshot(driver, process_id)
    except Exception as e:
        logging.error(f"Fehler beim Hochladen der Datei: {e}")
        return False
    smart_sleep(5)
    if stop_event is not None and process_id is not None:
        check_stop(process_id, stop_event)
    try:
        take_screenshot(driver, process_id)
        WebDriverWait(driver, 60).until(
            EC.presence_of_element_located(
    (By.XPATH, "//div[@aria-level='1' and (contains(text(), 'Zuschneiden') or contains(text(), 'Crop'))]")
)
        )
        take_screenshot(driver, process_id)
        logging.info("Zuschneiden-Dialog aufgetaucht.")
        smart_sleep(4)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        try:
            take_screenshot(driver, process_id)
            ok_button = driver.find_element(By.XPATH, "//button[contains(text(), 'OK')]")
            if ok_button.is_displayed():
                ok_button.click()
                logging.info("'OK'-Button im Zuschneiden-Dialog geklickt.")
                smart_sleep(5)
                take_screenshot(driver, process_id)
                if stop_event is not None and process_id is not None:
                    check_stop(process_id, stop_event)
        except Exception:
            logging.info("Kein 'OK'-Button im Zuschneiden-Dialog gefunden oder Fehler beim Klicken.")
            pass
    except Exception as e:
        logging.error(f"Zuschneiden-Dialog nicht aufgetaucht oder Fehler: {e}")
        return False
    smart_sleep(3)
    if stop_event is not None and process_id is not None:
        check_stop(process_id, stop_event)
    try:
        take_screenshot(driver, process_id)
        
        driver.find_element(By.XPATH, "//*[name()='svg' and (@aria-label='Zuschnitt auswählen' or @aria-label='Select crop')]").click()        
        smart_sleep(1)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        driver.find_element(By.XPATH, "//div[@role='button' and .//span[text()='9:16']]").click()
        smart_sleep(3)
        take_screenshot(driver, process_id)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        driver.find_element(By.XPATH, "//div[(text()='Weiter' or text()='Next') or (contains(text(), 'Weiter') or contains(text(), 'Next'))]").click()
    except Exception as e:
        logging.warning(f"Crop/Next Fehler: {e}")
    temp_image = extract_random_frame(video_path, "/home/images/")
    if temp_image:
        try:
            upload_image = driver.find_element(
            
            By.CSS_SELECTOR, "input[type='file']")
            upload_image.send_keys(temp_image)
        except Exception as e:
            logging.warning(f"Thumbnail‑Upload Fehler: {e}")
        else:
            logging.warning("Thumbnail konnte nicht extrahiert werden")
    smart_sleep(6)
    if stop_event is not None and process_id is not None:
        check_stop(process_id, stop_event)
    try:
        take_screenshot(driver, process_id)
        WebDriverWait(driver, 15).until(
        EC.element_to_be_clickable((By.XPATH, "//div[text()='Weiter' or text()='Next']"))
        ).click()
        take_screenshot(driver, process_id)
        logging.info("Zweiten 'Weiter'-Button geklickt.")
    except Exception as e:
        logging.error(f"Konnte zweiten 'Weiter'-Button nicht klicken: {e}")
        return False
    smart_sleep(2)
    if stop_event is not None and process_id is not None:
        check_stop(process_id, stop_event)
    try:
        take_screenshot(driver, process_id)
        caption_box = WebDriverWait(driver, 20).until(
        EC.presence_of_element_located((
            By.XPATH, 
            "//div[contains(@aria-label, 'Bildunterschrift') or contains(@aria-label, 'Write a caption...')]"
        ))
        )
        caption_box.send_keys(caption)
        logging.info(f"Bildunterschrift hinzugefügt: '{caption}'")
        smart_sleep(3)
        take_screenshot(driver, process_id)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
    except Exception as e:
        logging.error(f"Fehler beim Hinzufügen der Bildunterschrift: {e}")
        return False
    try:
        smart_sleep(3)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        take_screenshot(driver, process_id)
        share_button = WebDriverWait(driver, 20).until(
        EC.element_to_be_clickable((By.XPATH, "//div[text()='Teilen' or text()='Share']"))
        )
        track_system_usage()
        share_button.click()
        logging.info("Teilen-Button geklickt.")
        take_screenshot(driver, process_id)
    except Exception as e:
        logging.error(f"Fehler beim Klicken des 'Teilen'-Buttons: {e}")
        return False
    try:
        WebDriverWait(driver, 100).until(
            EC.any_of(
                EC.presence_of_element_located((By.XPATH, "//span[contains(text(), 'Dein Beitrag wurde geteilt.')]" ) ),
                EC.presence_of_element_located((By.XPATH, "//div[@aria-level='1' and (contains(text(), 'Reel geteilt') or contains(text(), 'Reel shared'))]")),
                EC.presence_of_element_located((By.XPATH, "//span[contains(text(), 'Your post was shared.')]")),

            )
        )
        take_screenshot(driver, process_id)
        smart_sleep(2)
        take_screenshot(driver, process_id)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        driver.get("https://www.instagram.com/")
        logging.info("REEL GETEILT: Erfolgsmeldung erhalten.")
        smart_sleep(8)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        return True
    except Exception as e:
        logging.error(f"Upload fehlgeschlagen oder Erfolgsmeldung nicht erhalten: {e}")
        take_screenshot(driver, process_id)

        smart_sleep(2)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        driver.get("https://www.instagram.com/")
        smart_sleep(8)
        if stop_event is not None and process_id is not None:
            check_stop(process_id, stop_event)
        return False

def extract_random_frame(video_path, output_folder):
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        print(f"Fehler beim �ffnen des Videos: {video_path}")
        return None

    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    random_frame_index = random.randint(0, total_frames - 1)
    cap.set(cv2.CAP_PROP_POS_FRAMES, random_frame_index)
    ret, frame = cap.read()
    if not ret:
        print(f"Fehler beim Lesen des Frames: {random_frame_index}")
        return None

    random_string = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
    output_filename = f"frame_{random_string}.jpg"
    output_path = os.path.join(output_folder, output_filename)
    cv2.imwrite(output_path, frame)
    cap.release()
    print(f"Zuf�lliger Frame gespeichert als: {output_path}")
    return output_path
# Prozess-Log-Verzeichnis anlegen
PROCESS_LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'process_logs')
if not os.path.exists(PROCESS_LOG_DIR):
    os.makedirs(PROCESS_LOG_DIR)

# Prozess-Logfunktion

def log_process_message(process_id, message, level="INFO"):
    """
    Schreibt eine Lognachricht für einen Prozess in eine JSON-Logdatei.
    """
    log_file = os.path.join(PROCESS_LOG_DIR, f"{process_id}.json")
    log_entry = {
        "timestamp": datetime.datetime.now().isoformat(timespec='seconds'),
        "level": level,
        "message": message
    }
    logs = []
    if os.path.exists(log_file):
        try:
            with open(log_file, "r", encoding="utf-8") as f:
                logs = json.load(f)
        except Exception:
            logs = []
    logs.append(log_entry)
    with open(log_file, "w", encoding="utf-8") as f:
        json.dump(logs, f, ensure_ascii=False, indent=2)

def check_stop(process_id, stop_event):
    if stop_event.is_set():
        log_process_message(process_id, "Prozess wurde per Stop-Event abgebrochen.", level="WARNING")
        raise Exception("Prozess gestoppt")

def restart_browser(process_id, process_data, mlx_token=None, debug_port=None, driver=None):
    """
    Startet den Browser und Multilogin-Session neu.
    Gibt neue Driver-Instanz und neuen Debug-Port zurück.
    """
    log_process_message(process_id, "Starte Browser-Neustart wegen urllib3 Verbindungsproblemen...", level="WARNING")
    
    profile_id = process_data.get('profile_id')
    folder_id = process_data.get('folder_id', DEFAULT_FOLDER_ID)
    
    # Alte Instanzen beenden
    if driver:
        try:
            driver.quit()
            log_process_message(process_id, "Alter WebDriver beendet.")
        except Exception as e:
            log_process_message(process_id, f"Fehler beim Beenden des alten WebDrivers: {e}", level="ERROR")
    
    # Multilogin Profil stoppen
    if mlx_token and profile_id:
        try:
            stop_multilogin_profile(profile_id, mlx_token)
            log_process_message(process_id, "Multilogin-Profil gestoppt.")
        except Exception as e:
            log_process_message(process_id, f"Fehler beim Stoppen des Multilogin-Profils: {e}", level="ERROR")
    
    # Kurze Pause für Cleanup
    time.sleep(5)
    
    try:
        # Neues Token holen falls nicht vorhanden
        if not mlx_token:
            mlx_token = sign_in_multilogin(MULTILOGIN_EMAIL, MULTILOGIN_PASSWORD)
        
        # Neue Multilogin-Session starten
        new_debug_port = start_multilogin_profile(
            folder_id,
            profile_id,
            mlx_token,
            AUTOMATION_TYPE,
            HEADLESS_MODE
        )
        log_process_message(process_id, f"Neue Multilogin-Session gestartet. Debug-Port: {new_debug_port}")
        
        # Selenium an Browser anhängen
        new_driver = attach_to_multilogin_browser(new_debug_port)
        new_driver.set_window_size(1920, 1080)
        
        # Zurück zu Instagram navigieren
        new_driver.get("https://www.instagram.com/")
        time.sleep(5)
        
        log_process_message(process_id, "Browser erfolgreich neugestartet und zu Instagram navigiert.")
        
        # urllib3 detector zurücksetzen
        urllib3_detector.reset()
        
        return new_driver, new_debug_port, mlx_token
        
    except Exception as e:
        log_process_message(process_id, f"Fehler beim Browser-Neustart: {e}", level="ERROR")
        raise


def simulate_test_bot(process_data):
    """
    Führt den Bot-Prozess zum Hochladen von Videos durch.
    Aktualisiert den Status und Fortschritt in regelmäßigen Abständen.
    """
    
    logging.info("=" * 60)
    logging.info("simulate_test_bot GESTARTET")
    logging.info(f"Empfangene process_data: {json.dumps(process_data, indent=2)}")
    logging.info("=" * 60)

    process_id = process_data.get('process_id')
    video_id = process_data.get('video_id')
    num_videos = process_data.get('num_videos')  # Nutze num_videos aus den empfangenen Daten
    start_time_str = process_data.get('start_time')

    # Register the stop event and thread in the global dictionary
    stop_event = threading.Event()
    
    # WICHTIG: stop_event sofort registrieren
    with running_bots_lock:
        running_bots[process_id] = {
            'stop_event': stop_event,
            'driver': None,
            'mlx_token': None,
            'debug_port': None,
            'profile_id': process_data.get('profile_id')
        }
    logging.info(f"[{process_id}] Stop-Event registriert in running_bots")

    # Wenn start_time angegeben ist, warte bis zu diesem Zeitpunkt
    if start_time_str:
        try:
            # Parse start_time as aware datetime in UTC or local timezone
            start_time = datetime.datetime.fromisoformat(start_time_str)
            if start_time.tzinfo is None:
                # Assume start_time is in local timezone if no tzinfo
                import pytz
                local_tz = pytz.timezone('Europe/Berlin')
                start_time = local_tz.localize(start_time)
            # Get current time in same timezone as start_time
            now = datetime.datetime.now(start_time.tzinfo)
            wait_seconds = (start_time - now).total_seconds()
            if wait_seconds > 0:
                log_process_message(process_id, f"Warte {wait_seconds} Sekunden bis zum geplanten Startzeitpunkt {start_time_str} (lokale Zeit).")
                # Wait in small increments to allow stop event checking
                waited = 0
                while waited < wait_seconds:
                    if stop_event.is_set():
                        logging.info(f"Prozess {process_id} wurde vor dem Start gestoppt.")
                        update_process_status_php(process_id, 0, 0, 'aborted', "Prozess vor Start gestoppt.")
                        with running_bots_lock:
                            running_bots.pop(process_id, None)
                        return
                    time.sleep(min(1, wait_seconds - waited))
                    waited += 1
            else:
                log_process_message(process_id, f"Geplanter Startzeitpunkt {start_time_str} liegt in der Vergangenheit. Starte sofort.")
        except Exception as e:
            log_process_message(process_id, f"Fehler beim Parsen des Startzeitpunkts: {e}", level="ERROR")
    else:
        log_process_message(process_id, "Kein Startzeitpunkt angegeben. Starte sofort.")

    # Der video_path muss von außen übergeben werden, da er nicht statisch ist.
    placeholder_video_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'placeholder_video.mov')
    if not os.path.exists(placeholder_video_path):
        try:
            with open(placeholder_video_path, 'w') as f:
                f.write("This is a dummy video file for testing purposes.")
            logging.info(f"Platzhalter-Videodatei erstellt: {placeholder_video_path}")
        except OSError as e:
            logging.error(f"Fehler beim Erstellen der Platzhalter-Videodatei {placeholder_video_path}: {e}")

    current_video_path = "/var/www/html/" + get_video_path_by_id(video_id)
    print(current_video_path)

    if not all([process_id, video_id, num_videos is not None]):
        log_process_message(process_id, f"Fehler: Fehlende Prozessdaten (process_id, video_id oder num_videos).", level="ERROR")
        logging.error(f"Fehler: Fehlende Prozessdaten (process_id, video_id oder num_videos). Erhalten: {json.dumps(process_data, indent=4)}")
        if process_id:
            update_process_status_php(process_id, 0, 0, 'aborted', "Fehlende Prozessdaten.")
        with running_bots_lock:
            running_bots.pop(process_id, None)
        return

    log_process_message(process_id, f"Starte Bot-Simulation für Prozess ID: {process_id} mit {num_videos} Uploads.")
    logging.info(f"Starte Bot-Simulation für Prozess ID: {process_id} mit {num_videos} Uploads.")

    mlx_token = None
    debug_port = None
    driver = None

    try:
        # Multilogin: Sign in und Profil starten
        profile_id = process_data.get('profile_id')
        folder_id = process_data.get('folder_id', DEFAULT_FOLDER_ID)
        
        logging.info(f"[{process_id}] Multilogin Konfiguration:")
        logging.info(f"  - Profile ID: {profile_id}")
        logging.info(f"  - Folder ID: {folder_id}")
        logging.info(f"  - Email: {MULTILOGIN_EMAIL}")
        logging.info(f"  - Automation Type: {AUTOMATION_TYPE}")
        logging.info(f"  - Headless: {HEADLESS_MODE}")
        
        if not profile_id:
            error_msg = "Profile ID ist in process_data erforderlich. Bitte die Multilogin Profile-ID in der Datenbank (instagram_accounts.profile_id) eintragen!"
            logging.error(error_msg)
            log_process_message(process_id, error_msg, level="ERROR")
            update_process_status_php(process_id, 0, 0, 'error', error_msg)
            with running_bots_lock:
                running_bots.pop(process_id, None)
            return

        display = Display(visible=0, size=(1920, 1080))
        display.start()
        logging.info(f"[{process_id}] Virtual Display gestartet")
        
        # Step 1: Sign in to Multilogin
        logging.info(f"[{process_id}] Step 1: Multilogin Sign-in...")
        try:
            mlx_token = sign_in_multilogin(MULTILOGIN_EMAIL, MULTILOGIN_PASSWORD)
            log_process_message(process_id, "Erfolgreich bei Multilogin angemeldet.")
            logging.info(f"[{process_id}] ✓ Multilogin Token erhalten")
        except Exception as e:
            logging.error(f'[{process_id}] ✗ Multilogin Sign-in fehlgeschlagen: {e}')
            log_process_message(process_id, f"Multilogin Sign-in fehlgeschlagen: {e}", level="ERROR")
            update_process_status_php(process_id, 0, 0, 'error', f"Multilogin Sign-in Error: {e}")
            with running_bots_lock:
                running_bots.pop(process_id, None)
            return
        
        # Step 2: Start Multilogin profile
        logging.info(f"[{process_id}] Step 2: Multilogin Profil starten...")
        try:
            debug_port = start_multilogin_profile(
                folder_id,
                profile_id,
                mlx_token,
                AUTOMATION_TYPE,
                HEADLESS_MODE
            )
            log_process_message(process_id, f"Multilogin Profil gestartet. Debug-Port: {debug_port}")
            logging.info(f"[{process_id}] ✓ Profil gestartet auf Port {debug_port}")
        except Exception as e:
            logging.error(f'[{process_id}] ✗ Multilogin Profil konnte nicht gestartet werden: {e}')
            log_process_message(process_id, f"Multilogin Profil Fehler: {e}", level="ERROR")
            update_process_status_php(process_id, 0, 0, 'error', f"Multilogin Profile Error: {e}")
            with running_bots_lock:
                running_bots.pop(process_id, None)
            return

        if not debug_port:
            log_process_message(process_id, "Kein Debug-Port erhalten!", level="ERROR")
            with running_bots_lock:
                running_bots.pop(process_id, None)
            return

        log_process_message(process_id, f"Multilogin gestartet. Debug-Port: {debug_port}")
        logging.info(f"Multilogin gestartet. Debug-Port: {debug_port}")

        # Step 3: Attach Selenium to browser
        logging.info(f"[{process_id}] Step 3: Selenium an Browser anhängen...")
        try:
            driver = attach_to_multilogin_browser(debug_port)
            log_process_message(process_id, "Selenium WebDriver an Multilogin-Browser angehängt.")
        except Exception as e:
            logging.error(f"Selenium Attach fehlgeschlagen: {e}")
            log_process_message(process_id, f"Selenium Attach Fehler: {e}", level="ERROR")
            update_process_status_php(process_id, 0, 0, 'error', f"Selenium Attach Error: {e}")
            # Stop profile on error
            if mlx_token and profile_id:
                stop_multilogin_profile(profile_id, mlx_token)
            with running_bots_lock:
                running_bots.pop(process_id, None)
            return

        log_process_message(process_id, "Selenium WebDriver initialisiert.")
        logging.info("Selenium WebDriver initialisiert.")
        driver.set_window_size(1920, 1080)
        #update_video_status_php(video_id, 'reserviert')

        log_process_message(process_id, "Navigiere zu Instagram...")
        logging.info("Navigiere zu Instagram...")
        driver.get("https://www.instagram.com/")
        logging.info("Erfolgreich zu Instagram navigiert.")
        log_process_message(process_id, "Erfolgreich zu Instagram navigiert.")
        smart_sleep(5)
        take_screenshot(driver, process_id)
        smart_sleep(2)
        handle_messaging_popup(driver)
        smart_sleep(5)
        take_screenshot(driver, process_id)


        with running_bots_lock:
            if process_id in running_bots:
                running_bots[process_id]['driver'] = driver
                running_bots[process_id]['mlx_token'] = mlx_token
                running_bots[process_id]['debug_port'] = debug_port
                running_bots[process_id]['profile_id'] = profile_id

        smart_sleep(5)

########################## pool
        

        
        
        logging.info(f"Screenshot-Thread für Prozess {process_id} gestartet.")

        # --- NEU: CAPTIONS LADEN ---
        # 1. Shop ID ermitteln (falls nicht in process_data, dann aus DB)
        shop_id = process_data.get('shop_id')
        if not shop_id:
            shop_id = get_shop_id_by_process_id(process_id)
        
        # 2. Captions laden
        active_captions = []
        if shop_id:
            active_captions = get_captions_from_db(shop_id)
        
        # 3. Fallback prüfen
        if not active_captions:
            logging.warning(f"Keine Captions in DB gefunden für Shop {shop_id}. Nutze Standard-Liste.")
            active_captions = DEFAULT_CAPTIONS
        else:
            logging.info(f"Starte Uploads mit {len(active_captions)} Custom-Captions aus der Datenbank.")
        # ---------------------------

        successful_uploads = 0
        attempt_count = 0
        consecutive_failures = 0  # Zähler für aufeinanderfolgende Fehler

        for i in range(num_videos):
            check_stop(process_id, stop_event)
            if stop_event.is_set():
                log_process_message(process_id, f"Prozess wurde während des Uploads gestoppt.", level="WARNING")
                logging.info(f"Prozess {process_id} wurde während des Uploads gestoppt.")
                update_process_status_php(process_id, i, successful_uploads, 'aborted', "Prozess während des Uploads gestoppt.")
                break
                
            # urllib3 Warning-Check vor jedem Upload
            if urllib3_detector.is_warning_detected():
                log_process_message(process_id, "urllib3 Verbindungswarnung erkannt - Browser wird neugestartet", level="WARNING")
                try:
                    driver, debug_port, mlx_token = restart_browser(process_id, process_data, mlx_token, debug_port, driver)
                    # Update running_bots with new instances
                    with running_bots_lock:
                        if process_id in running_bots:
                            running_bots[process_id]['driver'] = driver
                            running_bots[process_id]['debug_port'] = debug_port
                            running_bots[process_id]['mlx_token'] = mlx_token
                except Exception as restart_error:
                    log_process_message(process_id, f"Browser-Neustart fehlgeschlagen: {restart_error}", level="ERROR")
                    update_process_status_php(process_id, i, successful_uploads, 'aborted', f"Browser-Neustart fehlgeschlagen: {restart_error}")
                    break
                    
            if active_captions:
                caption = random.choice(active_captions)
            else:
                caption = ""
            log_process_message(process_id, f"Versuche Video {i + 1}/{num_videos} hochzuladen.")
            logging.info(f"Prozess {process_id}: Versuche Video {i + 1}/{num_videos} hochzuladen.")
            log_process_message(process_id, f"Aktuelle Speichernutzung: {psutil.Process(os.getpid()).memory_info().rss / (1024*1024):.2f} MB")
            check_stop(process_id, stop_event)
            update_process_status_php(process_id, i + 1, successful_uploads, 'running', f"Upload von Video {i+1} gestartet.")
            check_stop(process_id, stop_event)
            
            # Upload-Versuch mit erweiteter Fehlerbehandlung
            try:
                check_stop(process_id, stop_event)
                upload_success = upload_video(driver, current_video_path, caption, process_id, stop_event)
                
                # Nach dem Upload erneut prüfen
                if urllib3_detector.is_warning_detected():
                    log_process_message(process_id, "urllib3 Warnung während Upload erkannt", level="WARNING")
                    upload_success = False  # Upload als fehlgeschlagen markieren
                    
            except Exception as e:
                log_process_message(process_id, f"Upload abgebrochen: {str(e)}", level="WARNING")
                upload_success = False
                
            if upload_success:
                successful_uploads += 1
                consecutive_failures = 0  # Reset bei erfolgreichem Upload
                log_process_message(process_id, f"Video {i + 1}/{num_videos} erfolgreich hochgeladen. Erfolgreiche Uploads gesamt: {successful_uploads}")
                logging.info(f"Prozess {process_id}: Video {i + 1}/{num_videos} erfolgreich hochgeladen. Erfolgreiche Uploads gesamt: {successful_uploads}")
                if i == 0:
                    print("genutzt")
                    #update_video_status_php(video_id, 'bereits genutzt')
            else:
                consecutive_failures += 1
                error_msg = f"Upload für Video {i+1} (Pfad: {current_video_path}) fehlgeschlagen."
                log_process_message(process_id, error_msg, level="ERROR")
                logging.error(f"Prozess {process_id}: {error_msg}")
                update_process_status_php(process_id, i + 1, successful_uploads, 'running', error_msg)
                
                # Bei 3 aufeinanderfolgenden Fehlern Browser neustarten
                if consecutive_failures >= 3:
                    log_process_message(process_id, f"3 aufeinanderfolgende Fehler - erzwinge Browser-Neustart", level="WARNING")
                    try:
                        driver, debug_port, mlx_token = restart_browser(process_id, process_data, mlx_token, debug_port, driver)
                        consecutive_failures = 0 # Reset nach Neustart
                        urllib3_detector.reset() # Detector zurücksetzen
                        with running_bots_lock:
                            if process_id in running_bots:
                                running_bots[process_id]['driver'] = driver
                                running_bots[process_id]['debug_port'] = debug_port
                                running_bots[process_id]['mlx_token'] = mlx_token
                    except Exception as restart_error:
                        log_process_message(process_id, f"Erzwungener Browser-Neustart fehlgeschlagen: {restart_error}", level="ERROR")
                        update_process_status_php(process_id, i + 1, successful_uploads, 'aborted', f"Browser-Neustart nach Fehlern fehlgeschlagen: {restart_error}")
                        break
                        
            attempt_count += 1
            check_stop(process_id, stop_event)
            smart_sleep(random.randint(10, 20))
            check_stop(process_id, stop_event)
        if not stop_event.is_set():
            if successful_uploads == num_videos:
                final_status = 'completed'
                final_message = f"Alle {successful_uploads} Videos erfolgreich hochgeladen für Prozess ID: {process_id}"
                log_process_message(process_id, final_message)
                logging.info(final_message)
                display.stop()
            else:
                final_status = 'completed_with_errors'
                final_message = f"Prozess {process_id} abgeschlossen mit {successful_uploads}/{num_videos} erfolgreichen Uploads. Einige Uploads sind fehlgeschlagen."
                log_process_message(process_id, final_message, level="WARNING")
                logging.warning(final_message)
                display.stop()

                remaining_videos = num_videos - successful_uploads
                if remaining_videos > 0:
                    logging.info(f"Starte Neustart des Prozesses {process_id} mit {remaining_videos} verbleibenden Uploads.")
                    new_process_data = process_data.copy()
                    new_process_data['num_videos'] = remaining_videos
                    retry_count = new_process_data.get('retry_count', 0)
                    if retry_count >= 3:
                        logging.error(f"Maximale Neustartversuche für Prozess {process_id} erreicht. Abbruch.")
                    else:
                        new_process_data['retry_count'] = retry_count + 1
                        retry_thread = threading.Thread(target=simulate_test_bot, args=(new_process_data,))
                        retry_thread.daemon = True
                        retry_thread.start()
                        logging.info(f"Neustart-Thread für Prozess {process_id} gestartet.")
                else:
                    logging.info(f"Keine verbleibenden Uploads für Prozess {process_id}. Kein Neustart erforderlich.")

            update_process_status_php(process_id, num_videos, successful_uploads, final_status, final_message)
            log_process_message(process_id, f"Prozessstatus auf '{final_status}' gesetzt.")
    except Exception as e:
        error_message_full = f"Ein unerwarteter Fehler während der Bot-Simulation für Prozess {process_id} aufgetreten: {str(e)}"
        log_process_message(process_id, error_message_full, level="ERROR")
        logging.error(error_message_full, exc_info=True)
        update_process_status_php(
            process_id,
            process_data.get('current_video_index', 0),
            process_data.get('total_videos_uploaded', 0),
            'aborted_with_critical_error', error_message_full
        )
    finally:
        # Stop the screenshot thread and cleanup
        stop_event.set()
       
        try:
            if 'driver' in locals() and driver:
                driver.quit()
                logging.info(f"Selenium WebDriver für Prozess {process_id} beendet.")
        except NameError:
            pass
        # Stop Multilogin profile
        try:
            if mlx_token and profile_id:
                stop_multilogin_profile(profile_id, mlx_token)
                logging.info(f"Multilogin-Profil für Prozess {process_id} gestoppt.")
        except NameError:
            pass
        except Exception as e:
            logging.warning(f"Fehler beim Stoppen des Multilogin-Profils: {e}")
        with running_bots_lock:
            running_bots.pop(process_id, None)
        log_process_message(process_id, "Bot-Prozess beendet.")

@app.route('/start_bot_process', methods=['POST'])
def start_bot_process_endpoint():
    logging.info("=" * 60)
    logging.info(">>> /start_bot_process ENDPOINT AUFGERUFEN <<<")
    logging.info("=" * 60)
    
    if request.is_json:
        try:
            data = request.get_json()
            logging.info("Daten vom PHP-Dashboard empfangen:")
            logging.info(json.dumps(data, indent=4))
            
            logging.info(f"Starte Thread für simulate_test_bot...")

            bot_thread = threading.Thread(target=simulate_test_bot, args=(data,))
            bot_thread.daemon = True 
            bot_thread.start()
            
            logging.info(f"Thread gestartet: {bot_thread.name}, is_alive: {bot_thread.is_alive()}")

            return jsonify({"status": "success", "message": "Bot-Prozess gestartet."}), 200
        except Exception as e:
            logging.error(f"Fehler beim Verarbeiten der start_bot_process-Anfrage: {e}", exc_info=True)
            return jsonify({"status": "error", "message": f"Serverfehler: {str(e)}"}), 500
    else:
        logging.warning("Nicht-JSON-Anfrage an /start_bot_process empfangen.")
        return jsonify({"status": "error", "message": "Anfrage muss JSON sein."}), 400
    
    
@app.route('/debug', methods=['GET'])
def debug_status():
    return jsonify({"status": "running", "message": "Python-Bot-Server ist aktiv."}), 200

@app.route('/stop_bot_process', methods=['POST'])
def stop_bot_process():
    print("stop_bot_process aufgerufen")
    if request.is_json:
        data = request.get_json()
        process_id = data.get('process_id')
        print(f"Received request to stop bot process with ID: {process_id}")
        if not process_id:
            return jsonify({"status": "error", "message": "process_id is required"}), 400

        with running_bots_lock:
            bot_info = running_bots.get(process_id)
            if not bot_info:
                return jsonify({"status": "error", "message": f"No running bot found for process_id {process_id}"}), 404

            # Signal the thread to stop
            bot_info['stop_event'].set()

            # Get driver and Multilogin info
            driver = bot_info.get('driver')
            mlx_token = bot_info.get('mlx_token')
            profile_id = bot_info.get('profile_id')

        # Quit driver outside lock
        if driver:
            try:
                driver.quit()
                logging.info(f"Selenium WebDriver für Prozess {process_id} beendet (stop_bot_process).")
            except Exception as e:
                logging.error(f"Fehler beim Beenden des WebDrivers für Prozess {process_id}: {e}")

        # Stop Multilogin profile outside lock
        if mlx_token and profile_id:
            try:
                stop_multilogin_profile(profile_id, mlx_token)
                logging.info(f"Multilogin-Profil für Prozess {process_id} gestoppt (stop_bot_process).")
            except Exception as e:
                logging.error(f"Fehler beim Stoppen des Multilogin-Profils für Prozess {process_id}: {e}")

        with running_bots_lock:
            running_bots.pop(process_id, None)

        return jsonify({"status": "success", "message": f"Bot process {process_id} stopped."}), 200
    else:
        return jsonify({"status": "error", "message": "Request must be JSON."}), 400

@app.route('/debug_screenshot/<int:account_id>')
def get_debug_screenshot(account_id):
    try:
        filename = f"debug_{account_id}.png"
        filepath = os.path.join(SCREENSHOT_DIR, filename)
        if os.path.exists(filepath):
            return send_file(filepath, mimetype='image/png')
        else:
            logging.warning(f"Screenshot für Account {account_id} nicht gefunden unter {filepath}")
            return jsonify({"error": "Kein Screenshot verfügbar"}), 404
    except Exception as e:
        logging.error(f"Fehler beim Bereitstellen des Screenshots für Account {account_id}: {e}", exc_info=True)
        return jsonify({"error": str(e)}), 500

if __name__ == '__main__':
    # Logging konfigurieren für bessere Diagnose
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.StreamHandler()
        ]
    )
    
    logging.info("=" * 60)
    logging.info("PYTHON BOT SERVER STARTET")
    logging.info("=" * 60)
    logging.info(f"Multilogin Email: {MULTILOGIN_EMAIL}")
    logging.info(f"Default Folder ID: {DEFAULT_FOLDER_ID}")
    logging.info(f"Automation Type: {AUTOMATION_TYPE}")
    logging.info(f"Headless Mode: {HEADLESS_MODE}")
    logging.info("=" * 60)
    
    app.run(debug=True, host='0.0.0.0', port=5000)
