import pyautogui
import threading
import time
from PIL import ImageGrab
import db
import os
import logging

# Setup logging - capture INFO for troubleshooting
logging.basicConfig(filename='clickrecorder.log', level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')

# Lazy import for pynput
_mouse_listener = None
_keyboard_listener = None

recording = False
start_time = None
current_playlist_id = None

# Store clicks in memory before committing to DB
click_buffer = []
screenshot_buffer = []
keyboard_event_buffer = []
action_trigger_buffer = []

# Shared state to ensure variable-click is detected reliably from app
variable_target_state = None  # {'variable_name': str, 'test_value': str|None}
suppressed_keyboard_events_remaining = 0  # legacy counter (kept for safety)
suppress_keyboard_events = False  # hard switch to ignore all keyboard events temporarily
RECORD_KEY_EVENTS = False  # master switch: do not record keyboard inputs at all

SCREENSHOTS_DIR = 'screenshots'

# For loop prevention
last_clicks = []  # Store last 3 (x, y) tuples

# Tkinter alert helpers
def show_alert(msg):
    try:
        import tkinter as tk
        from tkinter import messagebox
        root = tk.Tk()
        root.withdraw()
        messagebox.showwarning('ClickRecorder Alert', msg)
        root.destroy()
    except Exception as e:
        logging.error(f'Failed to show alert: {e}')

def show_confirm(msg):
    try:
        import tkinter as tk
        from tkinter import messagebox
        root = tk.Tk()
        root.withdraw()
        result = messagebox.askyesno('ClickRecorder Loop Detected', msg + '\nContinue recording?')
        root.destroy()
        return result
    except Exception as e:
        logging.error(f'Failed to show confirm dialog: {e}')
        return False


def start_recording(playlist_id, on_click_callback=None, on_key_callback=None):
    """
    Start recording mouse clicks, keyboard events, and screenshots for the given playlist.
    Optionally provide on_click_callback(x, y) and on_key_callback(key, event_type).
    """
    global recording, start_time, current_playlist_id, _mouse_listener, click_buffer, screenshot_buffer, last_clicks, _keyboard_listener, keyboard_event_buffer, action_trigger_buffer
    from pynput import mouse, keyboard  # Lazy import
    recording = True
    start_time = time.time()
    current_playlist_id = playlist_id
    click_buffer = []
    screenshot_buffer = []
    keyboard_event_buffer = []
    last_clicks = []
    action_trigger_buffer = []

    def on_click(x, y, button, pressed):
        global recording
        if not recording:
            return False  # Stop listener
        if pressed and button == mouse.Button.left:
            logging.info(f"[on_click] x={x} y={y} pressed={pressed} button={button}")
            # If a modal dialog is open, ignore ALL clicks until it's closed
            try:
                from autoclicker.app import ClickRecorderApp as _App
                _app = _App()
                if hasattr(_app, '_input_dialog') and _app._input_dialog and _app._input_dialog.winfo_exists():
                    logging.info("[on_click] Ignored click because input dialog is open")
                    return
                # Also ignore clicks if subplaylist selection dialog is open
                if hasattr(_app, '_subplaylist_dialog') and _app._subplaylist_dialog and _app._subplaylist_dialog.winfo_exists():
                    logging.info("[on_click] Ignored click because subplaylist dialog is open")
                    return
            except Exception as e:
                logging.error(f"[on_click] Error checking dialog presence: {e}")

            # Check if click is within the auto clicker window or any in-app dialog
            try:
                from autoclicker.app import ClickRecorderApp
                app = ClickRecorderApp()
                if app.winfo_exists():
                    # First, use robust hit-testing to see if the click hits any widget belonging to our app
                    try:
                        widget = app.winfo_containing(int(x), int(y))
                        if widget is not None and widget.winfo_toplevel() == app:
                            logging.info("[on_click] Ignored click inside app window (winfo_containing)")
                            return
                    except Exception as _e_wc:
                        logging.error(f"[on_click] winfo_containing check failed: {_e_wc}")

                    # Fallback to bounds check using root coordinates
                    app_x = app.winfo_rootx()
                    app_y = app.winfo_rooty()
                    app_width = app.winfo_width()
                    app_height = app.winfo_height()
                    if (app_x <= x <= app_x + app_width and 
                        app_y <= y <= app_y + app_height):
                        logging.info("[on_click] Ignored click inside app window (bounds)")
                        return

                    # Also ignore clicks within input or subplaylist dialog if open
                    try:
                        if hasattr(app, '_input_dialog') and app._input_dialog and app._input_dialog.winfo_exists():
                            dlg = app._input_dialog
                            # Prefer hit-test
                            try:
                                w = dlg.winfo_containing(int(x), int(y))
                                if w is not None and w.winfo_toplevel() == dlg:
                                    logging.info("[on_click] Ignored click inside input dialog (winfo_containing)")
                                    return
                            except Exception:
                                pass
                            # Fallback bounds with root coordinates
                            dlg_x = dlg.winfo_rootx()
                            dlg_y = dlg.winfo_rooty()
                            dlg_w = dlg.winfo_width()
                            dlg_h = dlg.winfo_height()
                            if (dlg_x <= x <= dlg_x + dlg_w and dlg_y <= y <= dlg_y + dlg_h):
                                logging.info("[on_click] Ignored click inside input dialog (bounds)")
                                return
                        if hasattr(app, '_subplaylist_dialog') and app._subplaylist_dialog and app._subplaylist_dialog.winfo_exists():
                            dlg2 = app._subplaylist_dialog
                            try:
                                w2 = dlg2.winfo_containing(int(x), int(y))
                                if w2 is not None and w2.winfo_toplevel() == dlg2:
                                    logging.info("[on_click] Ignored click inside subplaylist dialog (winfo_containing)")
                                    return
                            except Exception:
                                pass
                            dlg2_x = dlg2.winfo_rootx()
                            dlg2_y = dlg2.winfo_rooty()
                            dlg2_w = dlg2.winfo_width()
                            dlg2_h = dlg2.winfo_height()
                            if (dlg2_x <= x <= dlg2_x + dlg2_w and dlg2_y <= y <= dlg2_y + dlg2_h):
                                logging.info("[on_click] Ignored click inside subplaylist dialog (bounds)")
                                return
                    except Exception:
                        pass
            except Exception:
                pass  # If we can't check window bounds, proceed with click
                
            timestamp = time.time() - start_time
            # Loop prevention: check last 3 clicks
            last_clicks.append((x, y))
            if len(last_clicks) > 3:
                last_clicks.pop(0)
            if len(last_clicks) == 3 and last_clicks[0] == last_clicks[1] == last_clicks[2]:
                # Loop detected, allow user override
                logging.error(f'Loop detected at ({x}, {y}) in playlist {playlist_id}')
                if not show_confirm('Loop detected: 3 consecutive identical clicks.'):
                    show_alert('Recording will stop due to loop detection.')
                    stop_recording()
                    return False
            try:
                # Only record clicks and save screenshots if recording is True
                if recording:
                    # Check for variable input state
                    from autoclicker.app import ClickRecorderApp
                    app = ClickRecorderApp()
                    
                    # Prefer module-level state, fallback to app state
                    state = None
                    if variable_target_state:
                        state = {'step': 'selecting_input', **variable_target_state}
                    elif hasattr(app, 'variable_input_state') and app.variable_input_state:
                        state = app.variable_input_state

                    if state and state.get('step') == 'selecting_input':
                            # Record click location with variable name for future playback
                            logging.info(f"[on_click] Variable target click detected for '{state.get('variable_name')}' at ({x},{y})")
                            record_click(x, y, timestamp, playlist_id, state.get('variable_name'))
                            # Clear variable mode BEFORE typing so UI resumes immediately
                            try:
                                app.variable_input_state = None
                            except Exception:
                                pass
                            try:
                                clear_variable_target_mode()
                            except Exception:
                                pass
                            # Update UI status on Tk thread
                            try:
                                app.after(0, lambda: app.status_var.set('Recording'))
                            except Exception:
                                pass
                            # Type the provided test value to keep the user flow moving (no Enter)
                            try:
                                test_value = state.get('test_value')
                                if test_value is not None:
                                    # Estimate number of key events we will generate and suppress them (press+release per char)
                                    try:
                                        global suppressed_keyboard_events_remaining
                                        suppressed_keyboard_events_remaining += max(0, len(str(test_value)) * 2)
                                        logging.info(f"[variable] Suppressing next {suppressed_keyboard_events_remaining} keyboard events for programmatic typing")
                                    except Exception:
                                        pass
                                    _spawn_focus_and_type_thread(x, y, str(test_value))
                            except Exception as e:
                                logging.error(f'Error scheduling type of test value: {e}')
                            return
                    
                    # Normal click recording
                    logging.info(f"[on_click] Recording normal click at ({x},{y})")
                    record_click(x, y, timestamp, playlist_id)
                    if on_click_callback:
                        on_click_callback(x, y)
            except Exception as e:
                logging.error(f'Error recording click: {e}')
                show_alert(f'Error recording click: {e}')

    def on_press(key):
        if not recording or not RECORD_KEY_EVENTS:
            return False
            
        # Global suppression (during programmatic typing)
        try:
            if suppress_keyboard_events:
                logging.info('[on_press] Suppressed due to global flag')
                return
        except Exception:
            pass

        # Check if we're in variable input mode
        try:
            from autoclicker.app import ClickRecorderApp
            app = ClickRecorderApp()
            
            # Don't record keys in input dialog
            if hasattr(app, '_input_dialog') and app._input_dialog and app._input_dialog.winfo_exists():
                return
                
            # Don't record keys during variable input
            if hasattr(app, 'variable_input_state') and app.variable_input_state:
                return  # Skip recording any keys during variable input
                
        except Exception:
            pass  # If we can't check, proceed with recording
        
        # Suppress programmatic typing events
        try:
            global suppressed_keyboard_events_remaining
            if suppressed_keyboard_events_remaining > 0:
                suppressed_keyboard_events_remaining -= 1
                logging.info(f"[on_press] Suppressed programmatic key; remaining={suppressed_keyboard_events_remaining}")
                return
        except Exception:
            pass
            
        try:
            k = key.char if hasattr(key, 'char') and key.char is not None else str(key)
        except Exception:
            k = str(key)
        timestamp = time.time() - start_time
        keyboard_event_buffer.append({
            'playlist_id': playlist_id,
            'key': k,
            'event_type': 'press',
            'timestamp': timestamp
        })
        if on_key_callback:
            on_key_callback(k, 'press')

    def on_release(key):
        if not recording or not RECORD_KEY_EVENTS:
            return False
            
        # Global suppression (during programmatic typing)
        try:
            if suppress_keyboard_events:
                logging.info('[on_release] Suppressed due to global flag')
                return
        except Exception:
            pass

        # Check if we're in variable input mode
        try:
            from autoclicker.app import ClickRecorderApp
            app = ClickRecorderApp()
            if hasattr(app, '_input_dialog') and app._input_dialog and app._input_dialog.winfo_exists():
                return  # Don't record keys while in input dialog
        except Exception:
            pass  # If we can't check, proceed with recording
        
        # Suppress programmatic typing events
        try:
            global suppressed_keyboard_events_remaining
            if suppressed_keyboard_events_remaining > 0:
                suppressed_keyboard_events_remaining -= 1
                logging.info(f"[on_release] Suppressed programmatic key; remaining={suppressed_keyboard_events_remaining}")
                return
        except Exception:
            pass
            
        try:
            k = key.char if hasattr(key, 'char') and key.char is not None else str(key)
        except Exception:
            k = str(key)
        timestamp = time.time() - start_time
        keyboard_event_buffer.append({
            'playlist_id': playlist_id,
            'key': k,
            'event_type': 'release',
            'timestamp': timestamp
        })
        if on_key_callback:
            on_key_callback(k, 'release')

    _mouse_listener = mouse.Listener(on_click=on_click)
    _mouse_listener.start()
    _keyboard_listener = keyboard.Listener(on_press=on_press, on_release=on_release)
    _keyboard_listener.start()


def stop_recording():
    """Stop the current recording session and save clicks/screenshots/keyboard events to DB."""
    global recording, _mouse_listener, click_buffer, screenshot_buffer, _keyboard_listener, keyboard_event_buffer, action_trigger_buffer
    recording = False
    if _mouse_listener:
        _mouse_listener.stop()
        _mouse_listener = None
    if '_keyboard_listener' in globals() and _keyboard_listener:
        _keyboard_listener.stop()
        _keyboard_listener = None
    try:
        # Save all buffered clicks to DB
        conn = db.get_connection()
        cur = conn.cursor()
        for click in click_buffer:
            cur.execute(
                "INSERT INTO Clicks (playlist_id, x, y, timestamp, variable_name) VALUES (?, ?, ?, ?, ?)",
                (click['playlist_id'], click['x'], click['y'], click['timestamp'], click.get('variable_name'))
            )
        for shot in screenshot_buffer:
            cur.execute(
                "INSERT INTO Screenshots (playlist_id, path, is_manual) VALUES (?, ?, ?)",
                (shot['playlist_id'], shot['path'], shot['is_manual'])
            )
        for event in keyboard_event_buffer:
            cur.execute(
                "INSERT INTO KeyboardEvents (playlist_id, key, event_type, timestamp) VALUES (?, ?, ?, ?)",
                (event['playlist_id'], event['key'], event['event_type'], event['timestamp'])
            )
        for trig in action_trigger_buffer:
            cur.execute(
                "INSERT INTO ActionTriggers (playlist_id, action_type, timestamp, payload) VALUES (?, ?, ?, ?)",
                (trig['playlist_id'], trig['action_type'], trig['timestamp'], trig.get('payload'))
            )
        conn.commit()
        conn.close()
    except Exception as e:
        logging.error(f'Error saving to database: {e}')
        show_alert(f'Error saving to database: {e}')
    click_buffer = []
    screenshot_buffer = []
    keyboard_event_buffer = []
    action_trigger_buffer = []


def record_click(x, y, timestamp, playlist_id, variable_name=None):
    """Record a mouse click or variable input location."""
    try:
        if variable_name:
            logging.info(f"[record_click] click with variable_name='{variable_name}' at ({x},{y}) t={timestamp}")
        else:
            logging.info(f"[record_click] click at ({x},{y}) t={timestamp}")
    except Exception:
        pass
    click_data = {
        'playlist_id': playlist_id,
        'x': x,
        'y': y,
        'timestamp': timestamp,
        'variable_name': variable_name
    }
    click_buffer.append(click_data)
    # Disabled: Capture screenshot automatically at each click
    # try:
    #     capture_screenshot(playlist_id, is_manual=False)
    # except PermissionError as e:
    #     logging.error(f'Permission error capturing screenshot: {e}')
    #     show_alert(f'Permission error capturing screenshot: {e}')
    # except OSError as e:
    #     if 'disk' in str(e).lower() or 'space' in str(e).lower():
    #         logging.error(f'Disk error capturing screenshot: {e}')
    #         show_alert(f'Disk error: {e}')
    #     else:
    #         logging.error(f'OS error capturing screenshot: {e}')
    #         show_alert(f'OS error: {e}')
    # except Exception as e:
    #     logging.error(f'Error capturing screenshot: {e}')
    #     show_alert(f'Error capturing screenshot: {e}')


def capture_screenshot(playlist_id, is_manual=False, playlist_name=None):
    """Capture a screenshot and save it to the playlist-named folder. Returns the full path to the saved screenshot."""
    if not playlist_name:
        raise ValueError("playlist_name is required for saving screenshots.")
    # Ensure playlist-specific folder exists (by name, not id)
    playlist_folder = os.path.join(SCREENSHOTS_DIR, str(playlist_name))
    os.makedirs(playlist_folder, exist_ok=True)
    # Filename: click_<timestamp or count>.png
    import time
    filename = f"click_{int(time.time() * 1000)}.png"
    path = os.path.join(playlist_folder, filename)
    # Capture full screen
    from PIL import ImageGrab
    img = ImageGrab.grab()
    img.save(path, 'PNG')
    # Store relative path for DB
    rel_path = os.path.relpath(path)
    screenshot_buffer.append({
        'playlist_id': playlist_id,
        'path': rel_path,
        'is_manual': 1 if is_manual else 0
    })
    return path


def record_action_trigger(playlist_id, action_type, timestamp=None, payload=None):
    """Record a non-click action trigger such as 'screenshot'."""
    global start_time
    if timestamp is None:
        timestamp = 0.0 if start_time is None else time.time() - start_time
    action_trigger_buffer.append({
        'playlist_id': playlist_id,
        'action_type': action_type,
        'timestamp': timestamp,
        'payload': payload
    })

def record_subplaylist_action(playlist_id, subplaylist_id, timestamp=None):
    """Record a subplaylist action trigger."""
    if timestamp is None:
        timestamp = 0.0 if start_time is None else time.time() - start_time
    
    try:
        # Save to MySQL
        from mysql.mysql_client import save_subplaylist_action
        save_subplaylist_action(playlist_id, subplaylist_id, timestamp)
        
        # Also save to SQLite for local recording
        conn = db.get_connection()
        cur = conn.cursor()
        cur.execute(
            'INSERT INTO ActionTriggers (playlist_id, action_type, timestamp, payload) VALUES (?, ?, ?, ?)',
            (playlist_id, 'subplaylist', timestamp, str(subplaylist_id))
        )
        conn.commit()
        conn.close()
        
        logging.info(f"Recorded subplaylist action: playlist_id={playlist_id}, subplaylist_id={subplaylist_id}")
        
    except Exception as e:
        logging.error(f"Error recording subplaylist action: {e}")


def _type_text_safely(text: str) -> None:
    """Type text reliably into the active field, handling shift state and small delays.
    This avoids Ctrl+V; we simulate regular typing.
    """
    try:
        # Ensure no modifier keys stuck
        try:
            pyautogui.keyUp('shift')
            pyautogui.keyUp('ctrl')
            pyautogui.keyUp('alt')
        except Exception:
            pass
        time.sleep(0.05)
        pyautogui.typewrite(text, interval=0.02)
    except Exception as e:
        logging.error(f'Typing failed: {e}')


def _spawn_focus_and_type_thread(x: int, y: int, text: str) -> None:
    """Run focus-and-type in a background thread so it doesn't block the listener."""
    def _worker():
        try:
            logging.info(f"[type_thread] Typing len={len(text)} (no extra clicks)")
            time.sleep(0.1)
            # Enable hard suppression of keyboard events while we type
            try:
                suppress_keyboard_events = True
            except Exception:
                pass
            _type_text_safely(text)
            logging.info("[type_thread] Done typing test value")
        except Exception as e:
            logging.error(f"[type_thread] Error: {e}")
        finally:
            # Re-enable keyboard capture
            try:
                suppress_keyboard_events = False
            except Exception:
                pass
    t = threading.Thread(target=_worker, daemon=True)
    t.start()


def set_variable_target_mode(variable_name: str, test_value: str | None) -> None:
    """Enable variable-target mode so the next user click is treated as the insert point."""
    global variable_target_state
    variable_target_state = {'variable_name': variable_name, 'test_value': test_value}


def clear_variable_target_mode() -> None:
    """Disable variable-target mode."""
    global variable_target_state
    variable_target_state = None


# Removed clipboard paste helper; not used in recording anymore

# Lazy loading example: only import heavy modules when needed
def lazy_import_pynput():
    global mouse
    import pynput.mouse as mouse
    return mouse 