"""Shell execution tool."""

from __future__ import annotations

import subprocess
import uuid
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from memnixa.tools.base import Tool, ToolValidationError, paginate_text


@dataclass(slots=True)
class CommandExecutionRecord:
    """Stored command execution for follow-up pagination requests."""

    execution_id: str
    command: str
    exit_code: int
    stdout: str
    stderr: str


class RunCommandTool(Tool):
    name = "run_command"
    description = (
        "Run a shell command inside the workspace and capture paginated stdout/stderr."
    )
    parameters = {
        "type": "object",
        "additionalProperties": False,
        "properties": {
            "command": {
                "type": "string",
                "description": "Shell command to execute for a new run.",
                "minLength": 1,
            },
            "execution_id": {
                "type": "string",
                "description": "Existing command execution id for reading another page.",
                "minLength": 1,
            },
            "stream": {
                "type": "string",
                "description": "Which output stream to read.",
                "enum": ["combined", "stdout", "stderr"],
            },
            "timeout": {
                "type": "integer",
                "description": "Timeout in seconds.",
                "minimum": 1,
                "maximum": 600,
            },
            "page": {
                "type": "integer",
                "description": "1-based output page.",
                "minimum": 1,
            },
            "page_size": {
                "type": "integer",
                "description": "Number of characters per page.",
                "minimum": 256,
                "maximum": 12000,
            },
        },
    }

    def __init__(self, workspace: Path):
        self.workspace = workspace
        self._executions: dict[str, CommandExecutionRecord] = {}

    def validate_arguments(self, arguments: dict[str, Any]) -> None:
        super().validate_arguments(arguments)
        has_command = "command" in arguments
        has_execution_id = "execution_id" in arguments
        if has_command == has_execution_id:
            raise ToolValidationError(
                "provide exactly one of 'command' or 'execution_id'"
            )
        if has_execution_id and "timeout" in arguments:
            raise ToolValidationError(
                "field 'timeout' can only be used when 'command' is provided"
            )

    def execute(self, arguments: dict[str, Any]):
        stream = str(arguments.get("stream") or "combined")
        page = int(arguments.get("page") or 1)
        page_size = int(arguments.get("page_size") or 2000)

        if "execution_id" in arguments:
            record = self._executions.get(str(arguments["execution_id"]))
            if record is None:
                return self.failure(
                    "not_found",
                    f"execution_id '{arguments['execution_id']}' was not found",
                )
        else:
            timeout = int(arguments.get("timeout") or 120)
            completed = subprocess.run(
                str(arguments["command"]),
                cwd=self.workspace,
                shell=True,
                capture_output=True,
                text=True,
                timeout=timeout,
                check=False,
            )
            record = CommandExecutionRecord(
                execution_id=uuid.uuid4().hex,
                command=str(arguments["command"]),
                exit_code=completed.returncode,
                stdout=completed.stdout.strip(),
                stderr=completed.stderr.strip(),
            )
            self._executions[record.execution_id] = record

        try:
            content, pagination = paginate_text(
                _render_stream(record, stream),
                page=page,
                page_size=page_size,
            )
        except ValueError as exc:
            return self.failure("page_out_of_range", str(exc))

        return self.success(
            data={
                "execution_id": record.execution_id,
                "command": record.command,
                "exit_code": record.exit_code,
                "stream": stream,
                "content": content,
            },
            meta={
                "pagination": pagination,
                "stdout_chars": len(record.stdout),
                "stderr_chars": len(record.stderr),
            },
        )


def _render_stream(record: CommandExecutionRecord, stream: str) -> str:
    if stream == "stdout":
        return record.stdout or "(empty)"
    if stream == "stderr":
        return record.stderr or "(empty)"
    return (
        f"exit_code: {record.exit_code}\n"
        f"stdout:\n{record.stdout or '(empty)'}\n"
        f"stderr:\n{record.stderr or '(empty)'}"
    )
