"""Process Action - Manage running exec sessions.

Supports: list, poll, log, write, send-keys, submit, paste, kill, clear, remove.
"""

from __future__ import annotations

import logging
import time
from dataclasses import dataclass

from elizaos_plugin_shell.process_registry import (
    FinishedSession,
    ProcessRegistry,
    ProcessSession,
    ProcessStatus,
)

logger = logging.getLogger(__name__)


@dataclass(slots=True)
class ProcessActionParams:
    """Parameters for a process management action."""

    action: str  # list, poll, log, write, send-keys, submit, paste, kill, clear, remove
    session_id: str | None = None
    data: str | None = None
    keys: list[str] | None = None
    literal: str | None = None
    text: str | None = None
    eof: bool = False
    offset: int = 0
    limit: int = 100


@dataclass(slots=True)
class ProcessActionResult:
    """Result of a process management action."""

    success: bool
    message: str
    data: dict[str, object] | None = None


PROCESS_KEYWORDS = frozenset({
    "process", "session", "sessions", "list", "poll", "log",
    "write", "send-keys", "send keys", "submit", "paste",
    "kill", "clear", "remove", "running", "background",
})

ACTION_KEYWORDS = frozenset({
    "check", "show", "get", "view", "manage", "stop", "terminate", "status",
})


def _format_duration(ms: float) -> str:
    """Format milliseconds as human-readable duration."""
    seconds = ms / 1000
    if seconds < 60:
        return f"{seconds:.0f}s"
    minutes = seconds / 60
    if minutes < 60:
        remaining = seconds % 60
        return f"{minutes:.0f}m{remaining:.0f}s"
    hours = minutes / 60
    remaining_min = minutes % 60
    return f"{hours:.0f}h{remaining_min:.0f}m"


def _format_session_line(session: ProcessSession) -> str:
    """Format a single session for display."""
    elapsed = time.time() * 1000 - session.started_at
    status = "running" if not session.exited else "exited"
    return f"{session.id} {status} {_format_duration(elapsed)} :: {session.command}"


def _format_finished_line(session: FinishedSession) -> str:
    """Format a finished session for display."""
    duration = session.ended_at - session.started_at
    return f"{session.id} {session.status.value} {_format_duration(duration)} :: {session.command}"


class ProcessAction:
    """Action for managing running shell/exec sessions."""

    name = "MANAGE_PROCESS"
    similes = [
        "PROCESS_LIST", "PROCESS_POLL", "PROCESS_LOG", "PROCESS_WRITE",
        "PROCESS_KILL", "LIST_SESSIONS", "POLL_SESSION", "KILL_SESSION",
        "CHECK_PROCESS", "SEND_KEYS",
    ]
    description = (
        "Manage running shell/exec sessions: list, poll, log, write, "
        "send-keys, submit, paste, kill, clear, remove"
    )

    def __init__(self, registry: ProcessRegistry) -> None:
        self._registry = registry

    def validate(self, message: dict[str, object]) -> bool:
        """Validate if this action should handle the message."""
        content = message.get("content")
        text = (content.get("text") if isinstance(content, dict) else "") or ""  # type: ignore[union-attr]
        text = str(text).lower()
        has_process_kw = any(kw in text for kw in PROCESS_KEYWORDS)
        has_action_kw = any(kw in text for kw in ACTION_KEYWORDS)
        return has_process_kw or (has_action_kw and "session" in text)

    async def handler(
        self,
        params: ProcessActionParams,
    ) -> ProcessActionResult:
        """Execute a process management action."""
        action = params.action.lower()

        if action == "list":
            return self._handle_list()
        if action == "poll":
            return self._handle_poll(params.session_id)
        if action == "log":
            return self._handle_log(params.session_id, params.offset, params.limit)
        if action == "write":
            return self._handle_write(params.session_id, params.data, params.eof)
        if action == "kill":
            return self._handle_kill(params.session_id)
        if action == "clear":
            return self._handle_clear()
        if action == "remove":
            return self._handle_remove(params.session_id)
        if action in ("send-keys", "submit", "paste"):
            return self._handle_input(params)
        return ProcessActionResult(
            success=False,
            message=f"Unknown process action: {action}",
        )

    def _handle_list(self) -> ProcessActionResult:
        """List all sessions."""
        result = self._registry.list_all()
        running = result["running"]
        finished = result["finished"]

        if not running and not finished:
            return ProcessActionResult(
                success=True,
                message="No active or recent sessions.",
                data={"running": [], "finished": []},
            )

        lines: list[str] = []
        if running:
            lines.append("Running sessions:")
            for s in running:
                lines.append(f"  {_format_session_line(s)}")
        if finished:
            lines.append("Finished sessions:")
            for s in finished:
                lines.append(f"  {_format_finished_line(s)}")

        return ProcessActionResult(
            success=True,
            message="\n".join(lines),
            data={
                "running": [{"id": s.id, "command": s.command} for s in running],
                "finished": [{"id": s.id, "command": s.command, "status": s.status.value} for s in finished],
            },
        )

    def _handle_poll(self, session_id: str | None) -> ProcessActionResult:
        """Poll session status."""
        if not session_id:
            return ProcessActionResult(
                success=False, message="Session ID required for poll action."
            )

        session = self._registry.get_process(session_id)
        if session:
            drained = self._registry.drain_session(session)
            elapsed = time.time() * 1000 - session.started_at

            lines = [f"Session {session.id} is {'running' if not session.exited else 'exited'}."]
            lines.append(f"Elapsed: {_format_duration(elapsed)}")
            if drained["stdout"]:
                lines.append(f"\nOutput:\n{drained['stdout']}")
            if drained["stderr"]:
                lines.append(f"\nErrors:\n{drained['stderr']}")
            if session.exited:
                lines.append(f"\nExit code: {session.exit_code}")

            return ProcessActionResult(
                success=True,
                message="\n".join(lines),
                data={
                    "id": session.id,
                    "exited": session.exited,
                    "exit_code": session.exit_code,
                    "stdout": drained["stdout"],
                    "stderr": drained["stderr"],
                },
            )

        finished = self._registry.get_finished(session_id)
        if finished:
            duration = finished.ended_at - finished.started_at
            return ProcessActionResult(
                success=True,
                message=(
                    f"Session {finished.id} finished ({finished.status.value}).\n"
                    f"Duration: {_format_duration(duration)}\n"
                    f"Exit code: {finished.exit_code}\n"
                    f"Output tail:\n{finished.tail}"
                ),
                data={
                    "id": finished.id,
                    "status": finished.status.value,
                    "exit_code": finished.exit_code,
                    "tail": finished.tail,
                },
            )

        return ProcessActionResult(
            success=False, message=f"Session '{session_id}' not found."
        )

    def _handle_log(
        self, session_id: str | None, offset: int = 0, limit: int = 100
    ) -> ProcessActionResult:
        """Get session log output."""
        if not session_id:
            return ProcessActionResult(
                success=False, message="Session ID required for log action."
            )

        session = self._registry.get_process(session_id)
        if session:
            output = session.aggregated
            lines = output.split("\n")
            sliced = lines[offset: offset + limit]
            return ProcessActionResult(
                success=True,
                message="\n".join(sliced),
                data={"total_lines": len(lines), "offset": offset, "limit": limit},
            )

        finished = self._registry.get_finished(session_id)
        if finished:
            output = finished.aggregated
            lines = output.split("\n")
            sliced = lines[offset: offset + limit]
            return ProcessActionResult(
                success=True,
                message="\n".join(sliced),
                data={"total_lines": len(lines), "offset": offset, "limit": limit},
            )

        return ProcessActionResult(
            success=False, message=f"Session '{session_id}' not found."
        )

    def _handle_write(
        self, session_id: str | None, data: str | None, eof: bool
    ) -> ProcessActionResult:
        """Write data to a session's stdin."""
        if not session_id:
            return ProcessActionResult(
                success=False, message="Session ID required for write action."
            )

        session = self._registry.get_process(session_id)
        if not session:
            return ProcessActionResult(
                success=False, message=f"Running session '{session_id}' not found."
            )

        if session.exited:
            return ProcessActionResult(
                success=False, message=f"Session '{session_id}' has already exited."
            )

        # In a full implementation, we would write to the process stdin.
        # Here we acknowledge the intent.
        msg = f"Write acknowledged for session {session_id}."
        if data:
            msg += f" Data length: {len(data)} chars."
        if eof:
            msg += " EOF will be sent."
        return ProcessActionResult(success=True, message=msg)

    def _handle_kill(self, session_id: str | None) -> ProcessActionResult:
        """Kill a running session."""
        if not session_id:
            return ProcessActionResult(
                success=False, message="Session ID required for kill action."
            )

        killed = self._registry.kill_process(session_id)
        if killed:
            return ProcessActionResult(
                success=True, message=f"Killed session {session_id}."
            )

        session = self._registry.get_finished(session_id)
        if session:
            return ProcessActionResult(
                success=False, message=f"Session '{session_id}' already finished."
            )

        return ProcessActionResult(
            success=False, message=f"Session '{session_id}' not found."
        )

    def _handle_clear(self) -> ProcessActionResult:
        """Clear all finished sessions."""
        self._registry.clear_finished()
        return ProcessActionResult(
            success=True, message="Cleared all finished sessions."
        )

    def _handle_remove(self, session_id: str | None) -> ProcessActionResult:
        """Remove a specific session."""
        if not session_id:
            return ProcessActionResult(
                success=False, message="Session ID required for remove action."
            )

        self._registry.delete_session(session_id)
        return ProcessActionResult(
            success=True, message=f"Removed session {session_id}."
        )

    def _handle_input(self, params: ProcessActionParams) -> ProcessActionResult:
        """Handle send-keys, submit, paste actions."""
        if not params.session_id:
            return ProcessActionResult(
                success=False, message="Session ID required."
            )

        session = self._registry.get_process(params.session_id)
        if not session:
            return ProcessActionResult(
                success=False,
                message=f"Running session '{params.session_id}' not found.",
            )
        if session.exited:
            return ProcessActionResult(
                success=False,
                message=f"Session '{params.session_id}' has already exited.",
            )

        action = params.action.lower()
        if action == "send-keys":
            msg = f"Send-keys to {params.session_id}: {params.keys or params.literal}"
        elif action == "submit":
            msg = f"Submit to {params.session_id}: {params.data}"
        else:  # paste
            msg = f"Paste to {params.session_id}: {len(params.text or '')} chars"

        return ProcessActionResult(success=True, message=msg)

    def examples(self) -> list[dict[str, str]]:
        """Return action examples."""
        return [
            {
                "user": "List all running processes",
                "agent": "Here are the running sessions:\ncalm-harbor running 5m30s :: npm install",
            },
            {
                "user": "Check the status of session calm-harbor",
                "agent": "Session calm-harbor is still running.\n\nOutput:\n...",
            },
            {
                "user": "Kill the session brisk-reef",
                "agent": "Killed session brisk-reef.",
            },
        ]
