"""Module declaring a command execution request."""

from __future__ import annotations

import sys
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING

from tox.util.redact import redact_argv

if TYPE_CHECKING:
    from collections.abc import Sequence


class StdinSource(Enum):
    OFF = 0  #: input disabled
    USER = 1  #: input via the standard input
    API = 2  #: input via programmatic access

    @staticmethod
    def user_only() -> StdinSource:
        """:returns: ``USER`` if the standard input is tty type else ``OFF``"""
        return StdinSource.USER if sys.stdin.isatty() else StdinSource.OFF


class ExecuteRequest:
    """Defines a commands execution request."""

    def __init__(  # ruff:ignore[too-many-arguments]
        self,
        cmd: Sequence[str | Path],
        cwd: Path,
        env: dict[str, str],
        stdin: StdinSource,
        run_id: str,
        allow: list[str] | None = None,
    ) -> None:
        """Create a new execution request.

        :param cmd: the command to run
        :param cwd: the current working directory
        :param env: the environment variables
        :param stdin: the type of standard input allowed
        :param run_id: an id to identify this run

        """
        if len(cmd) == 0:
            msg = "cannot execute an empty command"
            raise ValueError(msg)
        self.cmd: list[str] = [str(i) for i in cmd]  #: the command to run
        self.cwd = cwd  #: the working directory to use
        self.env = env  #: the environment variables to use
        self.stdin = stdin  #: the type of standard input interaction allowed
        self.run_id = run_id  #: an id to identify this run
        if allow is not None and "*" in allow:
            allow = None  # if we allow everything we can just disable the check
        self.allow = allow

    @property
    def shell_cmd(self) -> str:
        """:returns: the command to run as a shell command"""
        return self._shell_cmd(redact=False)

    @property
    def shell_cmd_redacted(self) -> str:
        """:returns: the command to run as a shell command with secret-looking flag values masked"""
        return self._shell_cmd(redact=True)

    def _shell_cmd(self, *, redact: bool) -> str:
        try:
            exe = str(Path(self.cmd[0]).relative_to(self.cwd))
        except ValueError:
            exe = self.cmd[0]
        cmd = [exe, *self.cmd[1:]]
        if redact:
            cmd = redact_argv(cmd)
        return shell_cmd(cmd)

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}(cmd={self.cmd!r}, cwd={self.cwd!r}, env=..., stdin={self.stdin!r})"


def shell_cmd(cmd: Sequence[str]) -> str:
    if sys.platform == "win32":  # pragma: win32 cover
        from subprocess import list2cmdline  # ruff:ignore[import-outside-top-level]

        return list2cmdline(tuple(str(x) for x in cmd))
    # pragma: win32 no cover
    from shlex import quote as shlex_quote  # ruff:ignore[import-outside-top-level]

    return " ".join(shlex_quote(str(x)) for x in cmd)


__all__ = (
    "ExecuteRequest",
    "StdinSource",
    "shell_cmd",
)
