"""
Recording functionality for the auto clicker application.
"""

import logging
import os
import db
import recorder


class RecordingManager:
    """Manages recording functionality."""
    
    def __init__(self, app):
        self.app = app
        self._hb_after_id = None
        
    def start_recording(self):
        """Start recording clicks and key events."""
        self.app.status_var.set('Recording')
        # Do not clear live clicks; continue from existing recorded actions
        try:
            # Ensure the Live Clicks panel reflects current SQLite actions at start
            if hasattr(self, 'repopulate_live_clicks_from_sqlite'):
                self.repopulate_live_clicks_from_sqlite()
        except Exception:
            pass
        self.app.recording = True
        self.app.is_variable_input = False  # Initialize variable input state
        # If we are starting a brand-new recording flow (likely a new playlist),
        # clear any previous MySQL playlist id so we don't append to the old one.
        try:
            self.app.current_mysql_playlist_id = None
        except Exception:
            pass
        
        # Normalize playlist name: prefer recording_name_var, fallback to selected_playlist; always strip
        try:
            name_from_var = (self.app.recording_name_var.get() or '').strip() if getattr(self.app, 'recording_name_var', None) else ''
        except Exception:
            name_from_var = ''
        try:
            name_from_sel = (self.app.selected_playlist.get() or '').strip() if getattr(self.app, 'selected_playlist', None) else ''
        except Exception:
            name_from_sel = ''
        pl_name = name_from_var or name_from_sel
        # Keep UI variables in sync with normalized name
        try:
            if getattr(self.app, 'selected_playlist', None):
                self.app.selected_playlist.set(pl_name)
        except Exception:
            pass
        try:
            if getattr(self.app, 'recording_name_var', None):
                self.app.recording_name_var.set(pl_name)
        except Exception:
            pass
        if not pl_name or pl_name == 'Select playlist':
            self.app.status_var.set('Idle')
            self.app.recording = False
            return
            
        # Ensure there is a local SQLite playlist row for recording storage
        conn = db.get_connection()
        cur = conn.cursor()
        cur.execute('SELECT id FROM Playlists WHERE name = ?', (pl_name,))
        row = cur.fetchone()
        if not row:
            # Create a local playlist row so recording has a playlist_id to reference
            from datetime import datetime as _dt
            cur.execute('INSERT INTO Playlists (name, created_date) VALUES (?, ?)', (pl_name, _dt.now().isoformat()))
            conn.commit()
            cur.execute('SELECT id FROM Playlists WHERE name = ?', (pl_name,))
            row = cur.fetchone()
        conn.close()
        
        if not row:
            self.app.status_var.set('Idle')
            self.app.recording = False
            return
            
        playlist_id = row[0]
        # Store local SQLite playlist id for use by in-app triggers
        try:
            self.app.current_local_playlist_id = playlist_id
        except Exception:
            pass
        
        # Attempt to acquire instance lock and start a periodic heartbeat while recording
        try:
            self.app.acquire_instance_lock_if_possible()
        except Exception:
            pass
        try:
            self._start_recording_heartbeat_timer()
        except Exception:
            pass

        # Start recording with callbacks
        recorder.start_recording(
            playlist_id,
            on_click_callback=self.app.append_live_click,
            on_key_callback=self.app.append_live_key
        )

    def stop_recording(self, *, skip_save: bool = False):
        """Stop recording and optionally skip saving actions to MySQL.

        Args:
            skip_save: When True, do not perform the DB save step.
        """
        self.app.status_var.set('Idle')
        self.app.recording = False
        recorder.stop_recording()
        # Stop heartbeat timer and release lock
        try:
            if self._hb_after_id is not None:
                try:
                    self.app.after_cancel(self._hb_after_id)
                except Exception:
                    pass
                self._hb_after_id = None
        except Exception:
            pass
        try:
            self.app.release_instance_lock()
        except Exception:
            pass

        if skip_save:
            return

        logging.basicConfig(
            filename='app_debug.log',
            level=logging.INFO,
            format='%(asctime)s %(levelname)s: %(message)s'
        )

        # Show summary and save to MySQL
        try:
            self._save_recording_to_mysql()
        except Exception as e:
            logging.error(f"Error in stop_recording: {e}")

    def _save_recording_to_mysql(self):
        """Save recorded actions to MySQL."""
        conn = db.get_connection()
        cur = conn.cursor()
        pl_name = self.app.selected_playlist.get()
        
        cur.execute('SELECT id FROM Playlists WHERE name = ?', (pl_name,))
        row = cur.fetchone()
        if not row:
            conn.close()
            return
        local_playlist_id = row[0]
        
        # Get clicks and keyboard events
        cur.execute(
            'SELECT x, y, timestamp, variable_name FROM Clicks WHERE playlist_id = ? ORDER BY timestamp ASC',
            (local_playlist_id,)
        )
        clicks = cur.fetchall()
        
        cur.execute(
            'SELECT key, event_type, timestamp FROM KeyboardEvents WHERE playlist_id = ? ORDER BY timestamp ASC',
            (local_playlist_id,)
        )
        keys = cur.fetchall()

        # Get action triggers (e.g., screenshot)
        try:
            cur.execute(
                'SELECT action_type, timestamp, payload FROM ActionTriggers WHERE playlist_id = ? ORDER BY timestamp ASC',
                (local_playlist_id,)
            )
            triggers = cur.fetchall()
        except Exception:
            triggers = []
        
        # Update live clicks view
        self._update_live_clicks_view(clicks, keys, triggers)
        
        # Save to MySQL only for existing (MySQL-backed) playlists. For new playlists,
        # we defer saving until after the user clicks Save (when a MySQL id exists).
        try:
            mysql_playlist_id = getattr(self.app, 'current_mysql_playlist_id', None)
            logging.info(f"Current MySQL playlist ID from app: {mysql_playlist_id}")
        except Exception as e:
            logging.error(f"Error getting current_mysql_playlist_id: {e}")
            mysql_playlist_id = None

        # If source indicates new/unsaved (sqlite), do not push now
        try:
            src = getattr(self.app, 'playlist_actions_source', None)
        except Exception:
            src = None

        if mysql_playlist_id is None or src == 'sqlite':
            # Try resolve by name for existing playlists
            try:
                from mysql.mysql_client import get_playlist_id_by_name
                mysql_playlist_id = get_playlist_id_by_name(pl_name)
                logging.info(f"Looked up MySQL playlist ID by name '{pl_name}': {mysql_playlist_id}")
                if mysql_playlist_id:
                    self.app.current_mysql_playlist_id = mysql_playlist_id
            except Exception as e:
                logging.error(f"Error looking up playlist by name: {e}")
                mysql_playlist_id = None

        if mysql_playlist_id is not None and src != 'sqlite':
            # We can push to MySQL now
            logging.info(f"Saving {len(clicks)} clicks and {len(keys)} keys to MySQL for playlist ID {mysql_playlist_id}")
            self._save_actions_to_mysql(clicks, keys, triggers, mysql_playlist_id, pl_name)
        else:
            logging.info(f"No MySQL playlist ID found for '{pl_name}' - deferring save until playlist is saved to MySQL")
        # else: defer pushing to MySQL until Save is clicked in new-playlist flow
        
        conn.close()
        
        # Debug: Check what we have after recording stops
        try:
            self.app.playlist_manager.debug_action_saving(pl_name)
        except Exception as e:
            logging.error(f"Error in debug call from recording: {e}")

    def _update_live_clicks_view(self, clicks, keys, triggers):
        """Update the live clicks view with recorded actions."""
        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
            
        self.app.live_clicks_text.config(state='normal')
        self.app.live_clicks_text.delete('1.0', 'end')
        
        for i, click_row in enumerate(clicks):
            # Support both (x,y,ts) and (x,y,ts,variable_name)
            if len(click_row) >= 4 and click_row[3]:
                x, y, ts, var = click_row[0], click_row[1], click_row[2], click_row[3]
                self.app.live_clicks_text.insert('end', f'Click {i+1}: x={x}, y={y}, t={ts:.2f}s var={var}\n')
            else:
                x, y, ts = click_row[0], click_row[1], click_row[2]
                self.app.live_clicks_text.insert('end', f'Click {i+1}: x={x}, y={y}, t={ts:.2f}s\n')
            
        for i, (key, event_type, ts) in enumerate(keys):
            self.app.live_clicks_text.insert('end', f'Key {event_type}: {key}, t={ts:.2f}s\n')

        # Show triggers
        for i, (action_type, ts, payload) in enumerate([(t[0], t[1], t[2] if len(t) > 2 else None) for t in triggers]):
            self.app.live_clicks_text.insert('end', f'Action: {action_type}, t={ts:.2f}s\n')
            
        self.app.live_clicks_text.see('end')
        self.app.live_clicks_text.config(state='disabled')

    def repopulate_live_clicks_from_sqlite(self) -> None:
        """Repopulate the live clicks UI from SQLite for the currently selected playlist.

        This is useful after the UI is rebuilt (e.g., after stopping recording) so the
        previously recorded actions remain visible in the Live Clicks panel.
        """
        try:
            if not hasattr(self.app, 'selected_playlist') or not self.app.selected_playlist:
                return
            pl_name = self.app.selected_playlist.get()
            if not pl_name or pl_name in ('Select playlist', 'No playlists found'):
                return
            conn = db.get_connection()
            cur = conn.cursor()
            cur.execute('SELECT id FROM Playlists WHERE name = ?', (pl_name,))
            row = cur.fetchone()
            if not row:
                conn.close()
                return
            local_playlist_id = row[0]
            # Fetch actions
            cur.execute(
                'SELECT x, y, timestamp, variable_name FROM Clicks WHERE playlist_id = ? ORDER BY timestamp ASC',
                (local_playlist_id,)
            )
            clicks = cur.fetchall()
            cur.execute(
                'SELECT key, event_type, timestamp FROM KeyboardEvents WHERE playlist_id = ? ORDER BY timestamp ASC',
                (local_playlist_id,)
            )
            keys = cur.fetchall()
            try:
                cur.execute(
                    'SELECT action_type, timestamp, payload FROM ActionTriggers WHERE playlist_id = ? ORDER BY timestamp ASC',
                    (local_playlist_id,)
                )
                triggers = cur.fetchall()
            except Exception:
                triggers = []
            conn.close()
            # Update the UI panel
            self._update_live_clicks_view(clicks, keys, triggers)
        except Exception:
            # Best-effort only; avoid surfacing exceptions in UI flow
            try:
                import logging as _logging
                _logging.exception("Failed to repopulate live clicks from SQLite")
            except Exception:
                pass

    def _save_actions_to_mysql(self, clicks, keys, triggers, playlist_mysql_id, playlist_name):
        """Save actions to MySQL."""
        try:
            logging.info(f"_save_actions_to_mysql called: {len(clicks)} clicks, {len(keys)} keys, {len(triggers)} triggers, playlist_id={playlist_mysql_id}, name='{playlist_name}'")
            from mysql.mysql_client import save_action_to_mysql
            
            # Save clicks (include variable_name when present)
            saved_clicks = 0
            for click_row in clicks:
                try:
                    # Heartbeat periodically during large saves
                    try:
                        hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
                        self.app.send_heartbeat_throttled(min_interval_sec=hb_interval)
                    except Exception:
                        pass
                    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
                    logging.info(f"[MySQL] Saving click: x={x} y={y} ts={ts} var={variable_name}")
                    result = save_action_to_mysql(playlist_mysql_id, 'click', x, y, None, ts, playlist_name, variable_name=variable_name)
                    if result and result.get('data'):
                        try:
                            saved_id = result['data'][0]['id']
                            saved_var = result['data'][0].get('variable_name')
                            logging.info(f"[MySQL] Click saved id={saved_id} var={saved_var}")
                        except Exception:
                            pass
                        saved_clicks += 1
                    else:
                        logging.error(f"Click save returned no data: {result}")
                except Exception as e:
                    logging.error(f"Failed to save click to MySQL: {e}")
                    
            # Save keyboard events
            saved_keys = 0
            for key, event_type, ts in keys:
                try:
                    # Heartbeat periodically during large saves
                    try:
                        hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
                        self.app.send_heartbeat_throttled(min_interval_sec=hb_interval)
                    except Exception:
                        pass
                    action_type = 'key_press' if event_type == 'press' else 'key_release'
                    result = save_action_to_mysql(playlist_mysql_id, action_type, None, None, key, ts, playlist_name)
                    if result and result.get('data'):
                        saved_keys += 1
                    else:
                        logging.error(f"Key save returned no data: {result}")
                except Exception as e:
                    logging.error(f"Failed to save key event to MySQL: {e}")
            
            logging.info(f"MySQL save completed: {saved_clicks}/{len(clicks)} clicks, {saved_keys}/{len(keys)} keys saved")
            
            # Save action triggers (e.g., screenshot, loop, search, subplaylist)
            saved_triggers = 0
            for action_type, ts, payload in [(t[0], t[1], t[2] if len(t) > 2 else None) for t in triggers]:
                try:
                    # Heartbeat periodically during large saves
                    try:
                        hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
                        self.app.send_heartbeat_throttled(min_interval_sec=hb_interval)
                    except Exception:
                        pass
                    # For loop trigger, persist as an action row with action_type='loop' and key set to variable_name for traceability
                    if action_type == 'loop':
                        logging.info(f"[MySQL] Saving loop trigger with variable='{payload}' ts={ts}")
                        result = save_action_to_mysql(playlist_mysql_id, 'loop', None, None, payload, ts, playlist_name)
                    elif action_type == 'search':
                        # Persist the search name (query) into the key field so playback can resolve value from Excel later
                        try:
                            import json as _json
                            query_name = None
                            if payload:
                                parsed = None
                                # Decode bytes if needed
                                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 JSON parse
                                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

                                # Extract 'query' field if dict
                                if isinstance(parsed, dict):
                                    q = parsed.get('query')
                                    if isinstance(q, (str, int, float)):
                                        query_name = str(q).strip() or None
                                # As a last resort, if payload_str is a plain string, use it
                                if query_name is None and isinstance(payload_str, str) and payload_str.strip():
                                    query_name = payload_str.strip()
                        except Exception:
                            query_name = None
                        logging.info(f"[MySQL] Saving search trigger query='{query_name}' ts={ts}")
                        result = save_action_to_mysql(playlist_mysql_id, 'search', None, None, query_name, ts, playlist_name)
                    elif action_type == 'subplaylist':
                        # Persist a subplaylist action with its subplaylist_id
                        try:
                            sub_id = None
                            # Payload may be stored as str/int; coerce safely
                            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:
                                logging.info(f"[MySQL] Saving subplaylist trigger with subplaylist_id={sub_id} ts={ts}")
                                _ = _save_sub(playlist_mysql_id, sub_id, ts, playlist_name)
                                # save_subplaylist_action commits internally; treat as saved
                                result = {'data': [{'id': None}]}
                            else:
                                # Fallback to generic save (will miss subplaylist_id)
                                logging.warning("[MySQL] save_subplaylist_action unavailable; falling back to generic action save without subplaylist_id")
                                result = save_action_to_mysql(playlist_mysql_id, 'subplaylist', None, None, None, ts, playlist_name)
                        else:
                            logging.error(f"[MySQL] Invalid subplaylist payload '{payload}' - skipping save")
                            result = None
                    else:
                        result = save_action_to_mysql(playlist_mysql_id, action_type, None, None, None, ts, playlist_name)
                    if result and result.get('data'):
                        saved_triggers += 1
                    else:
                        logging.error(f"Trigger save returned no data: {result}")
                except Exception as e:
                    logging.error(f"Failed to save trigger to MySQL: {e}")

            logging.info(f"MySQL triggers save completed: {saved_triggers}/{len(triggers)} triggers saved")
            
            # If we have any variable-target clicks captured live during this session, send them with variable_name
            try:
                from mysql.mysql_client import save_action_to_mysql as _save
                # Replay the Clicks from SQLite to see if our app recorded variable-targets
                # Note: SQLite Clicks does not store variable_name; variable clicks are sent live during recording.
                # Here we only log a reminder.
                logging.info("[Recording] Variable-target clicks are sent live during recording with variable_name.")
            except Exception:
                pass
                    
        except Exception as e:
            logging.error(f"Failed to save actions to MySQL: {e}")

    def append_live_click(self, x, y):
        """Append a live click to the display."""
        if not self.app.recording:
            return
        # Opportunistic heartbeat on user activity
        try:
            hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
            self.app.send_heartbeat_throttled(min_interval_sec=hb_interval)
        except Exception:
            pass

        # If we are in variable-target mode but the low-level hook didn't catch it,
        # convert this last click into a variable click and type the test value.
        try:
            import time as _time
            import recorder as _rec
            state = None
            if hasattr(_rec, 'variable_target_state') and _rec.variable_target_state:
                state = {'step': 'selecting_input', **_rec.variable_target_state}
            elif hasattr(self.app, 'variable_input_state') and self.app.variable_input_state:
                st = self.app.variable_input_state
                if st.get('step') == 'selecting_input':
                    state = st
            if state:
                # Update last click in buffer to attach variable name if possible
                try:
                    if _rec.click_buffer:
                        _rec.click_buffer[-1]['variable_name'] = state.get('variable_name')
                    else:
                        # Fallback: record a new click with computed timestamp
                        ts = 0.0
                        try:
                            ts = _time.time() - (_rec.start_time or _time.time())
                        except Exception:
                            pass
                        _rec.record_click(x, y, ts, _rec.current_playlist_id, state.get('variable_name'))
                except Exception:
                    pass
                # Type the test value into the target field to keep flow moving
                try:
                    tv = state.get('test_value')
                    if tv is not None:
                        import pyautogui as _pg
                        _pg.click(x, y)
                        _pg.typewrite(str(tv), interval=0.02)
                except Exception:
                    pass
                # Clear variable modes
                try:
                    self.app.variable_input_state = None
                except Exception:
                    pass
                try:
                    _rec.clear_variable_target_mode()
                except Exception:
                    pass
                # Update status
                try:
                    self.app.status_var.set('Recording')
                except Exception:
                    pass
                # Do not append a plain live click line; we handled it
                return
        except Exception:
            # Fall through to default live click appending
            pass

        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
            
        self.app.live_clicks.append((x, y))
        self.app.live_clicks_text.config(state='normal')
        self.app.live_clicks_text.insert('end', f'Click: x={x}, y={y}\n')
        self.app.live_clicks_text.see('end')
        self.app.live_clicks_text.config(state='disabled')

    def append_live_key(self, key, event_type):
        """Append a live key event to the display."""
        if not self.app.recording:
            return
        # Opportunistic heartbeat on user activity
        try:
            hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
            self.app.send_heartbeat_throttled(min_interval_sec=hb_interval)
        except Exception:
            pass
            
        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
            
        self.app.live_clicks_text.config(state='normal')
        self.app.live_clicks_text.insert('end', f'Key {event_type}: {key}\n')
        self.app.live_clicks_text.see('end')
        self.app.live_clicks_text.config(state='disabled')

    def clear_live_clicks(self):
        """Clear the live clicks display."""
        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
            
        self.app.live_clicks = []
        self.app.live_clicks_text.config(state='normal')
        self.app.live_clicks_text.delete('1.0', 'end')
        self.app.live_clicks_text.config(state='disabled')

    # --- Heartbeat scheduling for recording ---------------------------------
    def _start_recording_heartbeat_timer(self):
        """Schedule a periodic heartbeat while recording using Tk's event loop."""
        try:
            interval_ms = int(float(os.getenv("HB_INTERVAL_SEC", "60")) * 1000)
        except Exception:
            interval_ms = 60000
        # Inner tick function
        def _tick():
            try:
                if getattr(self.app, 'recording', False):
                    try:
                        hb_interval = float(os.getenv("HB_INTERVAL_SEC", "60"))
                        self.app.send_heartbeat_throttled(min_interval_sec=hb_interval)
                    except Exception:
                        pass
                    # Reschedule while recording
                    try:
                        self._hb_after_id = self.app.after(interval_ms, _tick)
                    except Exception:
                        self._hb_after_id = None
                else:
                    self._hb_after_id = None
            except Exception:
                self._hb_after_id = None
        # Kick off the first tick
        try:
            self._hb_after_id = self.app.after(interval_ms, _tick)
        except Exception:
            self._hb_after_id = None