from __future__ import annotations

import shlex
import subprocess
from dataclasses import dataclass
from pathlib import Path


@dataclass
class CommandDecision:
    should_execute: bool
    needs_approval: bool
    reason: str
    argv: list[str]
    display_command: str


@dataclass
class CommandResult:
    display_command: str
    exit_code: int
    output: str


class LocalCommandExecutor:
    def __init__(self, workdir: Path, allowed_commands: set[str], max_output_chars: int = 4000) -> None:
        self.workdir = workdir
        self.allowed_commands = allowed_commands
        self.max_output_chars = max_output_chars

    def prepare(self, raw_command: str, *, approved: bool = False) -> CommandDecision:
        text = raw_command.strip()
        if not text:
            return CommandDecision(
                should_execute=False,
                needs_approval=False,
                reason="Empty command.",
                argv=[],
                display_command="",
            )

        try:
            argv = shlex.split(text)
        except ValueError as exc:
            return CommandDecision(
                should_execute=False,
                needs_approval=False,
                reason=f"Invalid command syntax: {exc}",
                argv=[],
                display_command=text,
            )

        if not argv:
            return CommandDecision(
                should_execute=False,
                needs_approval=False,
                reason="Empty command.",
                argv=[],
                display_command=text,
            )

        command_name = argv[0]
        if approved:
            return CommandDecision(
                should_execute=True,
                needs_approval=False,
                reason="Approved command ready to execute.",
                argv=argv,
                display_command=text,
            )

        if command_name not in self.allowed_commands:
            return CommandDecision(
                should_execute=False,
                needs_approval=True,
                reason=f"Command '{command_name}' is outside the allowlist and needs /approve.",
                argv=argv,
                display_command=text,
            )

        return CommandDecision(
            should_execute=True,
            needs_approval=False,
            reason="Command is allowed.",
            argv=argv,
            display_command=text,
        )

    def execute(self, argv: list[str], display_command: str, *, workdir: Path | None = None) -> CommandResult:
        completed = subprocess.run(
            argv,
            cwd=workdir or self.workdir,
            capture_output=True,
            text=True,
            encoding="utf-8",
            errors="replace",
            check=False,
        )
        output = (completed.stdout or "") + (completed.stderr or "")
        output = output.strip() or "(no output)"
        if len(output) > self.max_output_chars:
            output = output[: self.max_output_chars].rstrip() + "\n...[truncated]"

        return CommandResult(
            display_command=display_command,
            exit_code=completed.returncode,
            output=output,
        )
