import json
import time
import threading
import requests
from flask import Flask, request, jsonify, send_file
import os
import psutil
import tempfile

import sqlite3
from gologin import GoLogin
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
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)

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("\n--- 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")

    # GoLogin-Prozesse finden und deren Nutzung tracken
    try:
        gologin_processes = [p for p in psutil.process_iter(['name', 'memory_info', 'cpu_percent']) if 'gologin' in p.info['name'].lower()]
        if gologin_processes:
            print("\n--- GoLogin-Prozesse ---")
            for p in gologin_processes:
                gologin_mem = p.info['memory_info'].rss / (1024**2)
                gologin_cpu = p.info['cpu_percent']
                print(f"PID: {p.pid}, RAM: {gologin_mem:.2f} MB, CPU: {gologin_cpu:.2f}%")
        else:
            print("\nKeine GoLogin-Prozesse gefunden.")
    except Exception as e:
        print(f"Fehler beim Suchen nach GoLogin-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.81.233.18/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, gologin):
    """
    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"\n🔍 DIAGNOSE START für Prozess {process_id}\n{'=' * 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 = int(gologin.debugger_address.split(":")[1])
        with socket.create_connection(("localhost", 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 {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"\n✅ DIAGNOSE ENDE für Prozess {process_id}\n{'=' * 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(), '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:
        take_screenshot(driver, process_id)
        element = driver.find_element(
                By.XPATH,
                "//span[contains(text(), 'Beitrag')]",
            )
        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')]")
            )
        )
        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']").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']").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, 10).until(
            EC.element_to_be_clickable((By.XPATH, "//div[text()='Weiter']"))
        ).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[@aria-label='Bildunterschrift verfassen …']"))
        )
        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']"))
        )
        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')]") )
            )
        )
        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, gl=None, driver=None):
    """
    Startet den Browser und GoLogin-Session neu.
    Gibt neue Driver- und GoLogin-Instanzen zurück.
    """
    log_process_message(process_id, "Starte Browser-Neustart wegen urllib3 Verbindungsproblemen...", level="WARNING")
    
    # 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")
    
    if gl:
        try:
            gl.stop()
            log_process_message(process_id, "Alte GoLogin-Session beendet.")
        except Exception as e:
            log_process_message(process_id, f"Fehler beim Beenden der alten GoLogin-Session: {e}", level="ERROR")
    
    # Kurze Pause für Cleanup
    time.sleep(5)
    
    try:
        # Neue GoLogin-Session starten
        token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2ODQzMzQ5MjIwMDMwYjQ0OTdlZmVmN2YiLCJ0eXBlIjoiZGV2Iiwiand0aWQiOiI2ODQ0NDA0MDYyNjQ0YWU1NzQ5ODI2MWMifQ.vj-RJv3gm0KpGnb0fAtmipDS2pDnLAFlDik8QKpsc8A'
        profile_id = process_data.get('profile_id')
        new_gl = GoLogin({
            "token": token,
            "profile_id": profile_id,

            "extra_params": [
                "--no-sandbox",
                "--disable-dev-shm-usage",
                "--enable-unsafe-swiftshader", 
                "--disable-gpu",
                "--disable-audio-output"
            ]
        })
        
        debugger_address = new_gl.start()
        log_process_message(process_id, f"Neue GoLogin-Session gestartet. Debugger-Adresse: {debugger_address}")
        
        chromium_version = new_gl.get_chromium_version()
        service = Service(ChromeDriverManager(driver_version=chromium_version).install())
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_experimental_option("debuggerAddress", debugger_address)
        
        new_driver = webdriver.Chrome(service=service, options=chrome_options)
        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_gl
        
    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.
    """
   

    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()


    # 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.")

    gl = None
    driver = None

    try:
        token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2ODQzMzQ5MjIwMDMwYjQ0OTdlZmVmN2YiLCJ0eXBlIjoiZGV2Iiwiand0aWQiOiI2ODQ0NDA0MDYyNjQ0YWU1NzQ5ODI2MWMifQ.vj-RJv3gm0KpGnb0fAtmipDS2pDnLAFlDik8QKpsc8A'
        logging.info(token)
        profile_id = process_data.get('profile_id')
        if not profile_id:
            raise ValueError("Profile ID ist in process_data erforderlich.")

        display = Display(visible=0, size=(1920, 1080))
        display.start()
        tmpdir = '/var/www/html/tmp_gologin'
        os.makedirs(tmpdir, exist_ok=True)
        os.chmod(tmpdir, 0o777)
        
        # Füge 'profile_path' zur GoLogin-Initialisierung hinzu
        gl = GoLogin({
            "token": token,
            "profile_id": profile_id,
            # Füge das temporäre Verzeichnis hinzu
            "extra_params": [
                "--no-sandbox",
                "--disable-dev-shm-usage",
                "--enable-unsafe-swiftshader", 
                "--disable-gpu",
                "--disable-audio-output"
            ]
        })
        debugger_address = None
        try:
            debugger_address = gl.start()
        except FileNotFoundError as e:
            logging.error(f'GoLogin Profil konnte nicht erstellt werden: {e}')
            log_process_message(process_id, f"GoLogin konnte nicht erstellt werden! Fehler: {e}", level="ERROR")
            update_process_status_php(process_id, 0, 0, 'error', f"GoLogin Error: {e}")
            with running_bots_lock:
                running_bots.pop(process_id, None)
            return
        except Exception as e:
            logging.error(f"GoLogin error: {e}")
            log_process_message(process_id, f"GoLogin Fehler: {e}", level="ERROR")
            update_process_status_php(process_id, 0, 0, 'error', f"GoLogin Error: {e}")
            with running_bots_lock:
                running_bots.pop(process_id, None)
            return

        if not debugger_address:
            return  # Kein weiterer Code, wenn GoLogin nicht erfolgreich!

        log_process_message(process_id, f"GoLogin gestartet. Debugger-Adresse: {debugger_address}")
        logging.info(f"GoLogin gestartet. Debugger-Adresse: {debugger_address}")

        chromium_version = gl.get_chromium_version()
        log_process_message(process_id, f"Chromium-Version: {chromium_version}")
        logging.info(f"Chromium-Version: {chromium_version}")

        service = Service(ChromeDriverManager(driver_version=chromium_version).install())
        chrome_options = webdriver.ChromeOptions()
        chrome_options.add_experimental_option("debuggerAddress", debugger_address)
        driver = webdriver.Chrome(service=service, options=chrome_options)
        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.")
        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]['gologin'] = gl

        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, gl = restart_browser(process_id, process_data, gl, 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]['gologin'] = gl
                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, gl = restart_browser(process_id, process_data, gl, 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]['gologin'] = gl
                    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
        try:
            if gl:
                gl.stop()
                logging.info(f"GoLogin-Instanz für Prozess {process_id} gestoppt.")
        except NameError:
            pass
        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():
    if request.is_json:
        try:
            data = request.get_json()
            logging.info("\nDaten vom PHP-Dashboard empfangen:")
            logging.info(json.dumps(data, indent=4))

            bot_thread = threading.Thread(target=simulate_test_bot, args=(data,))
            bot_thread.daemon = True 
            bot_thread.start()

            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_processstop_bot_processstop_bot_processstop_bot_processstop_bot_processstop_bot_processstop_bot_processstop_bot_processstop_bot_process")
    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()

            # Attempt to quit driver and stop gologin
            driver = bot_info.get('driver')
            gologin = bot_info.get('gologin')

        # 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 gologin outside lock
        if gologin:
            try:
                gologin.stop()
                logging.info(f"GoLogin-Instanz für Prozess {process_id} gestoppt (stop_bot_process).")
            except Exception as e:
                logging.error(f"Fehler beim Stoppen von GoLogin 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.info("Starte Python-Bot-Server...")
    app.run(debug=True, host='0.0.0.0', port=5000)

# New endpoint to stop a running bot process
