"""
Playlist management functionality for the auto clicker application.
"""

import json
import os
import logging
import subprocess
import tempfile
from datetime import datetime
from tkinter import simpledialog, messagebox, filedialog
import db
import subprocess

from .constants import CHROME_DOWNLOADS_FOLDER


class PlaylistManager:
    """Manages playlist operations."""
    
    def __init__(self, app):
        self.app = app
        self.logger = logging.getLogger('autoclicker.playlist_manager')
    
    # --- Safe UI notifications (avoid blocking dialogs on headless Linux) ---
    def _can_show_dialogs(self) -> bool:
        try:
            import sys, os
            if sys.platform.startswith('linux'):
                return bool(os.environ.get('DISPLAY'))
            return True
        except Exception:
            return False

    def _show_toast(self, message: str, *, duration_ms: int = 2500) -> None:
        """Non-blocking, auto-dismiss notification that stays above other windows.
        Avoids modal messageboxes getting hidden behind browsers on Linux.
        """
        try:
            import tkinter as tk
            # Ensure we run on UI thread
            def _spawn():
                try:
                    toast = tk.Toplevel(self.app)
                    try:
                        toast.overrideredirect(True)
                    except Exception:
                        pass
                    try:
                        toast.attributes('-topmost', True)
                    except Exception:
                        pass
                    # Content
                    frm = tk.Frame(toast, bg='#333', bd=1)
                    frm.pack(fill='both', expand=True)
                    lbl = tk.Label(frm, text=message, fg='#fff', bg='#333', padx=12, pady=8, justify='left')
                    lbl.pack()
                    # Position bottom-right of the root window
                    try:
                        self.app.update_idletasks()
                        rx = self.app.winfo_rootx()
                        ry = self.app.winfo_rooty()
                        rw = self.app.winfo_width()
                        rh = self.app.winfo_height()
                        tw = max(240, lbl.winfo_reqwidth() + 24)
                        th = max(40, lbl.winfo_reqheight() + 16)
                        x = rx + rw - tw - 20
                        y = ry + rh - th - 20
                    except Exception:
                        sw = self.app.winfo_screenwidth()
                        sh = self.app.winfo_screenheight()
                        tw, th = 260, 48
                        x = sw - tw - 20
                        y = sh - th - 40
                    try:
                        toast.geometry(f"{tw}x{th}+{int(x)}+{int(y)}")
                    except Exception:
                        pass
                    toast.after(duration_ms, lambda: (toast.destroy() if toast.winfo_exists() else None))
                except Exception:
                    pass
            try:
                self.app.after(0, _spawn)
            except Exception:
                _spawn()
        except Exception:
            pass

    def _show_info(self, title: str, message: str) -> None:
        try:
            # Prefer a non-blocking toast to avoid hidden modal dialogs
            self._show_toast(message)
            # Optionally also show a modal dialog when possible (Windows/desktop Linux)
            if self._can_show_dialogs():
                try:
                    self.app.lift()
                    self.app.attributes('-topmost', True)
                except Exception:
                    pass
                try:
                    messagebox.showinfo(title, message, parent=self.app)
                except Exception:
                    pass
                finally:
                    try:
                        self.app.attributes('-topmost', False)
                    except Exception:
                        pass
            if hasattr(self.app, 'status_var') and self.app.status_var:
                try:
                    self.app.status_var.set(message)
                except Exception:
                    pass
            try:
                self.logger.info(f"{title}: {message}")
            except Exception:
                pass
        except Exception:
            pass

    def _show_error(self, title: str, message: str) -> None:
        try:
            # Prefer non-blocking toast first
            self._show_toast(message)
            if self._can_show_dialogs():
                try:
                    self.app.lift()
                    self.app.attributes('-topmost', True)
                except Exception:
                    pass
                try:
                    messagebox.showerror(title, message, parent=self.app)
                except Exception:
                    pass
                finally:
                    try:
                        self.app.attributes('-topmost', False)
                    except Exception:
                        pass
            if hasattr(self.app, 'status_var') and self.app.status_var:
                try:
                    self.app.status_var.set(message)
                except Exception:
                    pass
            try:
                self.logger.error(f"{title}: {message}")
            except Exception:
                pass
        except Exception:
            pass
        
    def load_playlists(self):
        """Load playlist names and application details from MySQL."""
        try:
            from mysql.mysql_client import get_playlist_names_with_app_details
            playlists_with_apps = get_playlist_names_with_app_details()
            
            if not playlists_with_apps:
                self.logger.warning("No playlists found in MySQL database. Database may be empty or connection failed.")
                return []
            
            # Store application details for each playlist
            self.app.playlist_application_details = {}
            for playlist in playlists_with_apps:
                if playlist.get('application_id'):
                    self.app.playlist_application_details[playlist['name']] = {
                        'start_point': playlist.get('start_point'),
                        'platform': playlist.get('platform'),
                        'app_name': playlist.get('app_name'),
                        'application_id': playlist.get('application_id')
                    }
            
            # Return just the playlist names for backward compatibility
            playlist_names = [p['name'] for p in playlists_with_apps]
            
            return playlist_names
            
        except Exception as e:
            self.logger.error(f"Failed to load playlists from MySQL: {e}")
            # Show user-friendly error message
            if hasattr(self.app, 'status_var'):
                self.app.status_var.set('DB Connection Failed')
            return []

    def refresh_playlists(self):
        """Refresh playlist dropdown from database."""
        self.app.playlists = self.load_playlists()
        
        if hasattr(self.app, 'playlist_dropdown') and self.app.playlist_dropdown:
            self.app.playlist_dropdown['values'] = self.app.playlists
            
            if self.app.playlists:
                self.app.selected_playlist.set('Select playlist')
                self.app.playlist_dropdown.set('Select playlist')
                if hasattr(self.app, 'status_var'):
                    self.app.status_var.set(f'Loaded {len(self.app.playlists)} playlists')
            else:
                self.app.selected_playlist.set('No playlists found')
                self.app.playlist_dropdown.set('No playlists found')

    def _auto_launch_application(self, playlist_name):
        """Automatically launch the application for the given playlist."""
        try:
            app_details = self.app.playlist_application_details.get(playlist_name)
            if not app_details or not app_details.get('start_point'):
                self.logger.warning(f"No start_point found for playlist {playlist_name}")
                return
            
            start_point = app_details['start_point']
            platform = app_details.get('platform', '').lower()
            
            if platform == 'web':
                # Create a temporary user data directory for Chrome profile
                user_data_dir = None
                try:
                    user_data_dir = tempfile.mkdtemp(prefix="chrome-profile-")
                    # Disable save dialog and password prompts by setting Chrome preferences
                    prefs_dir = os.path.join(user_data_dir, "Default")
                    os.makedirs(prefs_dir, exist_ok=True)
                    prefs_file = os.path.join(prefs_dir, "Preferences")
                    prefs = {}
                    if os.path.exists(prefs_file):
                        with open(prefs_file, 'r', encoding='utf-8') as f:
                            prefs = json.load(f)
                    # Downloads: no prompt, fixed downloads folder
                    downloads_path = os.path.abspath(os.path.expanduser(CHROME_DOWNLOADS_FOLDER))
                    os.makedirs(downloads_path, exist_ok=True)
                    dl = prefs.setdefault("download", {})
                    dl["prompt_for_download"] = False
                    dl["default_directory"] = downloads_path.replace("\\", "/")
                    dl["directory_upgrade"] = True
                    # Password manager: completely disabled
                    prefs["credentials_enable_service"] = False
                    profile_prefs = prefs.setdefault("profile", {})
                    profile_prefs["password_manager_enabled"] = False
                    with open(prefs_file, 'w', encoding='utf-8') as f:
                        json.dump(prefs, f, indent=2)
                    # Also create Master Preferences file (works even in incognito)
                    master_prefs_file = os.path.join(user_data_dir, "Master Preferences")
                    with open(master_prefs_file, 'w', encoding='utf-8') as f:
                        json.dump(prefs, f, indent=2)
                    self.logger.info(
                        "Set Chrome prefs for playlist launch: downloads -> '%s', password manager disabled",
                        downloads_path,
                    )
                except Exception as e:
                    self.logger.warning(f"Failed to configure Chrome preferences: {e}")
                    user_data_dir = None
                
                # Launch web application in Chrome kiosk mode
                try:
                    chrome_cmd = ["google-chrome", "--kiosk", "--disable-features=DownloadBubbleV2"]
                    if user_data_dir:
                        chrome_cmd.append(f"--user-data-dir={user_data_dir}")
                    chrome_cmd.append(start_point)
                    subprocess.run(chrome_cmd, check=True, capture_output=True)
                    self.logger.info(f"Launched web application: {start_point} in Chrome kiosk mode")
                except subprocess.CalledProcessError as e:
                    self.logger.error(f"Failed to launch Chrome: {e}")
                    # Fallback to regular Chrome if kiosk mode fails
                    try:
                        chrome_cmd = ["google-chrome", "--disable-features=DownloadBubbleV2"]
                        if user_data_dir:
                            chrome_cmd.append(f"--user-data-dir={user_data_dir}")
                        chrome_cmd.append(start_point)
                        subprocess.run(chrome_cmd, check=True, capture_output=True)
                        self.logger.info(f"Launched web application: {start_point} in regular Chrome")
                    except subprocess.CalledProcessError as e2:
                        self.logger.error(f"Failed to launch Chrome (fallback): {e2}")
                except FileNotFoundError:
                    self.logger.error("Google Chrome not found. Please install Chrome or update PATH.")
                    
            elif platform == 'desktop':
                # Launch desktop application (Windows-safe)
                try:
                    if os.name == 'nt':
                        # Prefer PowerShell Start-Process -PassThru to capture the TARGET app PID (not the shell)
                        try:
                            ps_cmd = [
                                "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command",
                                f"$p=Start-Process -FilePath \"{start_point}\" -PassThru; $p.Id"
                            ]
                            res = subprocess.run(ps_cmd, capture_output=True, text=True, check=False)
                            pid_val = None
                            if res.stdout:
                                try:
                                    pid_val = int(res.stdout.strip().splitlines()[-1])
                                except Exception:
                                    pid_val = None
                            if pid_val:
                                try:
                                    if hasattr(self.app, 'launched_desktop_pids'):
                                        self.app.launched_desktop_pids.add(pid_val)
                                except Exception:
                                    pass
                                self.logger.info(f"Launched desktop application: {start_point} (pid={pid_val})")
                                # Also capture descendant/GUI PIDs under the same install folder (Sage spawns children)
                                try:
                                    dir_path = os.path.dirname(start_point).replace('"', '\"')
                                    ps_children = [
                                        "powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command",
                                        (
                                            "$dir=\"" + dir_path + "\"; "
                                            "$procs=Get-Process | Where-Object { $_.Path -like (\"$dir\\*\") -and $_.MainWindowHandle -ne 0 }; "
                                            "$procs | ForEach-Object { $_.Id }"
                                        )
                                    ]
                                    res2 = subprocess.run(ps_children, capture_output=True, text=True, check=False)
                                    if res2.stdout:
                                        for line in res2.stdout.splitlines():
                                            try:
                                                cid = int(line.strip())
                                                if hasattr(self.app, 'launched_desktop_pids'):
                                                    self.app.launched_desktop_pids.add(cid)
                                            except Exception:
                                                pass
                                except Exception:
                                    pass
                            else:
                                # Fallback to startfile if PID not captured
                                os.startfile(start_point)  # type: ignore[attr-defined]
                                self.logger.info(f"Launched desktop application (no pid captured): {start_point}")
                            try:
                                # Record process image name and full path for later cleanup
                                img_name = os.path.splitext(os.path.basename(start_point))[0]
                                if hasattr(self.app, 'launched_desktop_process_names'):
                                    self.app.launched_desktop_process_names.add(img_name)
                                if hasattr(self.app, 'launched_desktop_executable_paths'):
                                    self.app.launched_desktop_executable_paths.add(start_point)
                                try:
                                    if hasattr(self.app, 'launched_desktop_name_keywords'):
                                        self.app.launched_desktop_name_keywords.add('sage')
                                        name_kw = str(app_details.get('app_name') or app_details.get('name') or '').strip().lower()
                                        if name_kw:
                                            self.app.launched_desktop_name_keywords.add(name_kw)
                                except Exception:
                                    pass
                            except Exception:
                                pass
                        except Exception as startfile_err:
                            # Fallback to 'start' to handle spaces and non-.exe targets
                            self.logger.warning(f"os.startfile failed ({startfile_err}); falling back to 'start'")
                            subprocess.Popen(
                                ['cmd', '/c', 'start', '', start_point],
                                stdout=subprocess.DEVNULL,
                                stderr=subprocess.DEVNULL,
                            )
                            self.logger.info(f"Launched desktop application via cmd start: {start_point}")
                            try:
                                img_name = os.path.splitext(os.path.basename(start_point))[0]
                                if hasattr(self.app, 'launched_desktop_process_names'):
                                    self.app.launched_desktop_process_names.add(img_name)
                                if hasattr(self.app, 'launched_desktop_executable_paths'):
                                    self.app.launched_desktop_executable_paths.add(start_point)
                                try:
                                    if hasattr(self.app, 'launched_desktop_name_keywords'):
                                        self.app.launched_desktop_name_keywords.add('sage')
                                        name_kw = str(app_details.get('app_name') or app_details.get('name') or '').strip().lower()
                                        if name_kw:
                                            self.app.launched_desktop_name_keywords.add(name_kw)
                                except Exception:
                                    pass
                            except Exception:
                                pass
                    else:
                        # Linux/macOS
                        subprocess.Popen(
                            [start_point],
                            stdout=subprocess.DEVNULL,
                            stderr=subprocess.DEVNULL,
                            start_new_session=True,
                        )
                        self.logger.info(f"Launched desktop application: {start_point}")
                except Exception as e:
                    self.logger.error(f"Failed to launch desktop application {start_point}: {e}")
                    
            else:
                self.logger.warning(f"Unknown platform '{platform}' for application {start_point}")
                
        except Exception as e:
            self.logger.error(f"Error auto-launching application for playlist {playlist_name}: {e}")
    

    
    def launch_application_for_playlist(self, playlist_name):
        """Manually launch the application for a specific playlist."""
        self._auto_launch_application(playlist_name)

    def update_playlist_dropdown_selection(self, playlist_name):
        """Update the dropdown's values and set the selection to playlist_name."""
        self.app.playlists = self.load_playlists()
        
        if hasattr(self.app, 'playlist_dropdown') and self.app.playlist_dropdown:
            self.app.playlist_dropdown['values'] = self.app.playlists
            
            if playlist_name in self.app.playlists:
                self.app.selected_playlist.set(playlist_name)
                self.app.playlist_dropdown.set(playlist_name)
            else:
                self.app.selected_playlist.set('Select playlist')
                self.app.playlist_dropdown.set('Select playlist')

    def add_playlist(self):
        """Add a new playlist."""
        
        name = simpledialog.askstring('Add Playlist', 'Enter playlist name:')
        if not name:
            return
            
        try:
            from mysql.mysql_client import playlist_name_exists_in_mysql, save_playlist_to_mysql
        except Exception as import_e:
            self.logger.error(f"Failed to import mysql_client: {import_e}")
            return
            
        # Check for duplicate in MySQL
        self.app.show_loader('Checking playlist in MySQL...')
        if playlist_name_exists_in_mysql(name):
            self.app.hide_loader()
            return
            
        # Insert into MySQL
        try:
            self.app.show_loader('Saving playlist to MySQL...')
            # Pass context if available from direct integration
            application_id = getattr(self.app, 'current_application_id', None)
            user_id = getattr(self.app, 'current_user_id', None) 
            company_id = getattr(self.app, 'current_company_id', None)
            # If admin checked Sub playlist, set show_on=0, else default (None -> DB default=1)
            try:
                hide_on_sub = bool(self.app.hide_on_subplaylist_var.get()) if hasattr(self.app, 'hide_on_subplaylist_var') else False
            except Exception:
                hide_on_sub = False
            show_val = 0 if hide_on_sub else None
            self.logger.info(f"Add playlist - saving to MySQL with context: app_id={application_id}, user_id={user_id}, company_id={company_id}, show={show_val}")
            result = save_playlist_to_mysql(name, application_id=application_id, user_id=user_id, company_id=company_id, show=show_val)
            self.app.hide_loader()
            self.logger.info(f"MySQL insert result: {result}")
            
            # Capture MySQL playlist ID for action saving
            if result and result.get('data'):
                try:
                    self.app.current_mysql_playlist_id = result['data'][0]['id']
                    self.logger.info(f"Set current_mysql_playlist_id to {self.app.current_mysql_playlist_id}")
                except Exception as e:
                    self.logger.error(f"Failed to capture MySQL playlist ID in add_playlist: {e}")
                    
        except Exception as call_e:
            self.app.hide_loader()
            self.logger.error(f"Failed to call save_playlist_to_mysql: {call_e}")
            return
            
        if name not in self.app.playlists:
            self.app.playlists.append(name)
        
        # Only update dropdown if it exists (might be None in direct integration mode)
        if hasattr(self.app, 'playlist_dropdown') and self.app.playlist_dropdown:
            try:
                self.app.playlist_dropdown['values'] = self.app.playlists
            except Exception as e:
                self.logger.warning(f"Could not update playlist dropdown in add_playlist: {e}")
        
        self.app.selected_playlist.set(name)
        # Keep recording_name_var in sync so Save uses the same name as recording
        try:
            if hasattr(self.app, 'recording_name_var') and self.app.recording_name_var is not None:
                self.app.recording_name_var.set(name)
        except Exception:
            pass
        
        # Mark any corresponding direct integration upload as processed
        self._mark_direct_integration_processed(name)

    def save_playlist(self):
        """Save a new playlist."""
        
        # If recording is active, stop it first to flush buffers to SQLite
        try:
            if getattr(self.app, 'recording', False):
                self.logger.info("Save clicked during recording; stopping recording before save.")
                # Prefer the same flow as clicking the Stop button
                try:
                    if hasattr(self.app, 'on_stop_recording'):
                        self.app.on_stop_recording()
                    elif hasattr(self.app, 'recording_manager'):
                        # Fallback directly to recording manager
                        self.app.recording_manager.stop_recording()
                except Exception as stop_err:
                    self.logger.error(f"Error during stop before save: {stop_err}")
        except Exception as e:
            try:
                self.logger.error(f"Failed to check/stop recording before save: {e}")
            except Exception:
                pass

        # Get playlist name from recording_name_var; fallback to selected_playlist if blank
        name = self.app.recording_name_var.get().strip() if self.app.recording_name_var else ''
        if not name:
            try:
                name = (self.app.selected_playlist.get() or '').strip()
            except Exception:
                name = ''
        
        # If no name and recording name field is visible (manual mode), show error
        if not name and not getattr(self.app, 'hide_recording_name', False):
            self._show_error('Save Playlist', 'Please enter a recording name.')
            return
        
        # If no name and it's direct integration mode, show error too
        if not name:
            self._show_error('Save Playlist', 'Recording name not set properly.')
            return
        
        self.logger.info(f"Saving playlist: '{name}', hide_recording_name={getattr(self.app, 'hide_recording_name', False)}")
            
        # Check for duplicate in MySQL (not SQLite - SQLite is just session storage)
        # If it exists, PUSH actions to that existing playlist instead of returning.
        try:
            from mysql.mysql_client import playlist_name_exists_in_mysql, get_playlist_id_by_name
            if playlist_name_exists_in_mysql(name):
                try:
                    existing_id = get_playlist_id_by_name(name)
                except Exception:
                    existing_id = None
                if existing_id:
                    try:
                        self.app.current_mysql_playlist_id = existing_id
                    except Exception:
                        pass
                    # Transfer any local SQLite actions to this existing MySQL playlist
                    self._save_existing_actions_to_mysql(name, existing_id)
                    # Proceed with UI updates as if saved newly
                else:
                    # Could not resolve ID; continue to attempt normal save path
                    self.logger.warning(f"Playlist '{name}' exists in MySQL but id lookup failed; proceeding with create path")
        except Exception as e:
            self.logger.error(f"Error checking MySQL for duplicates: {e}")
            try:
                messagebox.showerror('Save Playlist', f'Error checking for duplicates: {e}')
            except Exception:
                pass
            # Continue; downstream may still save via create path
            
        # SQLite playlist may already exist from recording - that's fine, we'll use it for actions
        # Ensure SQLite playlist exists for action transfer (trimmed match)
        conn = db.get_connection()
        cur = conn.cursor()
        try:
            # Prefer TRIM(name)=TRIM(?) so trailing/leading spaces don't split rows
            cur.execute('SELECT id FROM Playlists WHERE TRIM(name) = TRIM(?)', (name,))
        except Exception:
            cur.execute('SELECT id FROM Playlists WHERE name = ?', (name,))
        if not cur.fetchone():
            # Create SQLite playlist if it doesn't exist (shouldn't happen but just in case)
            cur.execute(
                'INSERT INTO Playlists (name, created_date) VALUES (?, ?)',
                (name, datetime.now().isoformat())
            )
            conn.commit()
        conn.close()
        
        # If we already had an existing MySQL ID (duplicate case), skip create and just continue
        has_existing_mysql = bool(getattr(self.app, 'current_mysql_playlist_id', None))
        if not has_existing_mysql:
            # Now save to MySQL and transfer actions from SQLite
            # Pass additional context if available from direct integration
            application_id = getattr(self.app, 'current_application_id', None)
            user_id = getattr(self.app, 'current_user_id', None) 
            company_id = getattr(self.app, 'current_company_id', None)
            
            # Determine show_on based on admin-only checkbox
            try:
                hide_on_sub = bool(self.app.hide_on_subplaylist_var.get()) if hasattr(self.app, 'hide_on_subplaylist_var') else False
            except Exception:
                hide_on_sub = False
            show_val = 0 if hide_on_sub else None

            self.logger.info(f"Creating MySQL playlist '{name}' and transferring actions from SQLite (show={show_val})")
            self._save_to_mysql(name, application_id=application_id, user_id=user_id, company_id=company_id, show=show_val)
        
        # Update UI safely (playlist_dropdown might be None in direct integration mode)
        if name not in self.app.playlists:
            self.app.playlists.append(name)
        
        # Only update dropdown if it exists (might be None in direct integration mode)
        if hasattr(self.app, 'playlist_dropdown') and self.app.playlist_dropdown:
            try:
                self.app.playlist_dropdown['values'] = self.app.playlists
            except Exception as e:
                self.logger.warning(f"Could not update playlist dropdown: {e}")
        
        self.app.selected_playlist.set(name)
        self.app.status_var.set(f'Playlist "{name}" saved.')
        # Avoid modal popups; rely on status bar and logs
        try:
            if hasattr(self.app, 'status_var') and self.app.status_var:
                self.app.status_var.set(f'Playlist "{name}" saved')
        except Exception:
            pass
        try:
            self.logger.info(f'Playlist "{name}" has been saved.')
        except Exception:
            pass
        
        # Mark any corresponding direct integration upload as processed
        self._mark_direct_integration_processed(name)
        
        # Debug action saving flow
        self.debug_action_saving(name)

        # After a successful save, clear local SQLite rows for this playlist so
        # subsequent saves do not duplicate clicks/keys/triggers.
        try:
            self._clear_local_playlist_data(name)
            self.logger.info(f"Cleared local SQLite data for playlist '{name}' after saving to MySQL")
        except Exception as e:
            self.logger.warning(f"Failed to clear local SQLite data for '{name}': {e}")

        # After a successful save, run the same completion steps as playback (non-blocking)
        try:
            import threading
            try:
                setattr(self.app, 'playlist_actions_source', 'mysql')
                if hasattr(self.app, 'status_var'):
                    self.app.status_var.set('Saved. Finalizing...')
            except Exception:
                pass
            threading.Thread(
                target=self.app.playback_manager._handle_playback_completion,
                daemon=True,
            ).start()
        except Exception as e:
            self.logger.error(f"Failed to schedule completion flow after save: {e}")

    def _save_to_mysql(self, name, application_id=None, user_id=None, company_id=None, show=None):
        """Save playlist to MySQL with optional context from direct integration."""
        try:
            self.logger.info(f"Attempting to save playlist to MySQL: {name}")
            
            try:
                from mysql.mysql_client import save_playlist_to_mysql
            except Exception as import_e:
                self.logger.error(f"Failed to import save_playlist_to_mysql: {import_e}")
                messagebox.showerror('MySQL Import', f'Failed to import mysql_client: {import_e}')
                return
                
            try:
                # Pass additional context when available
                self.logger.info(f"Saving to MySQL with context: app_id={application_id}, user_id={user_id}, company_id={company_id}, show={show}")
                result = save_playlist_to_mysql(name, application_id=application_id, user_id=user_id, company_id=company_id, show=show)
                self.logger.info(f"MySQL insert result: {result}")
                
                # Capture MySQL playlist ID for action saving
                if result and result.get('data'):
                    try:
                        self.app.current_mysql_playlist_id = result['data'][0]['id']
                        self.logger.info(f"Set current_mysql_playlist_id to {self.app.current_mysql_playlist_id}")
                        
                        # Save any existing recorded actions to MySQL now that we have the playlist ID
                        self._save_existing_actions_to_mysql(name, self.app.current_mysql_playlist_id)
                        
                        # Debug action saving flow (_save_to_mysql)
                        self.debug_action_saving(name)
                        
                        # Mark any corresponding direct integration upload as processed
                        self._mark_direct_integration_processed(name)
                        
                        # Do not mark the playlist as processed; only direct_integration_uploads are tracked
                        
                    except Exception as e:
                        self.logger.error(f"Failed to capture MySQL playlist ID: {e}")
                        
            except Exception as call_e:
                self.logger.error(f"Failed to call save_playlist_to_mysql: {call_e}")
                messagebox.showerror('MySQL', f'Failed to save playlist to MySQL: {call_e}')
                
        except Exception as e:
            self.logger.error(f"Unexpected error in MySQL block: {e}")
            messagebox.showerror('MySQL', f'Unexpected error: {e}')

    

    def _save_existing_actions_to_mysql(self, playlist_name, mysql_playlist_id):
        """Save any existing recorded actions to MySQL after playlist creation."""
        try:
            self.logger.info(f"_save_existing_actions_to_mysql called for playlist '{playlist_name}' with MySQL ID {mysql_playlist_id}")
            import db
            conn = db.get_connection()
            cur = conn.cursor()
            
            # Find the local playlist ID
            try:
                cur.execute('SELECT id FROM Playlists WHERE TRIM(name) = TRIM(?)', (playlist_name,))
            except Exception:
                cur.execute('SELECT id FROM Playlists WHERE name = ?', (playlist_name,))
            row = cur.fetchone()
            if not row:
                self.logger.info(f"No local SQLite playlist found for name '{playlist_name}'")
                conn.close()
                return
            local_playlist_id = row[0]
            self.logger.info(f"Found local playlist ID {local_playlist_id} for name '{playlist_name}'")
            
            # Get recorded clicks (include variable_name)
            cur.execute(
                'SELECT x, y, timestamp, variable_name FROM Clicks WHERE playlist_id = ? ORDER BY timestamp ASC',
                (local_playlist_id,)
            )
            clicks = cur.fetchall()
            self.logger.info(f"Found {len(clicks)} clicks in SQLite for playlist {playlist_name}")
            
            cur.execute(
                'SELECT key, event_type, timestamp FROM KeyboardEvents WHERE playlist_id = ? ORDER BY timestamp ASC',
                (local_playlist_id,)
            )
            keys = cur.fetchall()
            self.logger.info(f"Found {len(keys)} key events in SQLite for playlist {playlist_name}")
            
            # Get triggers (loop/screenshot) from ActionTriggers
            try:
                cur.execute(
                    'SELECT action_type, timestamp, payload FROM ActionTriggers WHERE playlist_id = ? ORDER BY timestamp ASC',
                    (local_playlist_id,)
                )
                triggers = cur.fetchall() or []
                self.logger.info(f"Found {len(triggers)} triggers in SQLite for playlist {playlist_name}")
            except Exception as e:
                triggers = []
                self.logger.warning(f"Could not fetch ActionTriggers: {e}")

            conn.close()
            
            # Save to MySQL if we have actions
            if clicks or keys or triggers:
                self.logger.info(f"Saving {len(clicks)} clicks, {len(keys)} keys, {len(triggers)} triggers to MySQL for playlist {playlist_name}")
                from mysql.mysql_client import save_action_to_mysql
                
                # Save clicks with detailed logging
                successful_saves = 0
                failed_saves = 0
                
                for i, click_row in enumerate(clicks):
                    try:
                        if len(click_row) >= 4:
                            x, y, ts, variable_name = click_row[0], click_row[1], click_row[2], click_row[3]
                        else:
                            x, y, ts, variable_name = click_row[0], click_row[1], click_row[2], None
                        self.logger.info(f"Saving click {i+1}/{len(clicks)}: x={x}, y={y}, ts={ts}, var={variable_name}, mysql_playlist_id={mysql_playlist_id}")
                        result = save_action_to_mysql(mysql_playlist_id, 'click', x, y, None, ts, playlist_name, variable_name=variable_name)
                        
                        if result and result.get('data'):
                            successful_saves += 1
                            self.logger.info(f"SUCCESS: Click {i+1} saved successfully to MySQL")
                        else:
                            failed_saves += 1
                            error_msg = result.get('error', 'Unknown error') if result else 'No result returned'
                            self.logger.error(f"FAILED: Click {i+1} failed to save: {error_msg}")
                            
                    except Exception as e:
                        failed_saves += 1
                        self.logger.error(f"ERROR: Exception saving click {i+1} to MySQL: {e}")
                        
                # Save keyboard events with detailed logging (should be 0 for variable typing)
                for i, (key, event_type, ts) in enumerate(keys):
                    try:
                        action_type = 'key_press' if event_type == 'press' else 'key_release'
                        self.logger.info(f"Saving key {i+1}/{len(keys)}: key={key}, type={action_type}, ts={ts}, mysql_playlist_id={mysql_playlist_id}")
                        result = save_action_to_mysql(mysql_playlist_id, action_type, None, None, key, ts, playlist_name)
                        
                        if result and result.get('data'):
                            successful_saves += 1
                            self.logger.info(f"SUCCESS: Key {i+1} saved successfully to MySQL")
                        else:
                            failed_saves += 1
                            error_msg = result.get('error', 'Unknown error') if result else 'No result returned'
                            self.logger.error(f"FAILED: Key {i+1} failed to save: {error_msg}")
                            
                    except Exception as e:
                        failed_saves += 1
                        self.logger.error(f"ERROR: Exception saving key {i+1} to MySQL: {e}")
                
                # Save triggers (loop/screenshot/search/subplaylist)
                for i, trig in enumerate(triggers):
                    try:
                        action_type, ts, payload = trig[0], trig[1], (trig[2] if len(trig) > 2 else None)
                        if action_type == 'loop':
                            self.logger.info(f"Saving loop trigger {i+1}/{len(triggers)}: var={payload}, ts={ts}")
                            result = save_action_to_mysql(mysql_playlist_id, 'loop', None, None, payload, ts, playlist_name)
                        elif action_type == 'search':
                            # Extract the search name from payload and store in `key` for playback
                            try:
                                import json as _json
                                query_name = None
                                if payload is not None:
                                    parsed = None
                                    # Decode bytes if necessary
                                    try:
                                        if isinstance(payload, (bytes, bytearray)):
                                            payload_str = payload.decode('utf-8', errors='ignore')
                                        else:
                                            payload_str = payload if isinstance(payload, str) else None
                                    except Exception:
                                        payload_str = None

                                    # Attempt to parse JSON
                                    try:
                                        if isinstance(payload, dict):
                                            parsed = payload
                                        elif isinstance(payload_str, str) and payload_str.strip():
                                            parsed = _json.loads(payload_str)
                                    except Exception:
                                        parsed = None

                                    if isinstance(parsed, dict):
                                        q = parsed.get('query')
                                        if isinstance(q, (str, int, float)):
                                            query_name = str(q).strip() or None
                                    # If payload was a plain string, use it as the query name
                                    if query_name is None and isinstance(payload_str, str) and payload_str.strip():
                                        query_name = payload_str.strip()
                            except Exception:
                                query_name = None
                            self.logger.info(f"Saving search trigger {i+1}/{len(triggers)}: query='{query_name}', ts={ts}")
                            result = save_action_to_mysql(mysql_playlist_id, 'search', None, None, query_name, ts, playlist_name)
                        elif action_type == 'subplaylist':
                            # Persist subplaylist with its id using the dedicated helper
                            try:
                                sub_id = None
                                if isinstance(payload, (int, float)):
                                    sub_id = int(payload)
                                elif isinstance(payload, (bytes, bytearray)):
                                    try:
                                        sub_id = int(payload.decode('utf-8', errors='ignore').strip())
                                    except Exception:
                                        sub_id = None
                                elif isinstance(payload, str):
                                    try:
                                        sub_id = int(payload.strip())
                                    except Exception:
                                        sub_id = None
                            except Exception:
                                sub_id = None
                            if sub_id is not None:
                                try:
                                    from mysql.mysql_client import save_subplaylist_action as _save_sub
                                except Exception:
                                    _save_sub = None
                                if _save_sub:
                                    self.logger.info(f"Saving subplaylist trigger {i+1}/{len(triggers)}: subplaylist_id={sub_id}, ts={ts}")
                                    _ = _save_sub(mysql_playlist_id, sub_id, ts, playlist_name)
                                    # Treat as success; save_subplaylist_action commits internally
                                    result = {'data': [{'id': None}]}
                                else:
                                    self.logger.warning("save_subplaylist_action unavailable; falling back to generic save without subplaylist_id")
                                    result = save_action_to_mysql(mysql_playlist_id, 'subplaylist', None, None, None, ts, playlist_name)
                            else:
                                self.logger.error(f"Invalid subplaylist payload '{payload}' - skipping save")
                                result = None
                        else:
                            self.logger.info(f"Saving trigger {i+1}/{len(triggers)}: type={action_type}, ts={ts}")
                            result = save_action_to_mysql(mysql_playlist_id, action_type, None, None, None, ts, playlist_name)
                        if result and result.get('data'):
                            successful_saves += 1
                        else:
                            failed_saves += 1
                            err = result.get('error', 'Unknown error') if result else 'No result returned'
                            self.logger.error(f"FAILED: Trigger {i+1} failed to save: {err}")
                    except Exception as e:
                        failed_saves += 1
                        self.logger.error(f"ERROR: Exception saving trigger {i+1} to MySQL: {e}")

                # Final summary
                total_actions = len(clicks) + len(keys) + len(triggers)
                if successful_saves == total_actions:
                    self.logger.info(f"SUCCESS: Saved ALL {successful_saves}/{total_actions} actions to MySQL for playlist {playlist_name}")
                else:
                    self.logger.warning(f"PARTIAL: {successful_saves}/{total_actions} actions saved to MySQL for playlist {playlist_name} ({failed_saves} failed)")
            else:
                self.logger.info(f"No actions found in SQLite for playlist {playlist_name} - nothing to save to MySQL")
                        
        except Exception as e:
            self.logger.error(f"Failed to save existing actions to MySQL: {e}")

    def _clear_local_playlist_data(self, playlist_name: str) -> None:
        """Delete local SQLite rows (Clicks, KeyboardEvents, Screenshots, ActionTriggers)
        for the given playlist. Also removes the local Playlists row to avoid confusion.
        """
        try:
            import db
            conn = db.get_connection()
            cur = conn.cursor()
            try:
                cur.execute('SELECT id FROM Playlists WHERE TRIM(name) = TRIM(?)', (playlist_name,))
            except Exception:
                cur.execute('SELECT id FROM Playlists WHERE name = ?', (playlist_name,))
            row = cur.fetchone()
            if not row:
                conn.close()
                return
            local_id = row[0]
            # Delete child rows first
            try:
                cur.execute('DELETE FROM Clicks WHERE playlist_id = ?', (local_id,))
            except Exception:
                pass
            try:
                cur.execute('DELETE FROM KeyboardEvents WHERE playlist_id = ?', (local_id,))
            except Exception:
                pass
            try:
                cur.execute('DELETE FROM Screenshots WHERE playlist_id = ?', (local_id,))
            except Exception:
                pass
            try:
                cur.execute('DELETE FROM ActionTriggers WHERE playlist_id = ?', (local_id,))
            except Exception:
                pass
            # Remove the local playlist row as it is just session storage
            try:
                cur.execute('DELETE FROM Playlists WHERE id = ?', (local_id,))
            except Exception:
                pass
            conn.commit()
            conn.close()
        except Exception as e:
            try:
                conn.close()
            except Exception:
                pass
            raise e

    def debug_action_saving(self, playlist_name):
        """Debug method to check action saving flow."""
        try:
            self.logger.info(f"=== DEBUG ACTION SAVING FOR PLAYLIST: {playlist_name} ===")
            
            # Check MySQL playlist ID
            mysql_playlist_id = getattr(self.app, 'current_mysql_playlist_id', None)
            self.logger.info(f"Current MySQL playlist ID in app: {mysql_playlist_id}")
            
            # Check if playlist exists in SQLite
            import db
            conn = db.get_connection()
            cur = conn.cursor()
            
            cur.execute('SELECT id FROM Playlists WHERE name = ?', (playlist_name,))
            row = cur.fetchone()
            if row:
                local_playlist_id = row[0]
                self.logger.info(f"Local SQLite playlist ID: {local_playlist_id}")
                
                # Check for recorded actions in SQLite
                cur.execute('SELECT COUNT(*) FROM Clicks WHERE playlist_id = ?', (local_playlist_id,))
                click_count = cur.fetchone()[0]
                self.logger.info(f"SQLite clicks count: {click_count}")
                
                cur.execute('SELECT COUNT(*) FROM KeyboardEvents WHERE playlist_id = ?', (local_playlist_id,))
                key_count = cur.fetchone()[0]
                self.logger.info(f"SQLite key events count: {key_count}")
                
                if click_count > 0:
                    cur.execute('SELECT x, y, timestamp FROM Clicks WHERE playlist_id = ? LIMIT 3', (local_playlist_id,))
                    sample_clicks = cur.fetchall()
                    self.logger.info(f"Sample clicks from SQLite: {sample_clicks}")
                    
            else:
                self.logger.info(f"No local SQLite playlist found for '{playlist_name}'")
            
            conn.close()
            
            # Check if playlist exists in MySQL
            if mysql_playlist_id:
                try:
                    from mysql.mysql_client import get_playlist_actions
                    mysql_actions = get_playlist_actions(mysql_playlist_id)
                    self.logger.info(f"MySQL actions count: {len(mysql_actions) if mysql_actions else 0}")
                    if mysql_actions:
                        sample_mysql = mysql_actions[:3]
                        self.logger.info(f"Sample MySQL actions: {sample_mysql}")
                except Exception as e:
                    self.logger.error(f"Error checking MySQL actions: {e}")
            
            self.logger.info(f"=== END DEBUG ACTION SAVING ===")
            
        except Exception as e:
            self.logger.error(f"Error in debug_action_saving: {e}")

    def _mark_direct_integration_processed(self, playlist_name):
        """Mark any corresponding direct integration upload as completed (status=1) after manual playlist save."""
        try:
            from mysql.mysql_client import get_mysql_connection
            connection = get_mysql_connection()
            cursor = connection.cursor()
            
            # Look for unprocessed uploads with this playlist name (both S3 and local)
            query = """
                UPDATE direct_integration_uploads 
                SET processed = 1, 
                    error_message = NULL,
                    updated_at = NOW()
                WHERE processed IN (0, 2) 
                AND playlist_name = %s
            """
            cursor.execute(query, (playlist_name,))
            rows_updated = cursor.rowcount
            connection.commit()
            
            if rows_updated > 0:
                self.logger.info(f"Marked {rows_updated} direct integration upload(s) as completed (status=1) for playlist '{playlist_name}'")
            else:
                self.logger.debug(f"No direct integration uploads found to mark as completed for playlist '{playlist_name}'")
                
        except Exception as e:
            self.logger.error(f"Failed to mark direct integration uploads as processed: {e}")
        finally:
            if 'cursor' in locals() and cursor:
                try:
                    cursor.close()
                except Exception:
                    pass
            if 'connection' in locals() and connection:
                try:
                    connection.close()
                except Exception:
                    pass

    def delete_playlist_mysql(self):
        """Delete a playlist from both SQLite and MySQL databases."""
        pl_name = self.app.selected_playlist.get()
        if not pl_name or pl_name == 'Select playlist':
            return
            
        try:
            self.app.show_loader('Deleting playlist from databases...')
            
            # First, delete from SQLite
            self.logger.info(f"Deleting playlist '{pl_name}' from SQLite")
            import db
            sqlite_conn = db.get_connection()
            sqlite_cur = sqlite_conn.cursor()
            
            # Get SQLite playlist ID first
            sqlite_cur.execute('SELECT id FROM Playlists WHERE name = ?', (pl_name,))
            sqlite_row = sqlite_cur.fetchone()
            if sqlite_row:
                sqlite_playlist_id = sqlite_row[0]
                self.logger.info(f"Found SQLite playlist ID: {sqlite_playlist_id}")
                
                # Delete SQLite actions first
                sqlite_cur.execute('DELETE FROM Clicks WHERE playlist_id = ?', (sqlite_playlist_id,))
                sqlite_cur.execute('DELETE FROM KeyboardEvents WHERE playlist_id = ?', (sqlite_playlist_id,))
                sqlite_cur.execute('DELETE FROM Screenshots WHERE playlist_id = ?', (sqlite_playlist_id,))
                
                # Delete SQLite playlist
                sqlite_cur.execute('DELETE FROM Playlists WHERE id = ?', (sqlite_playlist_id,))
                
                sqlite_conn.commit()
                self.logger.info(f"Deleted playlist '{pl_name}' from SQLite")
            else:
                self.logger.info(f"Playlist '{pl_name}' not found in SQLite")
            
            sqlite_conn.close()
            
            # Then, delete from MySQL
            self.logger.info(f"Deleting playlist '{pl_name}' from MySQL")
            from mysql.mysql_client import get_mysql_connection
            
            mysql_connection = get_mysql_connection()
            mysql_cursor = mysql_connection.cursor()
            
            # Delete MySQL actions first
            mysql_cursor.execute("DELETE FROM actions WHERE playlist_name = %s", (pl_name,))
            
            # Delete MySQL playlist
            mysql_cursor.execute("DELETE FROM playlists WHERE name = %s", (pl_name,))
            
            mysql_connection.commit()
            self.logger.info(f"Deleted playlist '{pl_name}' from MySQL")
            self.app.hide_loader()
            
            # Mark any corresponding direct integration upload(s) as completed
            try:
                # Prefer marking the specific current upload id if available
                upload_id = getattr(self.app, 'current_direct_upload_id', None) or getattr(self.app, 'user_interaction_upload_id', None)
                if upload_id:
                    from mysql.mysql_client import set_direct_upload_status
                    set_direct_upload_status(int(upload_id), 1, error_message=None)
                else:
                    # Fallback: mark by playlist name for any pending (0/2/4) rows
                    self._mark_direct_integration_processed(pl_name)
            except Exception as e:
                self.logger.error(f"Failed to mark direct integration upload(s) complete: {e}")

            # Remove from local dropdown
            if pl_name in self.app.playlists:
                self.app.playlists.remove(pl_name)
                
                # Only update dropdown if it exists (might be None in direct integration mode)
                if hasattr(self.app, 'playlist_dropdown') and self.app.playlist_dropdown:
                    try:
                        self.app.playlist_dropdown['values'] = self.app.playlists
                    except Exception as e:
                        self.logger.warning(f"Could not update playlist dropdown in delete: {e}")
                
                self.app.selected_playlist.set('Select playlist')
            
            # Non-blocking status update instead of popup (avoids Linux Tk issues)
            try:
                self.app.status_var.set(f'Playlist "{pl_name}" deleted')
            except Exception:
                pass

            # Reset app state and UI identically to Cancel flow, and restart watcher
            try:
                self.app._reset_app_after_workflow()
            except Exception:
                pass
                
        except Exception as e:
            self.app.hide_loader()
            self.logger.error(f"Failed to delete playlist: {e}")
            messagebox.showerror('Delete Playlist', f'Failed to delete playlist: {e}')
        finally:
            # Clean up SQLite connection
            if 'sqlite_cur' in locals() and sqlite_cur:
                try:
                    sqlite_cur.close()
                except Exception:
                    pass
            if 'sqlite_conn' in locals() and sqlite_conn:
                try:
                    sqlite_conn.close()
                except Exception:
                    pass
            # Clean up MySQL connection
            if 'mysql_cursor' in locals() and mysql_cursor:
                try:
                    mysql_cursor.close()
                except Exception:
                    pass
            if 'mysql_connection' in locals() and mysql_connection:
                try:
                    mysql_connection.close()
                except Exception:
                    pass

    def export_json(self):
        """Export the selected playlist to JSON."""
        pl_name = self.app.selected_playlist.get()
        if not pl_name or pl_name == 'Select playlist':
            messagebox.showinfo('Export JSON', 'Please select a playlist to export.')
            return
            
        # Fetch playlist info from DB
        conn = db.get_connection()
        cur = conn.cursor()
        cur.execute('SELECT id, name, created_date FROM Playlists WHERE name = ?', (pl_name,))
        row = cur.fetchone()
        
        if not row:
            messagebox.showerror('Export JSON', 'Playlist not found in database.')
            return
            
        playlist_id, name, created_date = row
        
        # Fetch clicks
        cur.execute(
            'SELECT x, y, timestamp FROM Clicks WHERE playlist_id = ? ORDER BY timestamp ASC',
            (playlist_id,)
        )
        clicks = [{'x': x, 'y': y, 'timestamp': ts} for x, y, ts in cur.fetchall()]
        
        # Fetch screenshots
        cur.execute('SELECT path, is_manual FROM Screenshots WHERE playlist_id = ?', (playlist_id,))
        screenshots = [
            {'path': path, 'is_manual': bool(is_manual)}
            for path, is_manual in cur.fetchall()
        ]
        
        conn.close()
        
        # Prepare JSON data
        data = {
            'name': name,
            'created_date': created_date,
            'clicks': clicks,
            'screenshots': screenshots
        }
        
        # Ask user for file path
        os.makedirs('exports', exist_ok=True)
        file_path = filedialog.asksaveasfilename(
            defaultextension='.json',
            initialfile=f'{name}.json',
            initialdir='exports',
            filetypes=[('JSON Files', '*.json')]
        )
        
        if not file_path:
            return
            
        with open(file_path, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2)
            
        messagebox.showinfo('Export JSON', f'Exported playlist to {file_path}')

    def on_playlist_selected(self, event=None):
        """Handle playlist selection and auto-launch application."""
        pl_name = self.app.selected_playlist.get()
        self.app.clear_live_clicks()
        
        if not pl_name or pl_name == 'Select playlist':
            return
            
        if not hasattr(self.app, 'live_clicks_text') or not self.app.live_clicks_text or not self.app.live_clicks_text.winfo_exists():
            return
        
        # Do not auto-launch here; launching is handled by Direct Integration flow
        
        # Fetch actions from MySQL
        self.app.live_clicks_text.config(state='normal')
        self.app.live_clicks_text.delete('1.0', 'end')
        
        try:
            from mysql.mysql_client import get_playlist_id_by_name, get_playlist_actions
            
            playlist_id = get_playlist_id_by_name(pl_name)
            if playlist_id:
                # Store MySQL playlist ID for future action saving
                self.app.current_mysql_playlist_id = playlist_id
                self.logger.info(f"Set current_mysql_playlist_id to {playlist_id} for selected playlist {pl_name}")
                
                actions = get_playlist_actions(playlist_id)
                
                if actions:
                    for i, act in enumerate(actions):
                        if act.get('action_type') == 'click':
                            self.app.live_clicks_text.insert(
                                'end',
                                f"Click {i+1}: x={act.get('x')}, y={act.get('y')}, t={act.get('timestamp'):.2f}s\n"
                            )
                        elif act.get('action_type') in ('key_press', 'key_release'):
                            self.app.live_clicks_text.insert(
                                'end',
                                f"Key {act.get('action_type').replace('key_', '')}: {act.get('key')}, t={act.get('timestamp'):.2f}s\n"
                            )
                    self.app.live_clicks_text.see('end')
                else:
                    self.app.live_clicks_text.insert('end', f'No actions found for playlist "{pl_name}" in MySQL.\n')
            else:
                # Clear MySQL playlist ID if not found
                self.app.current_mysql_playlist_id = None
                self.app.live_clicks_text.insert('end', f'Playlist "{pl_name}" not found in MySQL.\n')
                
        except Exception as e:
            self.app.live_clicks_text.insert('end', f'Error fetching actions from MySQL:\n{e}\n')
            
        self.app.live_clicks_text.config(state='disabled')