"""
REPL Session Manager - Persistent sessions with auto-save

Extends SessionManager for REPL-specific features:
- Auto-save at configurable intervals
- Track file changes and command executions
- Session restore on startup
- Conversation turn tracking

Examples:
    manager = REPLSessionManager(project_path=Path.cwd())

    # Start session
    manager.start_session("my-session", agent="build")

    # Add conversation turns
    manager.add_turn(
        user_input="fix the bug",
        response="Done! I fixed...",
        tool_calls=[{"tool": "edit", "file": "main.py"}],
    )

    # Auto-save handles persistence
    # Or manually: manager.save()
"""

import threading
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Callable, Optional

from groknroll.tui.session import (
    Session,
    SessionManager,
)


@dataclass
class ConversationTurn:
    """
    A single conversation turn

    Attributes:
        user_input: User's input text
        response: Assistant's response
        tool_calls: List of tool calls made
        timestamp: When the turn occurred
        cost: Cost of the turn
        tokens: Tokens used
    """

    user_input: str
    response: str
    tool_calls: list[dict[str, Any]] = field(default_factory=list)
    timestamp: datetime = field(default_factory=datetime.now)
    cost: float = 0.0
    tokens: int = 0

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "user_input": self.user_input,
            "response": self.response,
            "tool_calls": self.tool_calls,
            "timestamp": self.timestamp.isoformat(),
            "cost": self.cost,
            "tokens": self.tokens,
        }


class REPLSessionManager:
    """
    Session manager with REPL-specific features

    Extends the base SessionManager with auto-save, turn tracking,
    and REPL lifecycle management.

    Example:
        manager = REPLSessionManager(
            project_path=Path.cwd(),
            auto_save=True,
            auto_save_interval=60,
        )

        manager.start_session("debug-session", agent="build")

        manager.add_turn(
            user_input="What's in main.py?",
            response="The file contains...",
        )

        # Session auto-saves periodically
        # Manual save: manager.save()
    """

    def __init__(
        self,
        project_path: Optional[Path] = None,
        auto_save: bool = True,
        auto_save_interval: int = 60,
        on_auto_save: Optional[Callable[[Session], None]] = None,
    ):
        """
        Initialize REPL session manager

        Args:
            project_path: Project root path
            auto_save: Enable auto-save (default: True)
            auto_save_interval: Seconds between auto-saves (default: 60)
            on_auto_save: Callback when auto-save occurs
        """
        self.project_path = project_path or Path.cwd()
        self.auto_save = auto_save
        self.auto_save_interval = auto_save_interval
        self.on_auto_save = on_auto_save

        # Initialize base session manager
        self.manager = SessionManager(project_name=self.project_path.name)

        # Current session
        self.current_session: Optional[Session] = None
        self.turns: list[ConversationTurn] = []

        # Auto-save thread
        self._auto_save_thread: Optional[threading.Thread] = None
        self._stop_auto_save = threading.Event()
        self._last_save_time: Optional[datetime] = None
        self._dirty = False  # Track if there are unsaved changes

    def start_session(
        self,
        name: Optional[str] = None,
        agent: str = "build",
        **metadata: Any,
    ) -> Session:
        """
        Start a new session

        Args:
            name: Session name (defaults to timestamp)
            agent: Agent name
            **metadata: Additional metadata

        Returns:
            New Session object
        """
        # Stop existing auto-save
        self._stop_auto_save_thread()

        # Create new session
        self.current_session = self.manager.create_session(
            name=name,
            agent=agent,
            **metadata,
        )
        self.turns.clear()
        self._dirty = False
        self._last_save_time = datetime.now()

        # Start auto-save if enabled
        if self.auto_save:
            self._start_auto_save_thread()

        return self.current_session

    def add_turn(
        self,
        user_input: str,
        response: str,
        tool_calls: Optional[list[dict]] = None,
        cost: float = 0.0,
        tokens: int = 0,
    ) -> ConversationTurn:
        """
        Add a conversation turn

        Args:
            user_input: User's input
            response: Assistant's response
            tool_calls: List of tool calls made
            cost: Cost of the turn
            tokens: Tokens used

        Returns:
            The created ConversationTurn
        """
        if self.current_session is None:
            # Auto-create session if needed
            self.start_session()

        # Create turn record
        turn = ConversationTurn(
            user_input=user_input,
            response=response,
            tool_calls=tool_calls or [],
            cost=cost,
            tokens=tokens,
        )
        self.turns.append(turn)

        # Add messages to session
        self.current_session.add_message("user", user_input)
        self.current_session.add_message("assistant", response)

        # Track tool calls
        if tool_calls:
            for call in tool_calls:
                tool_name = call.get("tool", "unknown")
                # Track file changes from edit/write tools
                if tool_name in ("edit", "write"):
                    path = call.get("path", call.get("file", ""))
                    action = "edit" if tool_name == "edit" else "create"
                    self.current_session.add_file_change(path, action)

                # Track command runs from bash tool
                if tool_name == "bash":
                    command = call.get("command", "")
                    exit_code = call.get("exit_code", 0)
                    self.current_session.add_command(command, exit_code)

        self._dirty = True
        self._maybe_auto_save()

        return turn

    def add_file_change(
        self,
        path: str,
        action: str,
        diff: Optional[str] = None,
    ) -> None:
        """
        Record a file change

        Args:
            path: File path
            action: Action (create, edit, delete)
            diff: Optional diff content
        """
        if self.current_session is None:
            return

        self.current_session.add_file_change(path, action, diff)
        self._dirty = True

    def add_command(
        self,
        command: str,
        exit_code: int,
        output: Optional[str] = None,
    ) -> None:
        """
        Record a command execution

        Args:
            command: Command string
            exit_code: Exit code
            output: Optional output
        """
        if self.current_session is None:
            return

        self.current_session.add_command(command, exit_code, output)
        self._dirty = True

    def save(self, name: Optional[str] = None) -> Optional[Path]:
        """
        Save current session

        Args:
            name: Optional new name for session

        Returns:
            Path to saved session file
        """
        if self.current_session is None:
            return None

        if name:
            self.current_session.name = name

        path = self.manager.save_session(self.current_session)
        self._dirty = False
        self._last_save_time = datetime.now()

        return path

    def load(self, name: str) -> Optional[Session]:
        """
        Load a session

        Args:
            name: Session name

        Returns:
            Session object or None
        """
        session = self.manager.load_session(name)
        if session:
            self.current_session = session
            self.turns.clear()
            self._dirty = False

            # Reconstruct turns from messages
            messages = session.messages
            for i in range(0, len(messages) - 1, 2):
                if i + 1 < len(messages):
                    user_msg = messages[i]
                    assistant_msg = messages[i + 1]
                    if user_msg.role == "user" and assistant_msg.role == "assistant":
                        turn = ConversationTurn(
                            user_input=user_msg.content,
                            response=assistant_msg.content,
                            timestamp=user_msg.timestamp,
                        )
                        self.turns.append(turn)

        return session

    def list_sessions(self, limit: Optional[int] = None) -> list[dict]:
        """
        List available sessions

        Args:
            limit: Maximum sessions to return

        Returns:
            List of session info dicts
        """
        sessions = self.manager.list_sessions(limit=limit)
        return [
            {
                "name": s.name,
                "agent": s.agent,
                "created": s.created.isoformat(),
                "updated": s.updated.isoformat(),
                "messages": s.message_count,
                "files": s.file_change_count,
                "commands": s.command_count,
            }
            for s in sessions
        ]

    def delete_session(self, name: str) -> bool:
        """
        Delete a session

        Args:
            name: Session name

        Returns:
            True if deleted
        """
        return self.manager.delete_session(name)

    def export_session(
        self,
        name: Optional[str] = None,
        format: str = "markdown",
    ) -> Optional[str]:
        """
        Export session to a format

        Args:
            name: Session name (current if not specified)
            format: Export format (markdown or json)

        Returns:
            Exported content
        """
        session_name = name
        if session_name is None and self.current_session:
            session_name = self.current_session.name

        if session_name is None:
            return None

        return self.manager.export_session(session_name, format=format)

    def get_current_session(self) -> Optional[Session]:
        """Get current session"""
        return self.current_session

    def get_turns(self, limit: Optional[int] = None) -> list[ConversationTurn]:
        """
        Get conversation turns

        Args:
            limit: Maximum turns to return

        Returns:
            List of turns
        """
        if limit:
            return self.turns[-limit:]
        return list(self.turns)

    def get_turn_count(self) -> int:
        """Get number of turns"""
        return len(self.turns)

    def has_unsaved_changes(self) -> bool:
        """Check if there are unsaved changes"""
        return self._dirty

    def switch_agent(self, agent: str) -> None:
        """
        Switch to a different agent

        Args:
            agent: Agent name
        """
        if self.current_session:
            self.current_session.agent = agent
            self._dirty = True

    def _maybe_auto_save(self) -> None:
        """Check if auto-save is needed"""
        if not self.auto_save or not self._dirty:
            return

        if self._last_save_time is None:
            return

        elapsed = (datetime.now() - self._last_save_time).total_seconds()
        if elapsed >= self.auto_save_interval:
            self._do_auto_save()

    def _do_auto_save(self) -> None:
        """Perform auto-save"""
        if self.current_session is None or not self._dirty:
            return

        path = self.save()

        if self.on_auto_save and path:
            self.on_auto_save(self.current_session)

    def _start_auto_save_thread(self) -> None:
        """Start auto-save background thread"""
        self._stop_auto_save.clear()
        self._auto_save_thread = threading.Thread(
            target=self._auto_save_loop,
            daemon=True,
        )
        self._auto_save_thread.start()

    def _stop_auto_save_thread(self) -> None:
        """Stop auto-save background thread"""
        if self._auto_save_thread is not None:
            self._stop_auto_save.set()
            self._auto_save_thread.join(timeout=1.0)
            self._auto_save_thread = None

    def _auto_save_loop(self) -> None:
        """Background auto-save loop"""
        while not self._stop_auto_save.is_set():
            self._stop_auto_save.wait(timeout=self.auto_save_interval)
            if not self._stop_auto_save.is_set():
                self._do_auto_save()

    def close(self) -> None:
        """
        Close the session manager

        Saves any pending changes and stops auto-save.
        """
        self._stop_auto_save_thread()

        # Final save if dirty
        if self._dirty and self.current_session:
            self.save()

    def __enter__(self) -> "REPLSessionManager":
        """Context manager entry"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        """Context manager exit"""
        self.close()


def create_session_manager(
    project_path: Optional[Path] = None,
    auto_save: bool = True,
) -> REPLSessionManager:
    """
    Create a REPL session manager (convenience function)

    Args:
        project_path: Project path
        auto_save: Enable auto-save

    Returns:
        Configured REPLSessionManager
    """
    return REPLSessionManager(project_path=project_path, auto_save=auto_save)
