from __future__ import annotations

import os
import subprocess
import time
from dataclasses import dataclass
from typing import Dict, List, Optional

from .engine import has_rust_core

try:  # pragma: no cover - optional
    import speedi_core  # type: ignore
except Exception:  # noqa: BLE001
    speedi_core = None  # type: ignore


@dataclass
class CmdResult:
    code: int
    stdout: str
    stderr: str
    duration_ms: int


def shell_py(args: List[str], cwd: Optional[str] = None, env: Optional[Dict[str, str]] = None) -> CmdResult:
    """Execute via Python subprocess only (explicit fallback)."""
    if not args:
        raise ValueError("args must contain at least a program")
    start = time.perf_counter()
    proc_env = os.environ.copy()
    if env:
        proc_env.update(env)
    try:
        cp = subprocess.run(
            args,
            cwd=cwd,
            env=proc_env,
            capture_output=True,
            text=True,
            check=False,
        )
        dur = int((time.perf_counter() - start) * 1000)
        return CmdResult(code=cp.returncode, stdout=cp.stdout, stderr=cp.stderr, duration_ms=dur)
    except Exception as e:  # noqa: BLE001
        dur = int((time.perf_counter() - start) * 1000)
        return CmdResult(code=-1, stdout="", stderr=str(e), duration_ms=dur)


def shell_rs(args: List[str], cwd: Optional[str] = None, env: Optional[Dict[str, str]] = None) -> Optional[CmdResult]:
    """Execute via Rust core only. Returns None if Rust core unavailable."""
    if not args:
        raise ValueError("args must contain at least a program")
    if not (has_rust_core() and speedi_core is not None):  # pragma: no cover
        return None
    env_list = list(env.items()) if env else None
    code, out, err, dur = speedi_core.run_cmd(args, cwd, env_list)
    return CmdResult(code=code, stdout=out, stderr=err, duration_ms=dur)


def shell(args: List[str], cwd: Optional[str] = None, env: Optional[Dict[str, str]] = None) -> CmdResult:
    """Execute a command quickly.

    Uses Rust core if available; otherwise falls back to Python subprocess.
    Returns CmdResult(code, stdout, stderr, duration_ms).
    """
    if not args:
        raise ValueError("args must contain at least a program")
    rs = shell_rs(args, cwd=cwd, env=env)
    if rs is not None:  # pragma: no cover
        return rs
    return shell_py(args, cwd=cwd, env=env)
