"""
Session Management - Save and restore conversation sessions

Provides session persistence for the CLI:
- Save sessions to ~/.groknroll/sessions/<project>/<name>.json
- Include messages, files changed, commands run
- List and restore previous sessions
- Auto-save support

Examples:
    # Create session manager
    manager = SessionManager(project_name="my-project")

    # Save current session
    session = manager.create_session(agent="build")
    session.add_message("user", "Hello!")
    session.add_message("assistant", "Hi there!")
    manager.save_session(session)

    # List sessions
    sessions = manager.list_sessions()
    for s in sessions:
        print(f"{s.name}: {s.created}")

    # Load session
    session = manager.load_session("session_name")
"""

import json
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Optional
from uuid import uuid4


@dataclass
class Message:
    """A conversation message"""

    role: str  # "user", "assistant", "system"
    content: str
    timestamp: datetime = field(default_factory=datetime.now)
    metadata: dict[str, Any] = field(default_factory=dict)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "role": self.role,
            "content": self.content,
            "timestamp": self.timestamp.isoformat(),
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Message":
        """Create from dictionary"""
        return cls(
            role=data["role"],
            content=data["content"],
            timestamp=datetime.fromisoformat(data["timestamp"]),
            metadata=data.get("metadata", {}),
        )


@dataclass
class FileChange:
    """Record of a file change"""

    path: str
    action: str  # "create", "edit", "delete"
    timestamp: datetime = field(default_factory=datetime.now)
    diff: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "path": self.path,
            "action": self.action,
            "timestamp": self.timestamp.isoformat(),
            "diff": self.diff,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "FileChange":
        """Create from dictionary"""
        return cls(
            path=data["path"],
            action=data["action"],
            timestamp=datetime.fromisoformat(data["timestamp"]),
            diff=data.get("diff"),
        )


@dataclass
class CommandRun:
    """Record of a command execution"""

    command: str
    exit_code: int
    timestamp: datetime = field(default_factory=datetime.now)
    output: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "command": self.command,
            "exit_code": self.exit_code,
            "timestamp": self.timestamp.isoformat(),
            "output": self.output,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "CommandRun":
        """Create from dictionary"""
        return cls(
            command=data["command"],
            exit_code=data["exit_code"],
            timestamp=datetime.fromisoformat(data["timestamp"]),
            output=data.get("output"),
        )


@dataclass
class Session:
    """A conversation session"""

    id: str
    name: str
    project: str
    agent: str
    created: datetime
    updated: datetime
    messages: list[Message] = field(default_factory=list)
    file_changes: list[FileChange] = field(default_factory=list)
    commands: list[CommandRun] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)

    def add_message(self, role: str, content: str, **metadata: Any) -> Message:
        """
        Add a message to the session

        Args:
            role: Message role (user, assistant, system)
            content: Message content
            **metadata: Additional metadata

        Returns:
            The created Message
        """
        message = Message(role=role, content=content, metadata=metadata)
        self.messages.append(message)
        self.updated = datetime.now()
        return message

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

        Args:
            path: File path
            action: Action (create, edit, delete)
            diff: Optional diff content

        Returns:
            The created FileChange
        """
        change = FileChange(path=path, action=action, diff=diff)
        self.file_changes.append(change)
        self.updated = datetime.now()
        return change

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

        Args:
            command: Command string
            exit_code: Exit code
            output: Optional output

        Returns:
            The created CommandRun
        """
        cmd = CommandRun(command=command, exit_code=exit_code, output=output)
        self.commands.append(cmd)
        self.updated = datetime.now()
        return cmd

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary"""
        return {
            "id": self.id,
            "name": self.name,
            "project": self.project,
            "agent": self.agent,
            "created": self.created.isoformat(),
            "updated": self.updated.isoformat(),
            "messages": [m.to_dict() for m in self.messages],
            "file_changes": [f.to_dict() for f in self.file_changes],
            "commands": [c.to_dict() for c in self.commands],
            "metadata": self.metadata,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Session":
        """Create from dictionary"""
        return cls(
            id=data["id"],
            name=data["name"],
            project=data["project"],
            agent=data["agent"],
            created=datetime.fromisoformat(data["created"]),
            updated=datetime.fromisoformat(data["updated"]),
            messages=[Message.from_dict(m) for m in data.get("messages", [])],
            file_changes=[FileChange.from_dict(f) for f in data.get("file_changes", [])],
            commands=[CommandRun.from_dict(c) for c in data.get("commands", [])],
            metadata=data.get("metadata", {}),
        )

    def get_summary(self) -> str:
        """Get a brief summary of the session"""
        return (
            f"{self.name} ({self.agent}) - "
            f"{len(self.messages)} messages, "
            f"{len(self.file_changes)} file changes, "
            f"{len(self.commands)} commands"
        )


@dataclass
class SessionInfo:
    """Brief session info for listing"""

    name: str
    project: str
    agent: str
    created: datetime
    updated: datetime
    message_count: int
    file_change_count: int
    command_count: int
    path: Path


class SessionManager:
    """
    Manage conversation sessions

    Handles saving, loading, and listing sessions with support
    for project-specific session directories.

    Example:
        manager = SessionManager("my-project")

        # Create and save session
        session = manager.create_session(agent="build")
        session.add_message("user", "Hello")
        manager.save_session(session)

        # List sessions
        for info in manager.list_sessions():
            print(info.name)

        # Load session
        session = manager.load_session("session_name")
    """

    DEFAULT_BASE_DIR = Path.home() / ".groknroll" / "sessions"

    def __init__(
        self,
        project_name: Optional[str] = None,
        base_dir: Optional[Path] = None,
    ):
        """
        Initialize session manager

        Args:
            project_name: Project identifier (defaults to "default")
            base_dir: Base directory for sessions
        """
        self.project_name = project_name or "default"
        self.base_dir = base_dir or self.DEFAULT_BASE_DIR
        self.project_dir = self.base_dir / self._sanitize_name(self.project_name)

    def _sanitize_name(self, name: str) -> str:
        """Sanitize name for use as filename"""
        # Replace problematic characters
        sanitized = "".join(c if c.isalnum() or c in "-_." else "_" for c in name)
        return sanitized or "unnamed"

    def _ensure_project_dir(self) -> None:
        """Ensure project directory exists"""
        self.project_dir.mkdir(parents=True, exist_ok=True)

    def _get_session_path(self, name: str) -> Path:
        """Get path for a session file"""
        return self.project_dir / f"{self._sanitize_name(name)}.json"

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

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

        Returns:
            New Session object
        """
        now = datetime.now()
        session_name = name or now.strftime("%Y-%m-%d_%H-%M-%S")

        return Session(
            id=str(uuid4()),
            name=session_name,
            project=self.project_name,
            agent=agent,
            created=now,
            updated=now,
            metadata=metadata,
        )

    def save_session(self, session: Session) -> Path:
        """
        Save session to disk

        Args:
            session: Session to save

        Returns:
            Path to saved session file
        """
        self._ensure_project_dir()
        session.updated = datetime.now()

        path = self._get_session_path(session.name)
        with open(path, "w", encoding="utf-8") as f:
            json.dump(session.to_dict(), f, indent=2, ensure_ascii=False)

        return path

    def load_session(self, name: str) -> Optional[Session]:
        """
        Load session from disk

        Args:
            name: Session name

        Returns:
            Session object or None if not found
        """
        path = self._get_session_path(name)

        if not path.exists():
            return None

        try:
            with open(path, encoding="utf-8") as f:
                data = json.load(f)
            return Session.from_dict(data)
        except (json.JSONDecodeError, KeyError):
            return None

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

        Args:
            name: Session name

        Returns:
            True if deleted
        """
        path = self._get_session_path(name)

        if path.exists():
            path.unlink()
            return True
        return False

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

        Args:
            limit: Maximum sessions to return (newest first)

        Returns:
            List of SessionInfo objects
        """
        if not self.project_dir.exists():
            return []

        sessions = []
        for path in self.project_dir.glob("*.json"):
            try:
                with open(path, encoding="utf-8") as f:
                    data = json.load(f)

                info = SessionInfo(
                    name=data["name"],
                    project=data["project"],
                    agent=data["agent"],
                    created=datetime.fromisoformat(data["created"]),
                    updated=datetime.fromisoformat(data["updated"]),
                    message_count=len(data.get("messages", [])),
                    file_change_count=len(data.get("file_changes", [])),
                    command_count=len(data.get("commands", [])),
                    path=path,
                )
                sessions.append(info)
            except (json.JSONDecodeError, KeyError):
                continue

        # Sort by updated time, newest first
        sessions.sort(key=lambda s: s.updated, reverse=True)

        if limit:
            return sessions[:limit]
        return sessions

    def session_exists(self, name: str) -> bool:
        """Check if session exists"""
        return self._get_session_path(name).exists()

    def get_session_count(self) -> int:
        """Get number of sessions for this project"""
        if not self.project_dir.exists():
            return 0
        return len(list(self.project_dir.glob("*.json")))

    def rename_session(self, old_name: str, new_name: str) -> bool:
        """
        Rename a session

        Args:
            old_name: Current name
            new_name: New name

        Returns:
            True if renamed
        """
        session = self.load_session(old_name)
        if not session:
            return False

        session.name = new_name
        self.save_session(session)
        self.delete_session(old_name)
        return True

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

        Args:
            name: Session name
            format: Export format ("markdown" or "json")

        Returns:
            Exported content or None
        """
        session = self.load_session(name)
        if not session:
            return None

        if format == "json":
            return json.dumps(session.to_dict(), indent=2, ensure_ascii=False)
        elif format == "markdown":
            return self._export_markdown(session)
        else:
            return None

    def _export_markdown(self, session: Session) -> str:
        """Export session to Markdown format"""
        lines = [
            f"# Session: {session.name}",
            "",
            f"**Project**: {session.project}",
            f"**Agent**: {session.agent}",
            f"**Created**: {session.created.isoformat()}",
            f"**Updated**: {session.updated.isoformat()}",
            "",
            "## Messages",
            "",
        ]

        for msg in session.messages:
            role = msg.role.capitalize()
            lines.append(f"### {role}")
            lines.append(f"_{msg.timestamp.strftime('%Y-%m-%d %H:%M:%S')}_")
            lines.append("")
            lines.append(msg.content)
            lines.append("")

        if session.file_changes:
            lines.extend(["## File Changes", ""])
            for change in session.file_changes:
                lines.append(f"- **{change.action}**: `{change.path}`")
            lines.append("")

        if session.commands:
            lines.extend(["## Commands Run", ""])
            for cmd in session.commands:
                status = "✓" if cmd.exit_code == 0 else "✗"
                lines.append(f"- {status} `{cmd.command}` (exit: {cmd.exit_code})")
            lines.append("")

        return "\n".join(lines)


# Convenience functions


def save_session(
    session: Session,
    project_name: Optional[str] = None,
) -> Path:
    """
    Save a session (convenience function)

    Args:
        session: Session to save
        project_name: Project name

    Returns:
        Path to saved file
    """
    manager = SessionManager(project_name=project_name or session.project)
    return manager.save_session(session)


def load_session(
    name: str,
    project_name: str = "default",
) -> Optional[Session]:
    """
    Load a session (convenience function)

    Args:
        name: Session name
        project_name: Project name

    Returns:
        Session or None
    """
    manager = SessionManager(project_name=project_name)
    return manager.load_session(name)


def list_sessions(
    project_name: str = "default",
    limit: Optional[int] = None,
) -> list[SessionInfo]:
    """
    List sessions (convenience function)

    Args:
        project_name: Project name
        limit: Maximum to return

    Returns:
        List of SessionInfo
    """
    manager = SessionManager(project_name=project_name)
    return manager.list_sessions(limit=limit)
