"""Subprocess runner and logging utilities."""

from __future__ import annotations

import os
import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Iterable, Mapping


@dataclass
class RunResult:
    cmd: list[str]
    returncode: int
    stdout: str
    stderr: str


def log_line(level: str, scope: str, message: str) -> None:
    ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
    print(f"{ts} {level:<5} {scope}: {message}")


def run(
    cmd: Iterable[str],
    *,
    log_file: str | None = None,
    env: Mapping[str, str] | None = None,
    allow_fail: bool = False,
    dry_run: bool = False,
) -> RunResult:
    """Run a command and optionally tee output to a log file."""
    cmd_list = list(cmd)
    if dry_run:
        if log_file:
            with open(log_file, "a", encoding="utf-8") as fh:
                fh.write(f"[dry-run] {' '.join(cmd_list)}\n")
        return RunResult(cmd=cmd_list, returncode=0, stdout="", stderr="")
    env_map = os.environ.copy()
    if env:
        env_map.update(env)

    proc = subprocess.Popen(
        cmd_list,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        env=env_map,
    )
    stdout, stderr = proc.communicate()

    if log_file:
        with open(log_file, "a", encoding="utf-8") as fh:
            if stdout:
                fh.write(stdout)
            if stderr:
                fh.write(stderr)

    if proc.returncode != 0 and not allow_fail:
        detail = ""
        if stderr:
            detail = f" stderr: {stderr.strip()}"
        raise RuntimeError(
            f"command failed: {' '.join(cmd_list)} (exit {proc.returncode}){detail}"
        )

    return RunResult(cmd=cmd_list, returncode=proc.returncode, stdout=stdout, stderr=stderr)
