"""Playback functionality for viewing code evolution."""
import os
import sys
import time
import threading
from datetime import datetime
from typing import Any


class KeyboardListener:
    """Simple keyboard listener using threading to avoid terminal mode changes."""

    def __init__(self):
        self.paused = False
        self.stop_flag = False
        self._lock = threading.Lock()
        try:
            import msvcrt
            self.WINDOWS = True
        except ImportError:
            import termios
            import tty
            self.WINDOWS = False
            self.termios = termios
            self.tty = tty

    def _listen_loop(self):
        """Background thread that listens for keyboard input."""
        if self.WINDOWS:
            import msvcrt
            while not self.stop_flag:
                if msvcrt.kbhit():
                    char = msvcrt.getch().decode('utf-8', errors='ignore')
                    if char in (' ', 'k'):
                        with self._lock:
                            self.paused = not self.paused
                        # Flush remaining input
                        while msvcrt.kbhit():
                            msvcrt.getch()
                time.sleep(0.05)
        else:
            # Unix: use termios in cbreak mode (allows signals but single-char input)
            old_settings = None
            try:
                old_settings = self.termios.tcgetattr(sys.stdin)
                self.tty.setcbreak(sys.stdin.fileno())  # cbreak not raw - allows signals

                while not self.stop_flag:
                    import select
                    if select.select([sys.stdin], [], [], 0.05)[0]:
                        char = sys.stdin.read(1)
                        if char in (' ', 'k'):
                            with self._lock:
                                self.paused = not self.paused
                            # Flush remaining
                            while select.select([sys.stdin], [], [], 0)[0]:
                                sys.stdin.read(1)
            finally:
                if old_settings:
                    self.termios.tcsetattr(sys.stdin, self.termios.TCSADRAIN, old_settings)

    def start(self):
        """Start the keyboard listener thread."""
        self._thread = threading.Thread(target=self._listen_loop, daemon=True)
        self._thread.start()

    def stop(self):
        """Stop the keyboard listener thread."""
        self.stop_flag = True
        if hasattr(self, '_thread'):
            self._thread.join(timeout=1.0)

    def is_paused(self) -> bool:
        """Check if playback is currently paused."""
        with self._lock:
            return self.paused

    def toggle_pause(self):
        """Manually toggle pause state."""
        with self._lock:
            self.paused = not self.paused
            return self.paused


def playback_recording(
    json_data: tuple[dict[str, Any], ...],
    document: str,
    template: str,
    speed: float = 1.0,
    start_event: int = 0,
) -> None:
    """
    Play back a recording, showing the code evolving in real-time.

    Only plays back edit events (type="edit" or no type field for backwards compatibility).
    Handles file renames by matching on extension.

    Parameters
    ----------
    json_data : tuple[dict[str, Any], ...]
        The recording events (all event types)
    document : str
        The document to play back
    template : str
        The initial template content
    speed : float
        Playback speed multiplier (1.0 = real-time, 2.0 = 2x speed, 0.5 = half speed)
    start_event : int
        Event index to start playback from (0 = beginning, default: 0)
    """
    # Filter to only edit events (backwards compatible)
    from .api.load import is_edit_event
    from .api.document import filter_events_by_document_with_rename_handling

    edit_events = [e for e in json_data if is_edit_event(e)]

    # Filter events for the target document, handling renames with extension matching
    doc_events = list(filter_events_by_document_with_rename_handling(tuple(edit_events), document))

    if not doc_events:
        print(f"No events found for document: {document}", file=sys.stderr)
        return

    # Validate start_event
    if start_event < 0 or start_event >= len(doc_events):
        print(f"Invalid start_event: {start_event} (valid range: 0-{len(doc_events) - 1})", file=sys.stderr)
        return

    # Check if first event is a file-open marker (no-op at offset 0)
    # If so, use it as the initial state instead of the template
    initial_content = template

    if doc_events and doc_events[0].get("offset") == 0:
        first_old = doc_events[0].get("oldFragment", "")
        first_new = doc_events[0].get("newFragment", "")

        # Check if first event is a no-op (file-open marker)
        if first_old == first_new and first_old:
            # Use the file's actual state when opened
            initial_content = first_old

    # Start with the determined initial content
    current_content = initial_content
    last_timestamp = None

    def clear_screen():
        """Clear the terminal screen."""
        os.system('cls' if os.name == 'nt' else 'clear')

    def parse_timestamp(ts_str: str) -> datetime:
        """Parse ISO timestamp string."""
        return datetime.fromisoformat(ts_str.replace("Z", "+00:00"))

    # Apply events up to start_event to get the correct initial state
    if start_event > 0:
        for event in doc_events[:start_event]:
            old_frag = event.get("oldFragment", "")
            new_frag = event.get("newFragment", "")
            offset = event.get("offset", 0)
            if new_frag != old_frag:
                current_content = current_content[:offset] + new_frag + current_content[offset + len(old_frag):]
        last_timestamp = doc_events[start_event - 1].get("timestamp") if start_event > 0 else None

    # Show initial state
    clear_screen()
    print(f"=" * 80)
    print(f"PLAYBACK: {document} (Speed: {speed}x)")
    print(f"Event {start_event} / {len(doc_events)} - {'Starting from event ' + str(start_event) if start_event > 0 else 'Initial Template'}")
    print(f"=" * 80)
    print(current_content)
    print(f"\n{'=' * 80}")
    print("Controls: SPACE or 'k' to pause/play, Ctrl+C to stop")
    time.sleep(2.0 / speed)

    # Start keyboard listener in background thread
    listener = KeyboardListener()
    listener.start()

    try:
        for idx, event in enumerate(doc_events, 1):
            # Skip events before start_event
            if idx <= start_event:
                continue

            old_frag = event.get("oldFragment", "")
            new_frag = event.get("newFragment", "")
            offset = event.get("offset", 0)
            timestamp = event.get("timestamp")

            # Skip no-op events (file-open markers) - they don't show meaningful changes
            if old_frag == new_frag:
                last_timestamp = timestamp
                continue

            # Calculate delay based on timestamp difference
            if last_timestamp and timestamp:
                try:
                    ts1 = parse_timestamp(last_timestamp)
                    ts2 = parse_timestamp(timestamp)
                    delay = (ts2 - ts1).total_seconds() / speed
                    # Cap delay at 5 seconds for very long pauses
                    delay = min(delay, 5.0)
                    if delay > 0:
                        # Sleep in small chunks to check for pause state
                        elapsed = 0
                        chunk_size = 0.1
                        while elapsed < delay:
                            if listener.is_paused():
                                # Wait while paused
                                print("\r[PAUSED] Press SPACE or 'k' to resume, Ctrl+C to stop", end='', flush=True)
                                while listener.is_paused():
                                    time.sleep(0.1)
                                print("\r" + " " * 60 + "\r", end='', flush=True)  # Clear pause message
                            else:
                                time.sleep(min(chunk_size, delay - elapsed))
                                elapsed += chunk_size
                except (ValueError, KeyError):
                    time.sleep(0.1 / speed)
            else:
                time.sleep(0.1 / speed)

            last_timestamp = timestamp

            # Apply the edit
            if new_frag != old_frag:
                current_content = current_content[:offset] + new_frag + current_content[offset + len(old_frag):]

            # Display current state
            clear_screen()
            print(f"=" * 80)
            print(f"PLAYBACK: {document} (Speed: {speed}x)")
            print(f"Event {idx} / {len(doc_events)} - {timestamp or 'unknown time'}")

            # Show what changed
            if new_frag != old_frag:
                change_type = "INSERT" if not old_frag else ("DELETE" if not new_frag else "REPLACE")
                print(f"Action: {change_type} at offset {offset} ({len(new_frag)} chars)")

            print(f"=" * 80)
            print(current_content)
            print(f"\n{'=' * 80}")
            progress_pct = int(idx * 100 / len(doc_events))
            progress_bar = '#' * (idx * 40 // len(doc_events))
            print(f"Progress: [{progress_bar.ljust(40)}] {idx}/{len(doc_events)} ({progress_pct}%)")
            pause_status = "[PAUSED]" if listener.is_paused() else "[PLAYING]"
            print(f"Controls: SPACE or 'k' to pause/play, Ctrl+C to stop {pause_status}")

    except KeyboardInterrupt:
        print("\n\nPlayback stopped by user.", file=sys.stderr)
    finally:
        # Stop the keyboard listener
        listener.stop()

    # Final summary
    print("\n\nPlayback complete!", file=sys.stderr)
    print(f"Total events: {len(doc_events)}", file=sys.stderr)
