# oceansguard/runner.py
from __future__ import annotations

import os
import subprocess
import sys
import time
from pathlib import Path
from typing import Dict, List, Optional


def _decode_bytes(b: bytes) -> str:
    if not b:
        return ""
    try:
        return b.decode("utf-8")
    except UnicodeDecodeError:
        return b.decode("utf-8", errors="replace")


def run_argv(argv: List[str], cwd: Path, check: bool = False, timeout: int | None = None) -> subprocess.CompletedProcess:
    cp = subprocess.run(
        argv,
        cwd=str(cwd),
        capture_output=True,
        check=check,
        timeout=timeout,
        env=os.environ.copy(),
    )
    cp.stdout = _decode_bytes(cp.stdout)  # type: ignore[attr-defined]
    cp.stderr = _decode_bytes(cp.stderr)  # type: ignore[attr-defined]
    return cp


def run_shell(cmd: str, cwd: Path, check: bool = False, timeout: int | None = None) -> subprocess.CompletedProcess:
    cp = subprocess.run(
        cmd,
        cwd=str(cwd),
        shell=True,
        capture_output=True,
        check=check,
        timeout=timeout,
        env=os.environ.copy(),
    )
    cp.stdout = _decode_bytes(cp.stdout)  # type: ignore[attr-defined]
    cp.stderr = _decode_bytes(cp.stderr)  # type: ignore[attr-defined]
    return cp


def run_shell_stream(
    cmd: str,
    cwd: Path,
    timeout: int | None = None,
    *,
    print_prefix: str = "",
    env: Optional[Dict[str, str]] = None,
) -> subprocess.CompletedProcess:
    """
    stdout/stderr をリアルタイムで流しつつ回収する（npm 系対策）。
    Windows でも動くように shell=True 前提。
    """
    proc = subprocess.Popen(
        cmd,
        cwd=str(cwd),
        shell=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        bufsize=1,
        universal_newlines=True,
        env=env or os.environ.copy(),
    )

    out_lines: List[str] = []
    err_lines: List[str] = []

    def _drain(pipe, collector: List[str], is_err: bool) -> None:
        assert pipe is not None
        for line in pipe:
            collector.append(line)
            if print_prefix:
                if is_err:
                    sys.stderr.write(f"{print_prefix}[stderr] {line}")
                else:
                    sys.stdout.write(f"{print_prefix}{line}")
            else:
                (sys.stderr if is_err else sys.stdout).write(line)

    import threading

    t_out = threading.Thread(target=_drain, args=(proc.stdout, out_lines, False), daemon=True)
    t_err = threading.Thread(target=_drain, args=(proc.stderr, err_lines, True), daemon=True)
    t_out.start()
    t_err.start()

    t0 = time.time()
    try:
        rc = proc.wait(timeout=timeout)
    except subprocess.TimeoutExpired:
        proc.kill()
        rc = 124
    finally:
        t_out.join(timeout=1.0)
        t_err.join(timeout=1.0)

    return subprocess.CompletedProcess(
        args=cmd,
        returncode=rc,
        stdout="".join(out_lines),
        stderr="".join(err_lines),
    )
