"""Run local subprocesses with live-streamed output and clean exit handling.

Commands shell out through :func:`run` so failures surface as ``typer.Exit``
with the child's exit code (preserving the ``set -e`` semantics of the wrapped
shell scripts). Tests patch :func:`run` to assert the exact argv built.
"""
from __future__ import annotations

import subprocess
import sys
from pathlib import Path
from typing import List, Optional, Sequence

import typer

from acdc_aws_etl_pipeline.config import repo_root


def python_script(relpath: str, *args: str) -> List[str]:
    """Build an argv that runs a repo service script with the current interpreter.

    Using ``sys.executable`` guarantees the script runs inside the same
    (Poetry) environment as the CLI itself.
    """
    return [sys.executable, str(repo_root() / relpath), *[str(a) for a in args]]


def bash_script(relpath: str, *args: str) -> List[str]:
    """Build an argv that runs a repo shell script via bash."""
    return ["bash", str(repo_root() / relpath), *[str(a) for a in args]]


def run(
    argv: Sequence[str],
    *,
    cwd: Optional[Path] = None,
    env: Optional[dict] = None,
    echo: bool = True,
) -> int:
    """Run ``argv``, streaming output live. Raise ``typer.Exit`` on failure.

    Returns 0 on success. The working directory defaults to the repo root so
    wrapped scripts resolve their relative paths the same way they do today.
    """
    argv = [str(a) for a in argv]
    workdir = Path(cwd) if cwd else repo_root()
    if echo:
        typer.secho(f"$ {' '.join(argv)}", fg=typer.colors.BRIGHT_BLACK)
    try:
        completed = subprocess.run(argv, cwd=str(workdir), env=env, check=False)
    except FileNotFoundError as exc:
        typer.secho(
            f"Command not found: {argv[0]} ({exc})", fg=typer.colors.RED, err=True
        )
        raise typer.Exit(127)
    if completed.returncode != 0:
        typer.secho(
            f"Command failed (exit {completed.returncode}): {' '.join(argv)}",
            fg=typer.colors.RED,
            err=True,
        )
        raise typer.Exit(completed.returncode)
    return 0
