"""Agent management for Legionnaire."""

import contextlib
import os
import signal
import time
from enum import Enum
from pathlib import Path

PIDFILE_NAME = ".legion-agent.pid"


class AgentType(Enum):
    """Supported LLM agent types."""

    CLAUDE = "claude"
    CURSOR = "cursor"


class AgentStatus(Enum):
    """Status of an agent process."""

    RUNNING = "running"
    STOPPED = "stopped"
    UNKNOWN = "unknown"


def pidfile_path(child_worktree: Path) -> Path:
    """Get the path to the PID file for a child worktree.

    Args:
        child_worktree: The path to the child worktree.

    Returns:
        The path to the .legion-agent.pid file.
    """
    return child_worktree / PIDFILE_NAME


def read_pid(pidfile: Path) -> int | None:
    """Read the PID from a pidfile.

    Args:
        pidfile: The path to the PID file.

    Returns:
        The PID as an integer, or None if the file doesn't exist or is invalid.
    """
    if not pidfile.exists():
        return None

    try:
        content = pidfile.read_text().strip()
        return int(content)
    except (ValueError, OSError):
        return None


def pid_alive(pid: int) -> bool:
    """Check if a process with the given PID is alive.

    Args:
        pid: The process ID to check.

    Returns:
        True if the process is alive, False otherwise.
    """
    try:
        os.kill(pid, 0)
        return True
    except (ProcessLookupError, PermissionError):
        return False


def get_agent_status(child_worktree: Path) -> tuple[AgentStatus, int | None]:
    """Get the status of the agent for a child worktree.

    Args:
        child_worktree: The path to the child worktree.

    Returns:
        A tuple of (AgentStatus, PID or None).
    """
    pid = read_pid(pidfile_path(child_worktree))
    if pid is None:
        return AgentStatus.UNKNOWN, None

    if pid_alive(pid):
        return AgentStatus.RUNNING, pid

    return AgentStatus.STOPPED, pid


def launch_command(agent: AgentType, parent_id: str, child_id: str, worktree_path: Path) -> str:
    """Generate the shell command to launch an agent.

    The command:
    1. Changes to the child worktree directory
    2. Exports LEGION_AGENT_NAME environment variable
    3. Writes the shell's PID to .legion-agent.pid
    4. exec's the agent CLI so the PID remains correct

    Args:
        agent: The type of agent to launch.
        parent_id: The parent ID.
        child_id: The child ID.
        worktree_path: The path to the child worktree.

    Returns:
        The bash command string to launch the agent.
    """
    agent_name = f"{parent_id}/{child_id}"

    if agent == AgentType.CLAUDE:
        agent_cmd = "claude"
    elif agent == AgentType.CURSOR:
        agent_cmd = "cursor agent"
    else:
        raise ValueError(f"Unknown agent type: {agent}")

    return (
        f"cd {worktree_path} && "
        f'bash -lc \'export LEGION_AGENT_NAME="{agent_name}"; '
        f"echo $$ > {PIDFILE_NAME}; exec {agent_cmd}'"
    )


def kill_agent_if_running(child_worktree: Path, timeout: float = 5.0) -> bool:
    """Kill the agent process if it's running.

    Attempts SIGTERM first, then SIGKILL if the process doesn't terminate.

    Args:
        child_worktree: The path to the child worktree.
        timeout: Seconds to wait for graceful termination before SIGKILL.

    Returns:
        True if a process was killed, False if no process was running.
    """
    pid = read_pid(pidfile_path(child_worktree))
    if pid is None:
        return False

    if not pid_alive(pid):
        # Process already dead, clean up PID file
        pf = pidfile_path(child_worktree)
        if pf.exists():
            pf.unlink()
        return False

    # Try graceful termination first
    try:
        os.kill(pid, signal.SIGTERM)
    except (ProcessLookupError, PermissionError):
        return False

    # Wait for process to terminate
    start = time.time()
    while time.time() - start < timeout:
        if not pid_alive(pid):
            break
        time.sleep(0.1)

    # Force kill if still running
    if pid_alive(pid):
        with contextlib.suppress(ProcessLookupError, PermissionError):
            os.kill(pid, signal.SIGKILL)

    # Clean up PID file
    pf = pidfile_path(child_worktree)
    if pf.exists():
        pf.unlink()

    return True


def delete_pidfile(child_worktree: Path) -> None:
    """Delete the PID file for a child worktree.

    Args:
        child_worktree: The path to the child worktree.
    """
    pf = pidfile_path(child_worktree)
    if pf.exists():
        pf.unlink()
