"""
DIY Agent Workspace Manager - AGPL-free multi-agent workspace isolation.

This module provides workspace isolation for multiple agents using:
- Git worktrees: Separate working directories per agent
- tmux sessions: Process isolation and terminal management

This is an AGPL-free alternative to Claude Squad that achieves the same
pattern without license encumbrance.

Usage:
    from agent_orchestrator.workspace import DIYAgentWorkspace

    workspace = DIYAgentWorkspace(Path("/path/to/main/repo"))

    # Create isolated workspace for an agent
    path = workspace.create_workspace("claude-agent", "feature/auth")

    # Run commands in the workspace
    workspace.run_in_workspace("claude-agent", "claude")

    # Switch to view the agent
    workspace.switch_to("claude-agent")

    # Cleanup when done
    workspace.cleanup("claude-agent")
"""

import asyncio
import shutil
import subprocess
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Optional

from ..persistence.models import Agent, AgentTool


@dataclass
class WorkspaceInfo:
    """Information about an agent's workspace."""

    agent_id: str
    workspace_path: Path
    branch: str
    tmux_session: str
    created_at: datetime = field(default_factory=datetime.now)
    tool: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "agent_id": self.agent_id,
            "workspace_path": str(self.workspace_path),
            "branch": self.branch,
            "tmux_session": self.tmux_session,
            "created_at": self.created_at.isoformat(),
            "tool": self.tool,
        }


class DIYAgentWorkspace:
    """
    AGPL-free multi-agent workspace management.

    Uses git worktrees for repository isolation and tmux sessions
    for process isolation, providing the same functionality as
    Claude Squad without the AGPL license concerns.

    Each agent gets:
    - Its own git worktree (separate working directory)
    - Its own branch
    - Its own tmux session for CLI interaction
    """

    def __init__(self, project_root: Path) -> None:
        """
        Initialize the workspace manager.

        Args:
            project_root: Path to the main git repository
        """
        self.project_root = project_root
        self.workspaces: dict[str, WorkspaceInfo] = {}

        # Verify dependencies
        self._git_available = shutil.which("git") is not None
        self._tmux_available = shutil.which("tmux") is not None

    def check_dependencies(self) -> tuple[bool, list[str]]:
        """
        Check if required dependencies are available.

        Returns:
            Tuple of (all_available, list_of_missing)
        """
        missing = []
        if not self._git_available:
            missing.append("git")
        if not self._tmux_available:
            missing.append("tmux")

        return len(missing) == 0, missing

    def create_workspace(
        self,
        agent_id: str,
        branch: str,
        tool: Optional[str] = None,
        base_branch: str = "main",
    ) -> Path:
        """
        Create an isolated workspace for an agent.

        Creates:
        1. A new branch (if it doesn't exist)
        2. A git worktree in a sibling directory
        3. A tmux session for the agent

        Args:
            agent_id: Unique identifier for the agent
            branch: Branch name for this agent's work
            tool: The CLI tool this agent uses (claude, gemini, codex)
            base_branch: Branch to base the new branch on

        Returns:
            Path to the created workspace

        Raises:
            RuntimeError: If dependencies are missing or operations fail
        """
        ok, missing = self.check_dependencies()
        if not ok:
            raise RuntimeError(f"Missing dependencies: {', '.join(missing)}")

        # Workspace path is a sibling directory
        workspace_path = self.project_root.parent / f"agent-{agent_id}"

        # Check if worktree already exists
        if workspace_path.exists():
            # Reuse existing workspace
            if agent_id in self.workspaces:
                return self.workspaces[agent_id].workspace_path

            # Register existing workspace
            self.workspaces[agent_id] = WorkspaceInfo(
                agent_id=agent_id,
                workspace_path=workspace_path,
                branch=branch,
                tmux_session=agent_id,
                tool=tool,
            )
            return workspace_path

        try:
            # Create branch if needed
            self._ensure_branch(branch, base_branch)

            # Create git worktree
            subprocess.run(
                ["git", "worktree", "add", str(workspace_path), branch],
                cwd=self.project_root,
                check=True,
                capture_output=True,
            )

            # Create tmux session
            self._create_tmux_session(agent_id, workspace_path)

            # Register workspace
            self.workspaces[agent_id] = WorkspaceInfo(
                agent_id=agent_id,
                workspace_path=workspace_path,
                branch=branch,
                tmux_session=agent_id,
                tool=tool,
            )

            return workspace_path

        except subprocess.CalledProcessError as e:
            error = e.stderr.decode() if e.stderr else str(e)
            raise RuntimeError(f"Failed to create workspace: {error}")

    def _ensure_branch(self, branch: str, base_branch: str) -> None:
        """Ensure the branch exists, creating it if needed."""
        # Check if branch exists
        result = subprocess.run(
            ["git", "branch", "--list", branch],
            cwd=self.project_root,
            capture_output=True,
        )

        if not result.stdout.strip():
            # Branch doesn't exist, create it
            subprocess.run(
                ["git", "branch", branch, base_branch],
                cwd=self.project_root,
                check=True,
                capture_output=True,
            )

    def _create_tmux_session(self, session_name: str, working_dir: Path) -> None:
        """Create a tmux session for the agent."""
        # Check if session already exists
        result = subprocess.run(
            ["tmux", "has-session", "-t", session_name],
            capture_output=True,
        )

        if result.returncode != 0:
            # Session doesn't exist, create it
            subprocess.run(
                [
                    "tmux", "new-session",
                    "-d",  # Detached
                    "-s", session_name,  # Session name
                    "-c", str(working_dir),  # Working directory
                ],
                check=True,
                capture_output=True,
            )

    def run_in_workspace(self, agent_id: str, command: str) -> None:
        """
        Run a command in an agent's tmux session.

        Args:
            agent_id: The agent's identifier
            command: Command to run (e.g., "claude", "gemini", "pytest")
        """
        if agent_id not in self.workspaces:
            raise ValueError(f"No workspace found for agent: {agent_id}")

        subprocess.run(
            ["tmux", "send-keys", "-t", agent_id, command, "Enter"],
            check=True,
            capture_output=True,
        )

    def send_input(self, agent_id: str, text: str, press_enter: bool = True) -> None:
        """
        Send input text to an agent's tmux session.

        Args:
            agent_id: The agent's identifier
            text: Text to send
            press_enter: Whether to press Enter after the text
        """
        if agent_id not in self.workspaces:
            raise ValueError(f"No workspace found for agent: {agent_id}")

        cmd = ["tmux", "send-keys", "-t", agent_id, text]
        if press_enter:
            cmd.append("Enter")

        subprocess.run(cmd, check=True, capture_output=True)

    def switch_to(self, agent_id: str) -> None:
        """
        Switch to an agent's tmux session.

        This attaches the current terminal to the agent's session,
        allowing direct interaction.

        Args:
            agent_id: The agent's identifier
        """
        if agent_id not in self.workspaces:
            raise ValueError(f"No workspace found for agent: {agent_id}")

        # This will take over the terminal
        subprocess.run(
            ["tmux", "switch-client", "-t", agent_id],
        )

    def capture_output(self, agent_id: str, lines: int = 100) -> str:
        """
        Capture recent output from an agent's tmux session.

        Args:
            agent_id: The agent's identifier
            lines: Number of lines to capture

        Returns:
            The captured output as a string
        """
        if agent_id not in self.workspaces:
            raise ValueError(f"No workspace found for agent: {agent_id}")

        result = subprocess.run(
            ["tmux", "capture-pane", "-t", agent_id, "-p", "-S", f"-{lines}"],
            capture_output=True,
        )

        return result.stdout.decode("utf-8")

    def get_git_diff(self, agent_id: str) -> str:
        """
        Get the git diff for an agent's workspace.

        Args:
            agent_id: The agent's identifier

        Returns:
            The git diff output
        """
        if agent_id not in self.workspaces:
            raise ValueError(f"No workspace found for agent: {agent_id}")

        workspace = self.workspaces[agent_id]
        result = subprocess.run(
            ["git", "diff", "--stat"],
            cwd=workspace.workspace_path,
            capture_output=True,
        )

        return result.stdout.decode("utf-8")

    def commit_changes(
        self,
        agent_id: str,
        message: str,
        add_all: bool = True,
    ) -> Optional[str]:
        """
        Commit changes in an agent's workspace.

        Args:
            agent_id: The agent's identifier
            message: Commit message
            add_all: Whether to add all changes before committing

        Returns:
            The commit hash, or None if no changes
        """
        if agent_id not in self.workspaces:
            raise ValueError(f"No workspace found for agent: {agent_id}")

        workspace = self.workspaces[agent_id]

        if add_all:
            subprocess.run(
                ["git", "add", "-A"],
                cwd=workspace.workspace_path,
                capture_output=True,
            )

        # Check if there are changes to commit
        result = subprocess.run(
            ["git", "status", "--porcelain"],
            cwd=workspace.workspace_path,
            capture_output=True,
        )

        if not result.stdout.strip():
            return None  # No changes

        # Commit
        subprocess.run(
            ["git", "commit", "-m", message],
            cwd=workspace.workspace_path,
            check=True,
            capture_output=True,
        )

        # Get commit hash
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            cwd=workspace.workspace_path,
            capture_output=True,
        )

        return result.stdout.decode("utf-8").strip()

    def cleanup(self, agent_id: str, force: bool = False) -> None:
        """
        Clean up an agent's workspace.

        Removes:
        1. The tmux session
        2. The git worktree

        Args:
            agent_id: The agent's identifier
            force: Force removal even with uncommitted changes
        """
        if agent_id not in self.workspaces:
            return

        workspace = self.workspaces[agent_id]

        # Kill tmux session
        subprocess.run(
            ["tmux", "kill-session", "-t", agent_id],
            capture_output=True,
        )

        # Remove worktree
        cmd = ["git", "worktree", "remove", str(workspace.workspace_path)]
        if force:
            cmd.append("--force")

        subprocess.run(
            cmd,
            cwd=self.project_root,
            capture_output=True,
        )

        # Remove from registry
        del self.workspaces[agent_id]

    def cleanup_all(self, force: bool = False) -> None:
        """
        Clean up all agent workspaces.

        Args:
            force: Force removal even with uncommitted changes
        """
        agent_ids = list(self.workspaces.keys())
        for agent_id in agent_ids:
            self.cleanup(agent_id, force=force)

    def list_workspaces(self) -> list[WorkspaceInfo]:
        """
        List all active workspaces.

        Returns:
            List of WorkspaceInfo objects
        """
        return list(self.workspaces.values())

    def get_workspace(self, agent_id: str) -> Optional[WorkspaceInfo]:
        """
        Get information about a specific workspace.

        Args:
            agent_id: The agent's identifier

        Returns:
            WorkspaceInfo or None if not found
        """
        return self.workspaces.get(agent_id)

    def is_session_active(self, agent_id: str) -> bool:
        """
        Check if an agent's tmux session is active.

        Args:
            agent_id: The agent's identifier

        Returns:
            True if the session exists and is active
        """
        result = subprocess.run(
            ["tmux", "has-session", "-t", agent_id],
            capture_output=True,
        )
        return result.returncode == 0


async def setup_multi_agent_workspace(
    project_root: Path,
    agents: list[tuple[str, str, str]],
) -> DIYAgentWorkspace:
    """
    Set up workspaces for multiple agents.

    Args:
        project_root: Path to the main repository
        agents: List of (agent_id, branch, tool) tuples

    Returns:
        Configured DIYAgentWorkspace with all agents set up

    Example:
        workspace = await setup_multi_agent_workspace(
            Path("/path/to/repo"),
            [
                ("claude-main", "feature/auth-claude", "claude"),
                ("gemini-docs", "feature/docs-gemini", "gemini"),
                ("codex-tests", "feature/tests-codex", "codex"),
            ]
        )
    """
    manager = DIYAgentWorkspace(project_root)

    for agent_id, branch, tool in agents:
        manager.create_workspace(agent_id, branch, tool)

    return manager
